# task-mcp
Agent-safe task runner MCP server backed by [just](https://just.systems/).
Exposes predefined justfile recipes as MCP tools, with access control via the `[allow-agent]` group attribute.
## Tools
| `session_start` | Set the working directory for this session. Must be called before `run` or `list` (unless `list` is given an explicit `justfile` path). | destructive |
| `list` | List available tasks from justfile. Returns names, descriptions, parameters, and groups. Requires `session_start` when no `justfile` parameter is given. | read-only, idempotent |
| `run` | Execute a predefined task. Only tasks visible in `list` can be run. Requires `session_start`. | destructive |
| `logs` | Retrieve execution logs of recent runs. Returns summary list or full output by task ID. | read-only, idempotent |
| `info` | Show current session state: active workdir, resolved justfile path, and task mode. | read-only, idempotent |
## Installation
```bash
cargo install --path .
```
Requires `just` to be installed and available on `PATH`.
## Usage
Start as MCP server (stdio transport):
```bash
task-mcp --mcp
```
### Claude Code integration
Add to your Claude Code MCP configuration:
```json
{
"mcpServers": {
"task-mcp": {
"command": "task-mcp",
"args": ["--mcp"]
}
}
}
```
## Configuration
Configuration is loaded from `.task-mcp.env` in the current directory (if present), then from environment variables.
| `TASK_MCP_MODE` | `agent-only` | `agent-only`: only `[allow-agent]` tagged recipes; `all`: all non-private recipes |
| `TASK_MCP_JUSTFILE` | auto-detect | Path to justfile (relative or absolute) |
| `TASK_MCP_ALLOWED_DIRS` | _(any)_ | Comma-separated list of directories allowed as session working directories. If unset, any directory is accepted. Paths are canonicalized on parse. |
`.task-mcp.env` example:
```env
TASK_MCP_MODE=agent-only
TASK_MCP_JUSTFILE=./justfile
TASK_MCP_ALLOWED_DIRS=/home/user/projects/my-project,/home/user/projects/other-project
```
## Justfile setup
Mark recipes as agent-safe using the `[group('agent')]` attribute:
```just
# Build the project
[group('agent')]
build:
cargo build --release
# Run tests
[group('agent')]
test filter="":
cargo test {{filter}}
# Deploy to production — NOT exposed to agent
deploy:
./scripts/deploy.sh
```
In `agent-only` mode (default), only `build` and `test` are returned by `list` and executable via `run`. The `deploy` recipe is hidden.
In `all` mode, all non-private recipes are exposed.
## Workflow
The typical agent workflow is:
1. Call `session_start` with the project directory to establish the working context.
2. Call `list` to discover available tasks.
3. Call `run` to execute a task.
4. Call `logs` to inspect output of a past run.
5. Call `info` at any point to confirm the current session state.
`list` can also be called without a prior `session_start` when an explicit `justfile` path is provided — this is useful for exploration before committing to a working directory.
## Tool details
### session_start
```json
{ "workdir": "/absolute/path/to/project" }
```
- `workdir`: absolute path to the project directory; must be accessible and, if `TASK_MCP_ALLOWED_DIRS` is set, must fall within one of the allowed directories
- Returns a confirmation message with the resolved path on success
### info
No parameters required. Returns:
- `workdir`: active working directory (or `null` if `session_start` has not been called)
- `justfile`: resolved justfile path (or `null` if not resolvable from the current session state)
- `mode`: current task mode (`agent-only` or `all`)
### list
```json
{
"filter": "agent",
"justfile": "./justfile"
}
```
- `filter`: optional group name to narrow results
- `justfile`: optional path override
### run
```json
{
"task_name": "test",
"args": { "filter": "unit" },
"timeout_secs": 120
}
```
- `task_name`: must appear in `list` output
- `args`: named arguments matching recipe parameter names
- `timeout_secs`: default 60
Returns a `TaskExecution` object with `id`, `exit_code`, `stdout`, `stderr`, `duration_ms`, and `truncated` fields.
### logs
```json
{ "task_id": "<uuid>", "tail": 50 }
```
- `task_id`: omit to get summary of the 10 most recent executions; provide to get full output
- `tail`: restrict stdout to the last N lines (only when `task_id` is provided)
In-memory only; logs are lost on server restart.
## Output truncation
stdout and stderr are capped at 100 KB per execution. When truncated, the `truncated` field is `true` and the head and tail of the output are preserved. Use `logs` with `tail` to retrieve specific portions.
## Security
- Only recipes whitelisted by `list` can be executed via `run`
- `session_start` validates the requested directory against `TASK_MCP_ALLOWED_DIRS` using canonicalized `Path::starts_with`; symlinks are resolved before comparison to prevent traversal attacks
- Argument values are validated to reject shell metacharacters
- Execution timeout is enforced (default: 60 s)
- `open_world_hint = false` on all tools: no external network calls
## License
MIT OR Apache-2.0