task-mcp 0.5.0

MCP server for task runner integration — Agent-safe harness for defined tasks
Documentation
# Execution Model

task-mcp が recipe 引数をどのように処理するかの説明。recipe 著者向け。

## 引数の流れ

```
MCP Client (JSON)
  │
  ▼
task-mcp (Rust)
  │  Command::new("just").arg(recipe_name).arg(value)
  │  ※ shell を経由しない (OS-level argv 直渡し)
  ▼
just
  │  recipe body 内の {{param}} をテキスト置換
  │  ※ shebang / linewise 問わず置換が発生する
  ▼
shell / interpreter
  │  置換済みの recipe body を実行
  │  ※ linewise: 各行を sh -c で実行
  │  ※ shebang: body を一時ファイルに書き出して実行
  ▼
実行結果
```

## task-mcp の責務

task-mcp は引数を **制御文字 (`\n`, `\r`) のみ拒否** する。shell metacharacter (`|`, `&`, `;`, `$`, `` ` `` 等) は拒否しない。

理由:

- task-mcp は `Command::new("just").arg()` で just を起動しており、shell を経由しない
- MCP 引数は構造化 JSON であり、shell 入力ではない
- OWASP MCP05:2025 の safe pattern (structured parameters) に準拠済み
- shell metacharacter の拒否は「防御対象が存在しない防御」

## just の責務 (recipe body 内の引数展開)

**ここが recipe 著者の責任範囲。**

just は recipe body 内の `{{param}}` をテキスト置換する。この置換は shebang / linewise を問わず発生する。置換結果がどう解釈されるかは recipe body の書き方に依存する。

### 危険なパターン

```just
# NG: ダブルクォート内に {{param}} を直接埋め込む
[group('allow-agent')]
example title:
    echo "{{title}}"
```

この場合、`title` に `"`, `` ` ``, `$` が含まれると shell が解釈する:

| 文字 | 挙動 |
|---|---|
| `"` | クォートが壊れる (データ欠損 or 構文エラー) |
| `` ` `` | コマンド置換として実行される |
| `$` | 変数展開される (`$HOME` → 実際のパス、`$1` → unbound variable エラー) |

### 安全なパターン: positional-arguments

```just
set positional-arguments

# OK: just が {{param}} 置換せず、shell の $1 として安全に渡す
[group('allow-agent')]
[positional-arguments]
example title:
    #!/usr/bin/env bash
    echo "$1"
```

`positional-arguments` を使うと:

1. just は `{{param}}` のテキスト置換を**行わない**
2. 引数は shell の positional parameter (`$1`, `$2`, ...) として渡される
3. `"$1"` でダブルクォートすれば shell 解釈を完全に回避できる

### 安全なパターン: jq --arg 経由

JSON を構築する recipe では、jq の `--arg` に shell 変数を渡す:

```just
set positional-arguments

[group('allow-agent')]
[positional-arguments]
create-item title:
    #!/usr/bin/env bash
    jq -n --arg title "$1" '{ title: $title }'
```

## content型引数

通常の `args`(positional argument)とは別に、`content` フィールドで任意の UTF-8 テキスト(改行含む)を渡せる。

### フロー

```
MCP Client (JSON)
  │  content: {"body": "line1\nline2"}
  ▼
task-mcp (Rust)
  │  Command::env("TASK_MCP_CONTENT_BODY", "line1\nline2")
  │  ※ positional arg ではなく env var として渡す
  │  ※ validate_arg_value をバイパス(改行含む任意 UTF-8 が通る)
  ▼
just
  │  env var を継承して recipe を実行
  ▼
shell / interpreter
  │  $TASK_MCP_CONTENT_BODY として参照可能
  ▼
実行結果
```

### args との違い

| 項目 | `args` | `content` |
|---|---|---|
| 渡し方 | positional argument (`just recipe value`) | env var (`TASK_MCP_CONTENT_KEY=value`) |
| バリデーション | `\n` / `\r` を拒否 | key のみ検証、value はバリデーションなし |
| 改行 | 不可 | 可(任意 UTF-8) |
| recipe 側の参照 | `{{param}}` または `$N` | `$TASK_MCP_CONTENT_KEY` |

### key のバリデーション

key は `^[A-Za-z][A-Za-z0-9_]*$` に一致する必要がある(env var 名として有効な文字のみ)。

### env var 命名規則

`TASK_MCP_CONTENT_{KEY}` — KEY は大文字に変換される。

例: `"body"` → `TASK_MCP_CONTENT_BODY`

### recipe 使用例

```just
[group('allow-agent')]
write-note:
    #!/usr/bin/env bash
    echo "$TASK_MCP_CONTENT_BODY" > /tmp/note.txt
```

呼び出し JSON:

```json
{
  "task_name": "write-note",
  "content": {
    "body": "line1\nline2\nline3"
  }
}
```

`args` と `content` の組み合わせも可能(`args` は positional argument、`content` は env var として独立して渡される)。

### サイズ制限

content value は OS の env var サイズ制限に依存する(通常 128 KB/var 程度、合計 ~2 MB)。巨大テキストの渡し方には注意し、必要に応じてファイルパス渡しを検討する。

## 推奨事項

| 項目 | 推奨 |
|---|---|
| recipe 形式 | shebang recipe (`#!/usr/bin/env bash`) |
| 引数アクセス | `positional-arguments` + `"$1"` |
| `{{param}}` の使用 | 固定値・フラグのみ (ユーザー入力を `{{param}}` で渡さない) |
| JSON 構築 | `jq --arg` 経由 (文字列を直接埋め込まない) |
| content 引数 | shebang recipe + `$TASK_MCP_CONTENT_*` 環境変数参照 |

## まとめ

```
task-mcp の責務: 制御文字の拒否 (args)、content key のバリデーション
recipe 著者の責務: 引数を安全に扱う recipe body の記述
```

task-mcp は recipe 内部の実装詳細に介入しない。recipe が引数をどう使うかは recipe 著者が制御する。`positional-arguments` パターンを使えば任意の文字列を安全に渡せる。改行を含むテキストは `content` フィールドを使う。