{"slug": "show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp", "title": "Show HN: Sub-Agent MCP: LLM delegation and sub-agent orchestration via MCP", "summary": "A developer released Sub-Agent MCP, a production-ready Python MCP server that enables parent LLMs to delegate tasks to specialized sub-agents defined in YAML configuration files. The server registers each sub-agent as an MCP tool with its own LLM, system prompt, and optional downstream tool servers, allowing parent models to call agents like \"researcher\" directly without context bloat. The tool aims to streamline multi-agent orchestration for AI coding assistants like Cursor by keeping parent models lightweight while giving each sub-agent role-specific tools and configurations.", "body_md": "Production-ready Python MCP server for **LLM delegation and sub-agent orchestration**. A parent LLM (for example, Cursor’s agent) connects to this server and delegates work by calling a **tool named after each agent** defined in YAML (for example, `researcher`\n\n).\n\nEach sub-agent is defined in YAML with its own LLM, system prompt, and optional downstream MCP tool servers.\n\n[What is this?](#what-is-this)[Why use it?](#why-use-it)[Features](#features)[Prerequisites](#prerequisites)[Quick start](#quick-start)[Verify installation](#verify-installation)[Connect Cursor](#connect-cursor)[How it works](#how-it-works)[Agent configuration](#agent-configuration)[MCP tools reference](#mcp-tools-reference)[Environment variables](#environment-variables)[Project layout](#project-layout)[Development](#development)[Docker image and releases](#docker-image-and-releases)[Troubleshooting](#troubleshooting)[License](#license)\n\nSub-Agent MCP sits between a **parent LLM** and one or more **specialized sub-agents**:\n\n- The parent connects to this server over\n**Streamable HTTP** at`/mcp`\n\n. - At startup, each agent in\n`agents.yaml`\n\nis registered as an MCP tool named by its`id`\n\n. - The parent calls that tool with a\n`prompt`\n\n; the server runs the sub-agent and returns the final response.\n\nEach sub-agent is a [LangChain](https://github.com/langchain-ai/langchain) agent with its own OpenAI-compatible LLM, system prompt, and optional connections to other MCP servers (filesystem, search, your own tools, and so on). Tool access can be restricted per agent with an allowlist.\n\n```\nsequenceDiagram\n    participant Parent as Parent_LLM\n    participant SAMCP as Sub_Agent_MCP\n    participant Agent as LangChain_sub_agent\n    participant LLM as OpenAI_compatible_LLM\n    participant Tools as Downstream_MCP_servers\n\n    Parent->>SAMCP: researcher(prompt)\n    SAMCP->>Agent: build agent + downstream tools\n    Agent->>LLM: reasoning loop\n    Agent->>Tools: MCP tool calls\n    Tools-->>Agent: tool results\n    LLM-->>Agent: final answer\n    Agent-->>SAMCP: response text\n    SAMCP-->>Parent: {\"response\": \"...\"}\n```\n\nThis is different from giving one agent every tool in the workspace: the parent stays lightweight, roles stay explicit, and each sub-agent only sees the MCP servers and tools you configure for it.\n\n**Delegation without context bloat**— The parent calls agent tools directly; it does not need every downstream tool schema in its own context.** Per-role configuration**— Different`id`\n\ns can use different models, prompts, MCP servers, and tool allowlists.**Production-oriented**— Pydantic-validated YAML, structured logging, Docker health checks, CI, and GHCR images on release tags.** OpenAI-compatible providers**— Point`llm.base_uri`\n\nat OpenAI, Azure, Ollama, LM Studio, or any compatible API.\n\n**Transport**\n\n- Streamable HTTP only (\n`streamable-http`\n\n); no stdio or legacy SSE.\n\n**Configuration**\n\n- YAML agent definitions with strict Pydantic validation.\n- Environment substitution:\n`${VAR}`\n\nand`${VAR:-default}`\n\n.\n\n**Runtime**\n\n- LangChain 1.x\n`create_agent`\n\nwith OpenAI-compatible chat models. - Per-agent MCP connections via\n`langchain-mcp-adapters`\n\n, with optional`server.tool`\n\nallowlists.\n\n**Security**\n\n- Agent tool descriptions never expose API keys.\n\n**Operations**\n\n- Structured logging (\n[structlog](https://www.structlog.org/)). - Docker image with health check; GitHub Actions CI.\n- Container images published to GHCR on version tags (\n`v0.x.y`\n\n).\n\n**MCP tools exposed by this server**\n\nEach agent in `agents.yaml`\n\nis registered as a tool named by its `id`\n\n(for example, `researcher`\n\n). Each tool accepts a single `prompt`\n\nargument and runs that sub-agent.\n\n| Requirement | Notes |\n|---|---|\n| Python 3.10+ | CI uses 3.12; `requires-python >= 3.10` in `pyproject.toml` |\n| API key for your LLM provider | Default example uses `OPENAI_API_KEY` |\n`pip` |\n\nChoose one path. **Docker Compose is the fastest way** to run the full demo stack (server + mock tool servers).\n\n```\nexport OPENAI_API_KEY=sk-...\ndocker compose up --build\n```\n\n| Service | Port | Endpoint |\n|---|---|---|\n| Sub-Agent MCP | 8000 | `http://localhost:8000/mcp` |\n| Mock filesystem MCP | 8001 | `http://localhost:8001/mcp` |\n| Mock search MCP | 8002 | `http://localhost:8002/mcp` |\n\nThe bundled [config/agents.yaml](/stormaref/Sub-Agent-MCP/blob/main/config/agents.yaml) uses **Docker network hostnames** (`filesystem-mcp`\n\n, `search-mcp`\n\n), which resolve correctly inside Compose.\n\n```\nuv sync --dev\n# or: pip install -e \".[dev]\"\n\ncp config/agents.example.yaml config/agents.yaml   # if you don't have config/agents.yaml yet\nexport OPENAI_API_KEY=sk-...\n\npython -m sub_agent_mcp.main\n# or: uv run sub-agent-mcp\n```\n\nServer listens at `http://0.0.0.0:8000/mcp`\n\n(reachable as `http://localhost:8000/mcp`\n\nfrom your machine).\n\n**Important:** The example config points MCP servers at Docker service names. For local Python you must either run the mock servers and use `localhost`\n\nURLs (see table below), or run only the mocks via Compose:\n\n```\n# Terminal 1: mock tool servers only\ndocker compose up filesystem-mcp search-mcp\n\n# Terminal 2: sub-agent server (after editing agents.yaml URLs to localhost)\nexport OPENAI_API_KEY=sk-...\npython -m sub_agent_mcp.main\n```\n\n| Environment | filesystem MCP URL | search MCP URL |\n|---|---|---|\n| Docker Compose network | `http://filesystem-mcp:8001/mcp` |\n`http://search-mcp:8002/mcp` |\n| Host machine / local Python | `http://localhost:8001/mcp` |\n`http://localhost:8002/mcp` |\n\nImages are published on **git tags** matching `v*`\n\n(for example `v0.1.2`\n\n), not on every push to `main`\n\n.\n\n```\ndocker run -p 8000:8000 \\\n  -e OPENAI_API_KEY=sk-... \\\n  -v \"$(pwd)/config/agents.yaml:/app/config/agents.yaml:ro\" \\\n  ghcr.io/stormaref/sub-agent-mcp:latest\n```\n\nMount your own `agents.yaml`\n\nand ensure MCP `url`\n\nvalues are reachable from inside the container (use host networking, service names, or `host.docker.internal`\n\nas appropriate).\n\n**OpenAPI document** (generated from registered MCP tools):\n\n```\ncurl -s http://localhost:8000/mcp/openapi.json | head\n```\n\n**Open WebUI (OpenAPI tool server)** — Register as **OpenAPI** (not MCP):\n\n| Field | Value |\n|---|---|\nURL |\n`http://localhost:8000/mcp` (or `http://mcp.example.com/sub-agent/mcp` behind a proxy) |\nOpenAPI path |\n`openapi.json` (default) |\n\nOpen WebUI appends tool paths from the spec (for example `/tools/researcher`\n\n) to that URL:\n\n```\ncurl -s -X POST http://localhost:8000/mcp/tools/researcher \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"prompt\":\"Summarize what tools you have access to.\"}'\n```\n\n**Docker health** — The image health check probes `http://127.0.0.1:8000/mcp`\n\n.\n\n**Functional check** — After connecting Cursor (below), ask the parent agent to call the `researcher`\n\ntool with a short prompt. A successful run returns `{ \"response\": \"...\" }`\n\n.\n\n- Open\n**Cursor Settings → MCP**(or edit your MCP config JSON). - Add the server:\n\n```\n{\n  \"mcpServers\": {\n    \"sub-agent-mcp\": {\n      \"url\": \"http://localhost:8000/mcp\"\n    }\n  }\n}\n```\n\n- Ensure the Sub-Agent MCP process is running and Cursor can reach\n`localhost:8000`\n\n. On WSL or Docker Desktop, confirm port forwarding if the server runs in a VM or container. - Reload MCP tools. You should see one tool per agent (for example,\n).`researcher`\n\n**Example delegation prompt for the parent agent:**\n\nCall the\n\n`researcher`\n\ntool with prompt: \"Summarize what tools you have access to.\"\n\n**Other MCP clients** — Any client that supports **Streamable HTTP** can connect to `http://<host>:8000/mcp`\n\n. Refer to your client’s MCP documentation for URL-based server configuration; this server does not use stdio transport.\n\n```\nflowchart LR\n    subgraph parent [Parent]\n        Cursor[Cursor agent]\n    end\n    subgraph subagentmcp [Sub_Agent_MCP]\n        ToolsMCP[agent tools e.g. researcher]\n        Builder[Agent builder]\n    end\n    subgraph subagent [Sub_agent runtime]\n        LC[LangChain create_agent]\n        LLM[OpenAI_compatible LLM]\n    end\n    subgraph downstream [Per_agent MCP servers]\n        FS[filesystem MCP]\n        SRCH[search MCP]\n    end\n\n    Cursor -->|Streamable HTTP /mcp| ToolsMCP\n    ToolsMCP --> Builder\n    Builder --> LC\n    LC --> LLM\n    LC --> FS\n    LC --> SRCH\n```\n\n**Agent tool flow**\n\n- Load and validate\n[config/agents.yaml](/stormaref/Sub-Agent-MCP/blob/main/config/agents.yaml)(or`AGENTS_CONFIG_PATH`\n\n) at startup. - Register one MCP tool per agent, named by\n`id`\n\n. - When a tool is called, build an OpenAI-compatible chat model from that agent’s\n`llm.*`\n\n. - Connect to the agent’s\n`mcp_servers`\n\n, discover tools, apply`tool_allowlist`\n\nif set. - Run the LangChain agent loop (bounded by\n`AGENT_RECURSION_LIMIT`\n\n). - Return the final assistant message as\n`{ \"response\": \"...\" }`\n\n, or`{ \"error\": \"...\" }`\n\non failure.\n\nAgents are loaded from `config/agents.yaml`\n\nat startup. Override the path with `AGENTS_CONFIG_PATH`\n\n.\n\n```\nagents:\n  - id: researcher\n    title: Research Agent\n    description: \"Agent specialized in research tasks\"\n    llm:\n      base_uri: https://api.openai.com/v1\n      api_key: ${OPENAI_API_KEY}\n      model_id: gpt-4.1-mini\n    system_prompt: |\n      You are a helpful research assistant.\n    mcp_servers:\n      - name: filesystem\n        transport: streamable_http\n        url: http://filesystem-mcp:8001/mcp\n        headers: {}\n      - name: search\n        transport: streamable_http\n        url: http://search-mcp:8002/mcp\n    tool_allowlist:\n      - filesystem.read_file\n      - search.web_search\n```\n\nCopy [config/agents.example.yaml](/stormaref/Sub-Agent-MCP/blob/main/config/agents.example.yaml) as a starting point.\n\n| Field | Description |\n|---|---|\n`id` |\nUnique slug; must start with a lowercase letter, then lowercase letters, digits, `-` , or `_` |\n`title` |\nHuman-readable name |\n`description` |\nAgent purpose (shown in the tool description exposed to the parent) |\n`llm.base_uri` |\nOpenAI-compatible API base URL |\n`llm.api_key` |\nAPI key; supports `${ENV_VAR}` substitution |\n`llm.model_id` |\nModel identifier for the provider |\n`llm.reasoning_effort` |\nOptional reasoning budget: `none` , `minimal` , `low` , `medium` , `high` , `xhigh` |\n`llm.reasoning_summary` |\nOptional reasoning summary style: `auto` , `concise` , `detailed` (use `detailed` for longer) |\n`llm.verbosity` |\nOptional response verbosity for reasoning models: `low` , `medium` , `high` |\n`llm.max_tokens` |\nOptional max output tokens (increase if answers are cut short) |\n`llm.temperature` |\nOptional sampling temperature (`0.0` –`2.0` ) |\n`system_prompt` |\nSystem message for the sub-agent |\n`mcp_servers` |\nList of remote MCP servers (`transport` must be `streamable_http` ) |\n`mcp_servers[].name` |\nShort name used in qualified tool names (`name.tool` ) |\n`mcp_servers[].url` |\nStreamable HTTP MCP endpoint (must end with `/mcp` for standard layouts) |\n`mcp_servers[].bearer_token` |\nOptional bearer token; sent as `Authorization: Bearer ...` (supports `${ENV_VAR}` ) |\n`mcp_servers[].headers` |\nOptional extra HTTP headers merged with bearer auth |\n`tool_allowlist` |\nOptional list of `server.tool` names; omit to allow all tools from connected servers |\n\nEnvironment variable substitution supports `${VAR}`\n\nand `${VAR:-default}`\n\n. If `VAR`\n\nis unset and no default is provided, startup fails with a clear error.\n\n**Second agent (different role, no extra MCP servers)**\n\n```\n- id: writer\n  title: Writing Agent\n  description: \"Drafts and edits text\"\n  llm:\n    base_uri: https://api.openai.com/v1\n    api_key: ${OPENAI_API_KEY}\n    model_id: gpt-4.1-mini\n  system_prompt: |\n    You are a concise technical writer.\n  mcp_servers: []\n```\n\n**Local model via Ollama (OpenAI-compatible)**\n\n```\nllm:\n  base_uri: http://localhost:11434/v1\n  api_key: ${OLLAMA_API_KEY:-ollama}\n  model_id: llama3.2\n```\n\n**Authenticated downstream MCP server**\n\n```\nmcp_servers:\n  - name: my_api\n    transport: streamable_http\n    url: https://mcp.example.com/mcp\n    bearer_token: ${MY_MCP_TOKEN}\n```\n\nYou can also set auth manually via `headers`\n\n:\n\n```\n    headers:\n      Authorization: \"Bearer ${MY_MCP_TOKEN}\"\n```\n\n**Allow all tools from connected servers** — Remove `tool_allowlist`\n\nor set it to `null`\n\nin YAML (omit the key).\n\n| Error | Typical cause |\n|---|---|\n| Config file not found | Missing `config/agents.yaml` ; copy from `agents.example.yaml` |\n| Environment variable not set | `${VAR}` without value or `${VAR:-}` default |\n| Pydantic validation failed | Invalid `id` , duplicate ids, empty `system_prompt` , wrong `transport` |\n| Duplicate agent ids | Two agents share the same `id` |\n\nEach agent in `config/agents.yaml`\n\nis exposed as an MCP tool named by its `id`\n\n.\n\n| Parameter | Type | Description |\n|---|---|---|\n`prompt` |\nstring | User message for the sub-agent |\n\n**Returns**\n\n| Shape | Meaning |\n|---|---|\n`{ \"response\": \"...\" }` |\nSuccess; final assistant text |\n`{ \"error\": \"...\" }` |\nFailure (LLM error, MCP connection error, and so on) |\n\nTool descriptions include `title`\n\n, `description`\n\n, and `model_id`\n\nbut never API keys.\n\nErrors are returned in the result object; they do not crash the MCP server process.\n\n| Variable | Default | Description |\n|---|---|---|\n`AGENTS_CONFIG_PATH` |\n`config/agents.yaml` |\nPath to agents YAML |\n`HOST` |\n`0.0.0.0` |\nServer bind host |\n`PORT` |\n`8000` |\nServer bind port |\n`LOG_LEVEL` |\n`INFO` |\nLog level (`DEBUG` , `INFO` , …) |\n`MCP_CLIENT_TIMEOUT` |\n`30` |\nTimeout in seconds when connecting to downstream MCP servers; increase for slow tools |\n`AGENT_RECURSION_LIMIT` |\n`25` |\nMaximum LangChain agent tool-loop steps per agent tool call; increase for multi-step tasks |\n\n```\nconfig/\n  agents.example.yaml    # Template; copy to agents.yaml\n  agents.yaml            # Runtime config (example committed in repo)\nsrc/sub_agent_mcp/\n  main.py                # FastMCP entry point\n  server/                # Per-agent MCP tools, OpenAPI route\n  agent/                 # LangChain builder and executor\n  config/                # YAML loader and Pydantic schema\n  mcp_client/            # Downstream MCP connections and tool registry\ndocker/mock_mcp/         # Dev mock filesystem and search MCP servers\ntests/                   # pytest suite\nscripts/bump-tag.sh      # Release tag helper (v0.major.minor)\nuv sync --dev\nuv run pytest -v\nuv run ruff check src tests\n```\n\nPull requests and pushes to `main`\n\nrun lint and tests in [ .github/workflows/ci.yml](/stormaref/Sub-Agent-MCP/blob/main/.github/workflows/ci.yml).\n\n**Registry:** `ghcr.io/stormaref/sub-agent-mcp`\n\n**When images publish:** Pushing a git tag `v0.*`\n\n(for example `v0.1.2`\n\n) runs the Docker job after tests pass. Pushes to `main`\n\nalone do not publish images.\n\n**Zero versioning:** Tags use `v0.major.minor`\n\n(for example `v0.1.0`\n\n, `v0.1.1`\n\n, `v0.2.0`\n\n).\n\nIn VS Code: **Terminal → Run Task → Release: bump zero-version tag and push**\n\nOr manually:\n\n```\n./scripts/bump-tag.sh minor   # v0.1.0 → v0.1.1\n./scripts/bump-tag.sh major   # v0.1.0 → v0.2.0\n```\n\nImage tags include the semver, `latest`\n\n, and major.minor aliases per the metadata action in CI.\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n`Configuration error` on startup |\nMissing or invalid `agents.yaml` |\n`AGENTS_CONFIG_PATH` |\n\n`Environment variable 'X' is not set`\n\n`${X}`\n\nwithout default in YAML`export X=...`\n\nor use `${X:-default}`\n\n[Local vs Docker MCP URLs](#local-vs-docker-mcp-urls)table`401`\n\n/ invalid credentials`llm.api_key`\n\nfor that agent`base_uri`\n\n`filesystem-mcp`\n\n/ `search-mcp`\n\n; review `tool_allowlist`\n\n`curl http://localhost:8000/mcp/openapi.json`\n\n; check firewall / WSL networking`AGENT_RECURSION_LIMIT`", "url": "https://wpnews.pro/news/show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp", "canonical_source": "https://github.com/stormaref/Sub-Agent-MCP", "published_at": "2026-06-06 11:10:02+00:00", "updated_at": "2026-06-06 11:17:38.702057+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "ai-tools", "ai-infrastructure", "ai-products"], "entities": ["Sub-Agent MCP", "Cursor", "LangChain", "MCP", "OpenAI"], "alternates": {"html": "https://wpnews.pro/news/show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp", "markdown": "https://wpnews.pro/news/show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp.md", "text": "https://wpnews.pro/news/show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp.txt", "jsonld": "https://wpnews.pro/news/show-hn-sub-agent-mcp-llm-delegation-and-sub-agent-orchestration-via-mcp.jsonld"}}