AgentStack
MCP unreviewed MIT Self-run

Agent Dispatch

mcp-ginkida-agent-dispatch · by ginkida

MCP server + CLI that lets Claude Code agents delegate tasks to agents in other project directories. Multi-agent orchestration: parallel dispatch, sessions, async jobs, agent dialogues.

No reviews yet
0 installs
13 views
0.0% view→install

Install

$ agentstack add mcp-ginkida-agent-dispatch

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Agent Dispatch? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

agent-dispatch

[](https://pypi.org/project/agent-dispatch/) [](https://github.com/ginkida/agent-dispatch/actions/workflows/ci.yml) [](https://pypi.org/project/agent-dispatch/) [](LICENSE)

MCP server that lets Claude Code agents delegate tasks to agents in other project directories.

Each agent runs as a separate claude -p session in its own project directory — inheriting that project's MCP servers, CLAUDE.md, and tools. The calling agent just gets the result back.

Works with OAuth, API key, and Claude subscription authentication.

> AI agents: this README is the canonical doc for using the tool — setup: [Quick Start](#quick-start) (every step has a deterministic verify), first call: [dispatch](#dispatch), tool selection: [Which Tool to Use](#which-tool-to-use), failure handling: [Error Recovery](#error-recovery). Working on this repo instead? See [AGENTS.md](AGENTS.md).

Quick Start

Prerequisite: the Claude Code CLI must be installed and authenticated. Check first:

claude --version   # must print a version — if it fails, install Claude Code before continuing

Then:

pip install agent-dispatch   # or: pipx install agent-dispatch

# 1. Create config + register the MCP server with Claude Code (user scope)
agent-dispatch init

# 2. Register project directories as agents — REPLACE the example paths with
#    real directories on your machine; they must exist (~ is expanded, relative
#    paths are resolved). Descriptions are auto-generated from project files.
#    No second project handy? Use the zero-setup block below instead.
agent-dispatch add infra ~/projects/infra
agent-dispatch add backend ~/projects/backend

# 3. Smoke test — dispatches a real task to the agent added in step 2 and prints
#    the answer; exit 0 on success. Default task when none given:
#    "What project is this? Describe in one sentence."
agent-dispatch test infra

# 4. Verify the whole install — prints "All checks passed." and exits 0 on success
agent-dispatch doctor

Zero-setup alternative for steps 2–3 (no second project needed — registers the current directory):

agent-dispatch add self . && agent-dispatch test self "Say hello"

Every Claude Code session now has the dispatch tools. Independent check: claude mcp list must print a line starting with agent-dispatch:. From inside a Claude Code session, the first MCP calls are list_agents(), then [dispatch(...)](#dispatch).

If init fails to register the MCP server (prints a warning instead of Registered MCP server), register manually:

claude mcp add-json agent-dispatch "{\"type\":\"stdio\",\"command\":\"$(which agent-dispatch)\",\"args\":[\"serve\"]}" --scope user

If test fails with a permission error (error_type: "permission"), grant tool access and re-test:

agent-dispatch update infra --allowed-tools "Bash,Read,Grep"      # least privilege
# or, if the agent needs everything (see SECURITY.md for the trade-off):
agent-dispatch update infra --permission-mode bypassPermissions

When to Dispatch

Do dispatch when a task needs tools, files, or context from another project:

  • Check container logs via infra agent's Portainer MCP
  • Query a database via db agent's postgres MCP
  • Read code or run tests in another repository

Don't dispatch when you can do it yourself — dispatching spawns a full Claude session.

MCP Tools Reference

list_agents

Lists all configured agents. Call this first to see what's available.

// Response (capability + permission fields shown only when populated)
[
  {
    "name": "infra",
    "directory": "/home/user/projects/infra",
    "description": "Infrastructure agent. MCP: portainer. Stack: Python, Docker",
    "healthy": true,
    "has_claude_md": true,
    "has_mcp_config": true,
    "mcp_servers": ["portainer", "postgres"],
    "stacks": ["Python", "Docker"],
    "dbs": ["Alembic"],
    "capabilities": ["docker_logs", "deploy_debug"],
    "risky_capabilities": ["restart_services"],
    "permission_mode": "bypassPermissions",
    "allowed_tools": ["Bash", "Read", "Grep"]
  }
]

mcp_servers, stacks, and dbs are detected from the agent's project files (.mcp.json, Dockerfile, pyproject.toml, Cargo.toml, prisma/, alembic.ini, etc.) so callers can pick the right agent without dispatching a probe.

inspect_agent

Cheap detailed lookup — reads the agent's files without spawning a claude session. Returns the full config (timeout, model, budget, permission mode, allowed/disallowed tools), detected MCP/stacks/DBs, plus short previews of CLAUDE.md and README.md when present.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | name | string | yes | Agent name from list_agents | | preview_lines | int | no | Max lines of CLAUDE.md/README.md (default 40, max 200, 0 disables) |

Use this before dispatch_async/dispatch to confirm an agent has the tools and context for your task — much cheaper than a probe dispatch.

dispatch

One-shot task delegation. Results are cached — identical requests within TTL return instantly.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | agent | string | yes | Agent name from list_agents | | task | string | yes | What to do — be specific, the agent has no context from your conversation | | context | string | no | Extra context: error messages, code snippets, stack traces | | caller | string | no | Your project/role — helps the agent understand who's asking | | goal | string | no | Broader objective — helps the agent make better trade-offs | | response_format | string | no | "json" to request a single JSON value; the parsed result lands in parsed_result. Empty = free-form text. | | return_ref | bool | no | When true, returns just a ref + summary preview instead of the full result text. Use fetch_result(ref) to load the full text on demand. | | summary_chars | int | no | Max chars of result text to include in the ref response (default 500). | | timeout_seconds | int | no | One-off timeout override for this call (0 = agent's configured timeout; clamped to 10–7200). No config edit needed for known-long tasks. |

# Call — recommended form (always include caller and goal)
dispatch(
    agent="infra",                # must exist in list_agents()
    task="Check container logs for errors related to the scheduler service",
    context="Error: TypeError at scheduler.py:42",
    caller="backend",             # your project/role
    goal="debug production crash" # the broader objective
)
// Response (success)
{
  "agent": "infra",
  "success": true,
  "result": "Found 3 errors in container logs: TypeError in scheduler.py:42...",
  "session_id": "sess-abc-123",
  "cost_usd": 0.02,
  "duration_ms": 5000,
  "num_turns": 2
}

// Response (failure — error_type helps you handle programmatically)
{
  "agent": "infra",
  "success": false,
  "result": "",
  "error": "Tool_use is not allowed in this permission mode\n\nHint: ...",
  "error_type": "permission"
}

error_type values: permission (tool/action denied), timeout, recursion (dispatch depth exceeded), not_found (missing directory or CLI), cli_error (other failures). Permission errors include an actionable hint.

Resumable timeouts: every fresh dispatch pre-assigns a session UUID (--session-id), so a timed-out dispatch still returns a session_id — the partial transcript survives the kill. The timeout error spells out the recovery: resume with dispatch_session(agent, "Continue where you left off", session_id=...), retry with a bigger timeout_seconds, or use dispatch_async.

Denied-tools visibility: in non-interactive mode the claude CLI auto-denies tools the agent isn't allowed to use — the agent then often "succeeds" with an answer like "I need your permission for one read-only query". When that happens the response carries the deterministic signal: denied_tools (parsed from the CLI's permission_denials) plus a hint explaining the result may be incomplete and how to grant access. success stays true — it's a soft signal, not a failure.

// Response (success, but a tool was blocked)
{
  "agent": "analysis",
  "success": true,
  "result": "Here is the offline mapping. To finish I'd need to run one read-only query...",
  "denied_tools": ["Bash"],
  "hint": "1 tool call(s) were denied by permissions: Bash. The result may be incomplete..."
}

Structured JSON output: pass response_format="json" to ask the agent for a single JSON value. The runner appends an instruction footer ("respond with a single valid JSON value, no fences, no prose") and on success parses the response — the parsed value lands in parsed_result. The raw text is always in result. Parse failures leave parsed_result=None but don't fail the dispatch (soft mode).

// Response with response_format="json"
{
  "agent": "infra",
  "success": true,
  "result": "{\"errors\": 3, \"first_at\": \"14:02\"}",
  "parsed_result": {"errors": 3, "first_at": "14:02"}
}

Always pass caller and goal — the dispatched agent sees a structured prompt:

## Goal
debug production crash

## Dispatched by
backend

## Context
Error: TypeError at scheduler.py:42

## Task
Check container logs for recent errors related to the scheduler service

dispatch_session

Multi-turn: continue a conversation with an agent. First call starts a session, pass session_id back to continue. Never cached.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | agent | string | yes | Agent name | | task | string | yes | Task or follow-up message | | session_id | string | no | From previous response — empty for new session | | context | string | no | Extra context | | caller | string | no | Who is dispatching | | goal | string | no | Broader objective | | timeout_seconds | int | no | One-off timeout override (0 = agent default; clamped to 10–7200) |

dispatch_session is also the timeout recovery path: a timed-out dispatch returns a session_id — pass it here with task="Continue where you left off" to salvage the partial work instead of restarting.

Turn 1: dispatch_session("infra", "List running containers")
         → session_id: "sess-abc"

Turn 2: dispatch_session("infra", "Restart the nginx one", session_id="sess-abc")
         → agent remembers previous context

dispatch_parallel

Run multiple tasks concurrently. Much faster than sequential dispatch calls.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | dispatches | string (JSON) | yes | JSON array of {"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?"} | | aggregate | string | no | Agent name to synthesize all results into one answer |

Important: dispatches is a JSON string, not a list.

// Input
[
  {"agent": "infra", "task": "check pod logs for errors", "caller": "backend", "goal": "debug crash"},
  {"agent": "db", "task": "are all migrations applied?", "caller": "backend", "goal": "debug crash"}
]
// Response (without aggregate)
[
  {"agent": "infra", "success": true, "result": "No errors in pod logs", ...},
  {"agent": "db", "success": true, "result": "All migrations applied", ...}
]
// Response (with aggregate="backend")
{
  "individual_results": [
    {"agent": "infra", "success": true, "result": "No errors in pod logs", ...},
    {"agent": "db", "success": true, "result": "All migrations applied", ...}
  ],
  "aggregated": {
    "agent": "backend",
    "success": true,
    "result": "Summary: all systems nominal. No pod errors, all migrations applied."
  }
}

dispatch_stream

Same as dispatch but shows live progress while the agent works. Use for long-running tasks. Not cached.

Parameters are the same as dispatch except return_ref/summary_chars (streaming is incompatible with ref-mode).

dispatch_dialogue

Two agents collaborate through multi-turn conversation. Never cached.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | requester | string | yes | Agent with the problem/context | | responder | string | yes | Agent with the expertise/tools | | topic | string | yes | Problem or question to discuss | | max_rounds | int | no | Max back-and-forth rounds (default: 3, max: 10) |

Each round costs up to 2 dispatches. Agents signal completion with [RESOLVED].

// Response
{
  "resolved": true,
  "rounds": 2,
  "total_cost_usd": 0.04,
  "total_duration_ms": 12000,
  "final_answer": "Staging had 1 pending migration. Applied successfully.",
  "conversation": [
    {"agent": "db", "role": "responder", "round": 1, "message": "Which environment?", "cost_usd": 0.01},
    {"agent": "backend", "role": "requester", "round": 1, "message": "Staging", "cost_usd": 0.01},
    {"agent": "db", "role": "responder", "round": 2, "message": "Applied. [RESOLVED]", "cost_usd": 0.01}
  ]
}

add_agent

Register a new project directory as an agent. Description is auto-generated from project files if omitted.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | name | string | yes | Agent name (letters, digits, hyphens, underscores) | | directory | string | yes | Path to an existing project directory (~ is expanded, relative paths resolved) | | description | string | no | What this agent can do — auto-generated if empty | | timeout | int | no | Timeout in seconds (0 = use global default) | | max_budget_usd | float | no | Max cost in USD per dispatch (0 = no limit) | | permission_mode | string | no | Permission mode (e.g. default, plan, bypassPermissions) | | allowed_tools | string | no | Comma-separated allowed tools (e.g. "Bash,Read,Edit") | | disallowed_tools | string | no | Comma-separated disallowed tools |

update_agent

Update an existing agent's configuration. Only non-empty fields are changed. Pass "none" to clear a field.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | name | string | yes | Agent name to update | | description | string | no | New description | | timeout | int | no | New timeout (0 = don't change) | | max_budget_usd | float | no | New budget limit (0 = don't change, negative = clear the limit) | | model | string | no | Model override. "none" to clear | | permission_mode | string | no | Permission mode. "none" to clear | | allowed_tools | string | no | Comma-separated. "none" to clear | | disallowed_tools | string | no | Comma-separated. "none" to clear |

remove_agent

Remove an agent from config.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | name | string | yes | Agent name to remove |

cache_stats / cache_clear

View cache hit rate and size, or clear all cached results.

Result references — return_ref + fetch_result

For dispatches whose result text is large (audits, log dumps, code searches), passing the full text back inflates the calling agent's context. Use return_ref=True to get just a small reference instead:

dispatch(agent="infra", task="audit every container", return_ref=True, summary_chars=200)
  -> {"ref": "8f3a...e1", "agent": "infra", "success": true,
      "size": 14823, "summary_chars": 200,
      "summary": "Inspected 32 containers. Found 3 OOM kills in the last hour:\n- worker-3...",
      "cost_usd": 0.08, "duration_ms": 9200}

// Later, when you actually need to read the result:
fetch_result(ref="8f3a...e1")              -> full DispatchResult JSON
fetc

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ginkida](https://github.com/ginkida)
- **Source:** [ginkida/agent-dispatch](https://github.com/ginkida/agent-dispatch)
- **License:** MIT
- **Homepage:** https://pypi.org/project/agent-dispatch/

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.