task-mcp 0.4.0

MCP server for task runner integration — Agent-safe harness for defined tasks
Documentation
# 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 `[group('allow-agent')]` attribute (or the legacy `# [allow-agent]` doc comment).

`allow-agent` is a **security boundary**: in the default `agent-only` mode, recipes without an `allow-agent` marker are NEVER exposed via MCP. The mode is selected by the `TASK_MCP_MODE` environment variable, set OUTSIDE the MCP. Reading the justfile directly bypasses this guard, but is not the canonical path for agent interaction.

## Tools

| Tool | Description | Annotations |
|------|-------------|-------------|
| `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.

| Variable | Default | Description |
|----------|---------|-------------|
| `TASK_MCP_MODE` | `agent-only` | `agent-only`: only `allow-agent` tagged recipes (Pattern A `[group('allow-agent')]` or Pattern B `# [allow-agent]`); `all`: all non-private recipes. This is the security guard switch — set it OUTSIDE the MCP. |
| `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

Two equivalent markers are supported for tagging recipes as agent-safe.

### Pattern A (recommended): `[group('allow-agent')]` attribute

This is the just-native form. Stack additional `[group('...')]` attributes on separate lines if you also want functional grouping.

```just
# Build the project
[group('allow-agent')]
build:
    cargo build --release

# Run tests
[group('allow-agent')]
test filter="":
    cargo test {{filter}}

# Profile build — agent-safe AND in the `profile` functional group
[group('allow-agent')]
[group('profile')]
profile-build:
    cargo build --profile=release-with-debug

# Deploy to production — NOT exposed to agent
deploy:
    ./scripts/deploy.sh
```

### Pattern B (legacy): `# [allow-agent]` doc comment

Supported for compatibility. **Caveat**: `just` only keeps the comment line immediately before a recipe as its `doc`, so combining `# [allow-agent]` with a descriptive doc comment causes one of the two to be dropped (the marker becomes invisible). Prefer Pattern A in new recipes.

```just
# [allow-agent]
info:
    echo "task-mcp"
```

### Mode behavior

In `agent-only` mode (default), only allow-agent tagged recipes are returned by `list` and executable via `run`. Untagged recipes are hidden — they never enter the agent context via MCP.

In `all` mode, all non-private recipes are exposed. Reading the justfile directly always shows everything, but that bypasses the MCP guard and is not the canonical path.

## 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": "profile",
  "justfile": "./justfile"
}
```

- `filter`: optional functional group name (e.g. `profile`, `release`) to narrow results. Supports glob wildcards (`*`, `?`). Filtering by `allow-agent` is not meaningful in `agent-only` mode (every visible recipe already carries it).
- `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