# Composio MCP over streamable HTTP (verified working 2026-07-28)

Use when the CLI auth path fails or the user only has a `ck_...` key from
https://connect.composio.dev/api/onboarding/setup. Those keys are MCP keys;
they 401 against `composio login --user-api-key` but work here.

Key is a secret; the user's working key was provided in-session.

## 1. Initialize — capture the session id from the response HEADER

```bash
curl -s -D /tmp/mcp_headers.txt -X POST "https://connect.composio.dev/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ck_..." \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"hermes","version":"1.0"}}}'
grep -i "mcp-session-id" /tmp/mcp_headers.txt   # -> mcp-session-id: <SID>
```

The `Accept: application/json, text/event-stream` header is REQUIRED — without
it the server returns `Not Acceptable: Client must accept both
application/json and text/event-stream`. Responses come back as SSE
(`event: message` / `data: {...}` lines); parse the `data: ` lines.

## 2. Send the initialized notification, then list tools

```bash
SID=<session id from step 1>
curl -s -X POST "https://connect.composio.dev/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ck_..." -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

curl -s -X POST "https://connect.composio.dev/mcp" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ck_..." -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
```

Only meta-tools are listed: COMPOSIO_SEARCH_TOOLS, COMPOSIO_GET_TOOL_SCHEMAS,
COMPOSIO_MANAGE_CONNECTIONS, COMPOSIO_MULTI_EXECUTE_TOOL,
COMPOSIO_WAIT_FOR_CONNECTIONS, COMPOSIO_REMOTE_WORKBENCH,
COMPOSIO_REMOTE_BASH_TOOL. Calling an app tool slug directly (e.g.
GMAIL_GET_PROFILE) returns `MCP error -32602: Tool ... not found`.

## 3. Standard workflow (all via tools/call on the meta-tools)

1. `COMPOSIO_SEARCH_TOOLS` — `{"queries":[{"use_case":"..."}]}` → returns
   `primary_tool_slugs`, plan steps, pitfalls. Response text is a JSON string
   inside `result.content[0].text` (double-unwrapped).
2. `COMPOSIO_MANAGE_CONNECTIONS` —
   `{"toolkits":[{"name":"gmail","action":"list"}]}` to check for an ACTIVE
   connection. `action:"add"` returns a `redirect_url` (10-min expiry) for the
   user to authorize; then poll with `COMPOSIO_WAIT_FOR_CONNECTIONS`.
3. `COMPOSIO_GET_TOOL_SCHEMAS` — `{"tool_slugs":["GMAIL_FETCH_EMAILS"]}` for
   exact argument shapes. Never invent slugs.
4. `COMPOSIO_MULTI_EXECUTE_TOOL` —
   `{"tools":[{"tool_slug":"GMAIL_FETCH_EMAILS","arguments":{...}}]}` to
   execute. Independent calls can be batched in one `tools` array.

## Gotchas observed

- A connection showing `status:"active"` can still 403 with
  `ACCESS_TOKEN_SCOPE_INSUFFICIENT` if authorized with narrow scopes — re-add
  the connection rather than retrying the tool.
- `user_id:"me"` works for Gmail arguments.
- Responses can be large; pipe through python to unwrap SSE `data:` lines and
  the nested JSON-in-text before reading.

## Session log highlights (2026-07-28)

- Existing connection `gmail_casino-dubby` was active but scope-insufficient
  (403 on GMAIL_GET_PROFILE and GMAIL_FETCH_EMAILS). Re-auth link was issued
  via action:"add"; user completion was pending at session end.
