feat(agents): integrations on the Luke MCP server

Merged
CH
5d ago
+2006-421

P07b of the "Bringing Codex or Claude Code Max plans into Luke" rollout. It serves connection_search and connection_execute on Luke's MCP server through an eve-free door. The door reuses the existing per-member grants, so it adds no new token store and no new OAuth flow. It also makes a look's text block on the MCP server {"status":"looked"}, which follows up #4288.

What changed

  • integrations/integration-token.ts (moved, no behaviour change). The token read (integrationAccessToken, integrationUnavailable and their types) moves out of integration-connection.ts. That file imports eve/connections, so the MCP route couldn't reach the token read without bundling eve. eve's connection still reads tokens through the same function, and edge.ts, connections.ts and the existing suite now import from the new module.

  • integrations/mcp-tools.ts. The MCP client's session (initialize, then send, then a courtesy end) is now inMcpSession. listMcpTools is unchanged. Two functions are added:

    • listMcpToolSchemas returns each tool with its schemas as the server wrote them.
    • callMcpTool lists the tools and makes tools/call in one session.

    The existing safeguards still apply to both: the bearer is sent only to the provider, and a server error message is kept only when nothing in it looks like a credential.

  • integrations/connection-search.ts. eve 0.74's ranking (rankConnectionTools, closestToolNames) and its TypeScript signature renderer, rewritten to give the same output. I checked the output against eve's own renderToolSignature.

  • integrations/connection-door.ts. runConnectionSearch and runConnectionExecute, with eve's names, descriptions and JSON schemas copied verbatim.

    • Connections: a coding agent gets what its turn would get (turnServers with AGENT_SWITCHES): every integration offered to the caller, minus those switched off for the agent.
    • Grants: each call reads the grant afresh for the acting member through integrationAccessToken, so the workspace Linear grant and multiple accounts behave as they do on eve.
    • Search: ranks and pages the tools, renders signatures, and lists what it couldn't load under unavailable. A connection the member hasn't connected, or whose grant expired, is listed with requiresSignIn: true and the existing sentence that says how to connect it. If every searched connection fails for a reason that reconnecting wouldn't fix, the search is refused, as eve does.
    • Execute: calls the tool on the member's own grant and reads the result as eve's toConnectionToolResult does (an error's text, its content blocks, structuredContent, or text parsed as JSON). It refuses an unknown connection, an unsigned one (requiresSignIn), an unknown tool (suggesting the closest names), and the tool's own error.
  • luke-mcp/tools.ts.

    • Both tools are registered. A tool result that contains images or audio comes back as MCP content blocks.
    • LukeMcpServices gains IntegrationConnect, which the web runtime already provides.
    • look_at_picture and look_at_board now return [{"type":"text","text":"{\"status\":\"looked\"}"}, image], so the transcript stores what eve's storedPictureLookOutput stores.

Where this differs from eve (small calls I made)

  • Mentions: an @Linear mention hands a switched-off server to the next turn only. The MCP server has no turns, so it never takes mentions: it applies the agent's switches and leaves mentions for whatever starts a native turn (P11) to take.
  • signIn: true starts no sign-in, because integrations are connected in Luke's Settings. An unsigned connection is refused with the "requires sign-in" shape and Luke's sentence.
  • Input validation: the door doesn't validate a call's input against the tool's schema before calling. The provider's server validates it, and its refusal becomes the call's error.
  • Nested call rows: eve's nested <connection>__<tool> row isn't reported. The transcript stores the outer connection_execute (P09).

Checks

  • Bundle: an esbuild metafile of apps/web/server/routes/agents/mcp.ts, built with the luke-mcp.js externals, has no eve input. The same detector does find eve when pointed at integration-connection.ts. function-externals.json was regenerated and is unchanged.
  • connection-door.test.ts, against Linear's MCP server run as a fake (the MCP SDK's own Server over its web-standard transport, accepting only the member's Connect-issued bearer), with PGlite:
    • search, ranking and paging;
    • execute with JSON, structured and image results;
    • an unsigned Notion account refused with requiresSignIn on both tools, before any bearer is sent;
    • a switched-off connection, an unknown tool, and the tool's own error, each refused;
    • a provider that refuses and echoes the bearer: neither result nor any logged line contains the token.
  • luke-mcp/app.test.ts:
    • both tools are listed;
    • each answers exactly as its door does;
    • an end-to-end search and an execute over the SDK client, where an image comes back as image content and an unsigned connection comes back as an error;
    • the two look tests now expect {"status":"looked"} plus the image.
  • pnpm check:affected: typecheck, lint, and every affected test suite pass. discarded-effect was OOM-killed (exit 137) in this sandbox, so CI is the check there.

The developer waived per-PR Preview runs for this rollout: this merges on green, and the live check comes before the cohort is turned on. Nothing routes agents to the MCP server until P11.

Made with Luke


Devin Review