Skip to content

feat(composio): key-gated Composio toolkit access over MCP - #327

Open
plombeer31 wants to merge 1 commit into
mainfrom
feat/composio-mcp-access
Open

feat(composio): key-gated Composio toolkit access over MCP#327
plombeer31 wants to merge 1 commit into
mainfrom
feat/composio-mcp-access

Conversation

@plombeer31

Copy link
Copy Markdown
Collaborator

What

Wires Composio — a hosted catalogue of ~1500 SaaS toolkits (Gmail, Slack, Notion, Linear, Jira, …) that also brokers each app's OAuth — into the agent as one more MCP server.

A Composio tool-router session yields a Streamable-HTTP MCP endpoint authenticated by a static x-api-key header, which is exactly the transport src/mcp/ already speaks. The cold-path wiring is a single await resolveComposioServerConfig(...) that appends at most one entry to the server list before McpManager is constructed.

Nothing is user-visible yet. The key-entry UI is the Integrations hub in a follow-up PR. To try this one, put COMPOSIO_API_KEY=… in <stateDir>/.env.

Why no SDK

@composio/core would drag a transitive dependency tree into a project that ships a single-file SEA binary, and would duplicate the lifecycle, retry, approval and status machinery src/mcp/ already owns. The whole integration needs exactly one HTTP call.

Composio's session model is also what keeps this cheap in prompt terms: rather than loading ~1500 toolkits' worth of schemas, the session exposes four meta-tools and the agent discovers real tools at runtime. Stable-prefix cost is four tools, flat, regardless of catalogue size.

Meta-tool readOnlyHint Gated?
COMPOSIO_SEARCH_TOOLS true no — discovery flows silently
COMPOSIO_GET_TOOL_SCHEMAS true no
COMPOSIO_MANAGE_CONNECTIONS false, destructive yes
COMPOSIO_MULTI_EXECUTE_TOOL false, destructive yes

Design notes

  • The key is the only gate. No key (or composio.enabled: false) ⇒ no server appended ⇒ no Composio tool registered ⇒ the model cannot see or call one. No second switch, no partial state.
  • The key never enters config.json. It lives in <stateDir>/.env under the name in composio.apiKeyEnv, matching TELEGRAM_BOT_TOKEN and the web.search.*.apiKeyEnv precedent. Config carries only the switch, the env-var name, and cached session ids.
  • Failure is soft. Composio unreachable / rate-limiting / rejecting a stale key logs a warning and boots without it. A third-party SaaS broker must never stand between the operator and their own shell, files and browser.
  • userId is a minted UUID, persisted, never an email. Composio scopes connected accounts to it, so regenerating it would silently orphan every app the operator had already authorised — and an email would hand PII to a third party for no benefit.
  • The remote workbench stays off. workbench: { enable: false } drops COMPOSIO_REMOTE_WORKBENCH / COMPOSIO_REMOTE_BASH_TOOL, which duplicate os.shell.run and would route the operator's shell work through a third-party sandbox.
  • Trust stays approval_gated. Loosening it to pure_read would un-gate the writes too. Discovery is unprompted anyway, via the existing readOnlyHint exemption in mcp-tool-adapter.ts — so the seamlessness costs nothing in consent.

Config

v50 adds an additive composio block (enabled, apiKeyEnv, userId, sessionId, mcpUrl). Older files inherit defaults that mount nothing. v49 was appended to SUPPORTED_INPUT_VERSIONS.

Testing

npm run lint and npm test green (7157 tests). 31 new unit tests across five files.

Verified against a live Composio key on a scratch state dir:

  • server connects up with toolCount: 4; registry shows mcp.composio.COMPOSIO_{SEARCH_TOOLS,GET_TOOL_SCHEMAS,MANAGE_CONNECTIONS,MULTI_EXECUTE_TOOL}
  • session id + minted UUID cached to config.json, reused unchanged on the next boot (no second API call)
  • the key does not appear anywhere in config.json
  • with no key: MCP servers: [], mcp.* tools: []

Annotations were read off the live endpoint, not assumed — that is where the gating table above comes from.

Composio is a hosted catalogue of ~1500 SaaS toolkits (Gmail, Slack,
Notion, Linear, Jira, ...) that also brokers each app's OAuth. This
wires it in as one more MCP server rather than as a bespoke
integration: a tool-router session yields a Streamable-HTTP MCP
endpoint authenticated by a static `x-api-key` header, which is
exactly the transport src/mcp/ already speaks.

No SDK. `@composio/core` would drag a transitive dependency tree into
a project that ships a single-file SEA binary, and would duplicate
lifecycle, retry, approval and status machinery the MCP client already
owns. The whole integration needs one HTTP call.

The session model keeps the prompt cost flat: instead of ~1500
toolkits' worth of schemas, four meta-tools land in the registry
(SEARCH_TOOLS, GET_TOOL_SCHEMAS, MANAGE_CONNECTIONS,
MULTI_EXECUTE_TOOL) and the agent discovers real tools at runtime.

Nothing is user-visible yet -- the key-entry UI is the Integrations
hub in a follow-up. Set COMPOSIO_API_KEY in <stateDir>/.env to try it.

Design notes:

- The key is the only gate. No key (or `composio.enabled: false`) and
  no server is appended, so no Composio tool is ever registered and
  the model cannot reach one.
- The key never enters config.json -- it lives in <stateDir>/.env,
  matching TELEGRAM_BOT_TOKEN and the web.search.*.apiKeyEnv
  precedent. Config carries only the switch, the env-var name, and
  cached session ids.
- Failure is soft. Composio being down must not stop the agent from
  booting; it logs a warning and continues with no server.
- `userId` is a minted UUID, persisted, never an email: Composio
  scopes connected accounts to it, so regenerating it would orphan
  every app the operator had already authorised.
- The remote workbench is disabled explicitly -- it duplicates
  os.shell.run and would route the operator's shell work through a
  third-party sandbox.
- Trust stays approval_gated. Discovery is still unprompted because
  mcp-tool-adapter exempts tools annotated readOnlyHint, which is how
  Composio tags its two discovery tools, while the two mutating tools
  are tagged destructive and keep hitting the approval gate.

Config v50: additive `composio` block; older files inherit defaults
that mount nothing.

Verified against a live Composio key: server connects `up` with 4
tools registered as mcp.composio.*, the session is cached and reused
on the next boot, the key does not appear in config.json, and with no
key the runtime reports zero MCP servers and zero mcp.* tools.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant