feat(agents): integrations on the Luke MCP server
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,integrationUnavailableand their types) moves out ofintegration-connection.ts. That file importseve/connections, so the MCP route couldn't reach the token read without bundling eve. eve's connection still reads tokens through the same function, andedge.ts,connections.tsand 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 nowinMcpSession.listMcpToolsis unchanged. Two functions are added:listMcpToolSchemasreturns each tool with its schemas as the server wrote them.callMcpToollists the tools and makestools/callin 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 ownrenderToolSignature. -
integrations/connection-door.ts.runConnectionSearchandrunConnectionExecute, with eve's names, descriptions and JSON schemas copied verbatim.- Connections: a coding agent gets what its turn would get (
turnServerswithAGENT_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 withrequiresSignIn: trueand 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
toConnectionToolResultdoes (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.
- Connections: a coding agent gets what its turn would get (
-
luke-mcp/tools.ts.- Both tools are registered. A tool result that contains images or audio comes back as MCP content blocks.
LukeMcpServicesgainsIntegrationConnect, which the web runtime already provides.look_at_pictureandlook_at_boardnow return[{"type":"text","text":"{\"status\":\"looked\"}"}, image], so the transcript stores what eve'sstoredPictureLookOutputstores.
Where this differs from eve (small calls I made)
- Mentions: an
@Linearmention 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: truestarts 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 outerconnection_execute(P09).
Checks
- Bundle: an esbuild metafile of
apps/web/server/routes/agents/mcp.ts, built with theluke-mcp.jsexternals, has noeveinput. The same detector does find eve when pointed atintegration-connection.ts.function-externals.jsonwas regenerated and is unchanged. connection-door.test.ts, against Linear's MCP server run as a fake (the MCP SDK's ownServerover 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
requiresSignInon 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-effectwas 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