Skip to content

Commit a090206

Browse files
committed
feat(managed_agents): CMA Sessions API as an MCP server (stdio + HTTP)
Thin MCP server wrapping Managed Agents sessions so Claude Desktop or claude.ai can drive hosted CMA agents via tool calls. DIY bridge until 'publish agent to claude.ai' is first-class. 9 tools in shared src/tools.ts — 8 are 1:1 endpoint wrappers; wait_for_idle is the SSE→request/response shim. Two entrypoints: server.ts (stdio, Desktop) and server-http.ts (Streamable HTTP + bearer auth, claude.ai Connector). Dockerfile for Fly/Railway/Render. list_agents capped + name_contains filter (busy workspaces hang auto-pagination). Authoring/destructive/secret endpoints deliberately excluded. README forks on client; CLAUDE.md invokes /claude-api; skill.md has Desktop + Connector setup, relay-mode Project instructions, org-admin gating note. stdio path proven e2e in Claude Desktop; HTTP smoke-tested via ngrok.
1 parent 33424c3 commit a090206

12 files changed

Lines changed: 562 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Anthropic
2+
ANTHROPIC_API_KEY=sk-ant-...
3+
4+
# One environment shared by all sessions this server creates.
5+
# Create via: ant beta:environments create --name cma-mcp --config '{type: cloud, networking: {type: unrestricted}}'
6+
CLAUDE_ENVIRONMENT_ID=env_...
7+
8+
# HTTP transport only — bearer token the client must send.
9+
# Generate: openssl rand -hex 32
10+
CMA_MCP_TOKEN=

managed_agents/cma-mcp/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
.env
3+
.env.local
4+
bun.lock

managed_agents/cma-mcp/CLAUDE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# CMA as an MCP server
2+
3+
MCP server that exposes Managed Agents session primitives as tools. Two entrypoints sharing one tool set: `src/server.ts` (stdio → Claude Desktop) and `src/server-http.ts` (Streamable HTTP → claude.ai web Connector).
4+
5+
## When the user asks to set this up, extend it, or debug it
6+
7+
1. **Invoke `/claude-api` first.** That skill loads the full Managed Agents API reference (agents, sessions, events, environments). Use it as the source of truth for any SDK call — don't guess field names.
8+
2. **Read `./skill.md`** and walk the user through it. First ask which client they're targeting (Claude Desktop → stdio path; claude.ai web → HTTP + deploy + Connector), then follow that path's checklist. Both end with the same `send_message → wait_for_idle` loop and relay-mode Project instructions.
9+
3. **Adding a tool?** Map the CMA endpoint 1:1 in `src/cma.ts`, then register it in `src/server.ts` with a zod schema. See the tier table in skill.md for what's deliberately **not** exposed (destructive ops, secrets).
10+
11+
Commands: `bun run stdio` (Desktop), `bun run http` (claude.ai), `bun run typecheck`.

managed_agents/cma-mcp/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM oven/bun:1-slim
2+
WORKDIR /app
3+
COPY package.json ./
4+
RUN bun install --production
5+
COPY src ./src
6+
ENV PORT=3000
7+
EXPOSE 3000
8+
CMD ["bun", "run", "src/server-http.ts"]

managed_agents/cma-mcp/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# CMA as an MCP server
2+
3+
A thin [MCP](https://modelcontextprotocol.io) server that wraps the Claude [Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) Sessions API — so Claude Desktop **or** claude.ai web can start and chat with your org's hosted agents as if they were tools.
4+
5+
```
6+
User ─▶ Claude (Desktop or claude.ai) ─▶ MCP: send_message + wait_for_idle ─▶ CMA session
7+
▲ │
8+
└──────────────────────── agent's reply ◀─── stream-to-idle ◀──────────────────┘
9+
```
10+
11+
Nine tools — eight are 1:1 with CMA endpoints, one (`wait_for_idle`) is the SSE→request/response shim. Same handlers, two transports.
12+
13+
## Quickstart
14+
15+
```bash
16+
cd managed_agents/cma-mcp
17+
bun install
18+
claude
19+
```
20+
21+
Then ask: **"walk me through setting this up."** Claude reads [`skill.md`](./skill.md) and drives whichever path you pick:
22+
23+
| Client | Transport | Entrypoint |
24+
|---|---|---|
25+
| **Claude Desktop / Claude Code** | stdio (local process) | `src/server.ts` |
26+
| **claude.ai web** (custom Connector) | Streamable HTTP (deployed URL + bearer token) | `src/server-http.ts` |
27+
28+
## Tools
29+
30+
| Tool | CMA endpoint |
31+
|---|---|
32+
| `list_agents` / `get_agent` | `GET /v1/agents[/{id}]` |
33+
| `create_session` | `POST /v1/sessions` |
34+
| `send_message` / `interrupt` | `POST /v1/sessions/{id}/events` |
35+
| `get_session` | `GET /v1/sessions/{id}` |
36+
| `list_events` | `GET /v1/sessions/{id}/events` |
37+
| `archive_session` | `POST /v1/sessions/{id}/archive` |
38+
| **`wait_for_idle`** | streams `…/events/stream` until idle, returns reply text |
39+
40+
## Files
41+
42+
| | |
43+
|---|---|
44+
| `src/cma.ts` | Anthropic SDK calls — shared |
45+
| `src/tools.ts` | Nine `server.tool(...)` registrations — shared |
46+
| `src/server.ts` | stdio entrypoint (~10 LOC) |
47+
| `src/server-http.ts` | HTTP entrypoint + bearer auth (~40 LOC) |
48+
| `Dockerfile` | Fly / Railway / Render deploy for the HTTP path |
49+
50+
Requires `@anthropic-ai/sdk` ≥ 0.95.1.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "cma-mcp",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"bin": {
7+
"cma-mcp": "./src/server.ts"
8+
},
9+
"scripts": {
10+
"stdio": "bun run src/server.ts",
11+
"http": "bun run src/server-http.ts",
12+
"typecheck": "tsc --noEmit"
13+
},
14+
"dependencies": {
15+
"@anthropic-ai/sdk": "^0.95.1",
16+
"@modelcontextprotocol/sdk": "^1.22.0",
17+
"zod": "^3.25.0"
18+
},
19+
"devDependencies": {
20+
"@types/bun": "latest",
21+
"typescript": "^5"
22+
}
23+
}

managed_agents/cma-mcp/skill.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Setup tips & tricks — CMA as an MCP server
2+
3+
Things that aren't obvious from the docs and tend to cost debugging time.
4+
5+
---
6+
7+
## Mental model
8+
9+
### Claude Desktop is the frontend, CMA is the backend
10+
11+
The Claude you're typing to in Desktop is a **relay**. It calls `send_message` with your words, calls `wait_for_idle` to block until the CMA agent finishes, then shows you the reply. The actual work — tool use, code execution, repo edits — happens in the CMA session, not in Desktop.
12+
13+
### Eight 1:1 tools, one shim
14+
15+
`list_agents`, `get_agent`, `create_session`, `send_message`, `interrupt`, `get_session`, `list_events`, `archive_session` are straight endpoint wrappers. `wait_for_idle` is the only editorial: MCP is request/response and CMA completion is SSE, so something has to stream-to-idle inside a tool call.
16+
17+
### `session_id` is the only state, and Claude holds it
18+
19+
`create_session` returns it; Claude passes it to every subsequent call. The MCP server itself is stateless.
20+
21+
### Two transports, one tool set
22+
23+
`src/tools.ts` registers the nine tools. `src/server.ts` wraps it in stdio (Claude Desktop spawns it as a subprocess); `src/server-http.ts` wraps it in Streamable HTTP (claude.ai web reaches it over the network). Pick one.
24+
25+
---
26+
27+
## Setup — Claude Desktop (stdio, local)
28+
29+
1. **Environment** (one-time — sessions need an `environment_id`):
30+
```bash
31+
ant beta:environments create --name cma-mcp \
32+
--config '{type: cloud, networking: {type: unrestricted}}' --transform id -r
33+
```
34+
2. **Agents**: this server doesn't create agents — it drives the ones already in your workspace. Create/update them via the `ant` CLI or the Console.
35+
3. **`.env.local`**:
36+
```
37+
ANTHROPIC_API_KEY=sk-ant-...
38+
CLAUDE_ENVIRONMENT_ID=env_...
39+
```
40+
4. **Register with Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) and restart Desktop:
41+
```json
42+
{
43+
"mcpServers": {
44+
"cma": {
45+
"command": "bun",
46+
"args": ["run", "/absolute/path/to/managed_agents/cma-mcp/src/server.ts"],
47+
"env": {
48+
"ANTHROPIC_API_KEY": "sk-ant-...",
49+
"CLAUDE_ENVIRONMENT_ID": "env_..."
50+
}
51+
}
52+
}
53+
}
54+
```
55+
5. **Test**: in a new Desktop chat, ask *"list my managed agents, start a session with the first one, and relay this message to it: hello."*
56+
57+
---
58+
59+
## Setup — claude.ai web (Streamable HTTP, remote)
60+
61+
Same tools, but the server runs at a public URL and claude.ai connects to it as a custom Connector.
62+
63+
1. **Token** — generate and keep it; anyone with this token can drive your agents:
64+
```bash
65+
export CMA_MCP_TOKEN=$(openssl rand -hex 32)
66+
```
67+
2. **Run locally first** (ngrok/cloudflared for a public URL while testing):
68+
```bash
69+
bun run http # → :3000/mcp
70+
```
71+
3. **Deploy** — the `Dockerfile` targets Fly / Railway / Render; set `ANTHROPIC_API_KEY`, `CLAUDE_ENVIRONMENT_ID`, `CMA_MCP_TOKEN` as secrets. (Cloudflare Workers also works — `WebStandardStreamableHTTPServerTransport` is fetch-native; swap `process.env``env` and `Bun.serve``export default { fetch }`.)
72+
4. **claude.ai → Settings → Connectors → Add custom connector**:
73+
74+
| Field | Value |
75+
|---|---|
76+
| Name | `CMA` |
77+
| URL | `https://<your-deploy>/mcp` |
78+
| Authentication | Bearer token → your `CMA_MCP_TOKEN` |
79+
80+
> **Team / Enterprise orgs:** adding a connector by URL is typically **org-admin only** — individual users see a curated directory, not a URL field. An admin adds the URL + token once in org settings; it then appears in every member's Connectors list to enable. (This is the shape you want for rolling out to non-technical users anyway.)
81+
82+
5. **Test**: new chat → enable the `CMA` connector → same prompt as above.
83+
84+
---
85+
86+
## Relay mode (recommended Project instructions)
87+
88+
Without guidance, Desktop Claude will try to answer the user itself instead of relaying. Put this in a Project's custom instructions:
89+
90+
> You are a frontend for a backend Managed Agent reached via the `cma` MCP tools. On the first user turn: `list_agents` (if needed) → `create_session``send_message(user text verbatim)``wait_for_idle` → return the `reply` verbatim. On subsequent turns: `send_message``wait_for_idle`. Do not answer from your own knowledge; do not paraphrase the backend's reply. If `wait_for_idle` returns `status: "timeout"`, tell the user it's still running and offer to keep waiting.
91+
92+
---
93+
94+
## Gotchas
95+
96+
### stdio = Desktop only; HTTP = the internet
97+
98+
Browser claude.ai can't spawn a local process, so stdio only works with Claude Desktop / Claude Code. The HTTP path gets you claude.ai web, but now the URL is public — the **bearer token is the only gate** in front of your `ANTHROPIC_API_KEY`'s CMA quota. Don't deploy without it, don't log it, rotate it like any API key.
99+
100+
### Long turns vs tool timeout
101+
102+
`wait_for_idle` blocks. If the CMA agent runs for minutes (big repo clone, many tool calls), the MCP client may time out the tool call. Mitigations: pass a smaller `timeout_sec` and loop (`wait_for_idle` returns `status: "timeout"` + `last_event_id`; call again), or have Claude poll `get_session` + `list_events(after_id=...)` instead.
103+
104+
### Billing
105+
106+
All CMA usage bills to the `ANTHROPIC_API_KEY` baked into the server config — not to the Desktop user's account. That's the point (non-technical users, no keys), but size the key's workspace limits accordingly.
107+
108+
### Deliberately not exposed
109+
110+
| Endpoint | Why not |
111+
|---|---|
112+
| `agents.archive` | Permanent, no undo — one bad tool call bricks a prod agent |
113+
| `agents.create` / `update` | Authoring belongs in the `ant` CLI / Console, not a chat turn |
114+
| `sessions.delete`, `environments.*` | Destructive infra ops |
115+
| `vaults.*`, `credentials.*` | Secrets |
116+
| `sessions.resources.add` | Needs tokens/file IDs the chat user won't have |
117+
118+
Adding any of these is a few lines in `src/cma.ts` + `src/server.ts` if your use case needs them.
119+
120+
---
121+
122+
## Debugging
123+
124+
| Symptom | Check |
125+
|---|---|
126+
| Desktop shows no `cma` tools | Config path wrong, or `bun` not on PATH for the Desktop process (use an absolute path to `bun`). Check Desktop's MCP logs. |
127+
| `CLAUDE_ENVIRONMENT_ID is required` | Env block missing from the Desktop config — stdio servers don't read your shell's env. |
128+
| `wait_for_idle` returns empty `reply` | Agent went idle without emitting text (e.g. only tool calls). `list_events` shows the full log. |
129+
| Every turn starts a new session | Claude isn't threading `session_id`. Tighten the Project instructions. |
130+
| claude.ai Connector shows "couldn't connect" | URL wrong, server not reachable, or token mismatch (check server logs for 401). |
131+
| HTTP server returns 401 locally | `CMA_MCP_TOKEN` not exported in the shell you ran `bun run http` from. |

0 commit comments

Comments
 (0)