Skip to content

feat(mcp): serve the Lore tools over Streamable HTTP - #165

Merged
spinsirr merged 6 commits into
mainfrom
feat/mcp-streamable-http
Oct 26, 2026
Merged

spinsirr merged 6 commits into
mainfrom
feat/mcp-streamable-http

Conversation

@corespeed-agent-ensemble

@corespeed-agent-ensemble corespeed-agent-ensemble Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What changes

The Lore server now serves the MCP tools over Streamable HTTP at POST /api/v1/mcp. The stdio lore-mcp adapter still works.

  • An Agent connects with an Agent token in Authorization: Bearer lore_agent_… and the Workspace in x-lore-workspace-id. These are the same headers as the API.
  • The route resolves the Actor first. A bad token, a foreign Workspace, or a revoked grant gets the API's own 403 before MCP starts.
  • The tools call the API in process through the SDK (createLoreMcpHttpHandler with the app's own fetch). Each tool call is admitted, authorized, and scoped by RLS like any other Agent request. The tool code does not change.
  • The endpoint is stateless and answers every exchange in JSON, never SSE, so a response returns only after each tool call ends. Workers close the request's database when the handler returns. The SDK's own 2025-era fallback streams SSE, so the handler routes those requests to a JSON-mode transport. GET and DELETE answer 405.
claude mcp add --transport http lore https://<lore>/api/v1/mcp \
  --header "Authorization: Bearer lore_agent_…" \
  --header "x-lore-workspace-id: <workspace-uuid>"

Decisions for the PM

  • Agent only. The endpoint refuses a human credential (401), because the tools act only as an Agent and record its provenance.
  • Workspace in a header, not the URL. This matches the API and the SDK. A client that can set only a URL and a bearer token cannot connect yet.
  • No OAuth. MCP hosts that support only OAuth (for example, remote connectors in some chat apps) cannot connect. That needs a separate change.
  • Not in OpenAPI. The endpoint speaks JSON-RPC, not the REST contract.
  • Layer rule changed. check-import-boundaries.ts now lets the server-composition layer import the MCP package. MCP still reaches Lore only through the SDK.

Other changes

  • packages/mcp imports its version module with .ts and sets rewriteRelativeImportExtensions, as the SDK package does. Turbopack could not resolve ./generated/version.js once the server bundled the package.
  • packages/mcp/tsconfig.json maps @corespeed/lore-sdk to the SDK source. Bun and Wrangler bundle the MCP source from the server, and the CI jobs do not build the SDK dist. The published package builds with tsconfig.build.json against the built SDK.
  • fix(deps): bun audit now fails on main too, with three new high advisories. This PR raises undici to 7.30.1 (override), zod to 4.6.7, and body-parser to 2.4.0 (override).
  • Docs: docs/agent-conventions.md, docs/architecture.md, docs/developer-integration.md, packages/mcp/README.md.

Verification

  • tests/integration/mcp-http.test.ts connects a real MCP client over Streamable HTTP through app.request. It covers writes recorded as the Agent, RLS on another User's private Memory, no credential, an unknown token, a foreign Workspace, a revoked grant, and a human credential.
  • Local: typecheck, architecture:check, next build, smoke:next, the OpenNext Cloudflare build, wrangler deploy --dry-run, and packages:smoke pass. The full suite passes except tests/modules/code/code-index.test.ts, which also fails on main in this sandbox because of a global Git ignore file. It passes with XDG_CONFIG_HOME=/nonexistent.

🤖 Generated with Claude Code

Agent and others added 6 commits October 24, 2026 02:36
An Agent connects with the same Authorization and x-lore-workspace-id
headers as the API. The tools call the API in process through the SDK,
so every tool call keeps its normal admission, authorization, and RLS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ackage

Document the Streamable HTTP endpoint beside the stdio adapter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… package

Bun and Wrangler resolved @corespeed/lore-sdk through the workspace link
to its built dist, which the database and build CI jobs do not build.
packages/mcp/tsconfig.json now maps the SDK to its source; the package
build uses tsconfig.build.json against the built SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bun audit now flags undici < 7.30.1 (GHSA-xjj6-f33q-929c, via Wrangler and
openai), zod < 4.6.6 (GHSA-mrhx-c7xc-m3w5, a direct dependency), and
body-parser < 2.4.0 (GHSA-27r3-jxmh-p828, via @opennextjs/cloudflare).
main fails the audit too. typecheck, next build, smoke:next, the OpenNext
build, wrangler deploy --dry-run, packages:smoke, and the integration and
package tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK's stateless legacy fallback streams SSE, so the response returned
before the tool call finished. On Workers the request then closed its
database and the tool failed with internal_error. 2025-era requests now
go to a stateless transport in JSON mode, which resolves only with every
result. The tests close the request's database when the handler returns,
as fetchCloudflareApi does; both MCP tests fail against the old handler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handler is stateless, so it has no session stream to open or end.
The Lore route already answered 405; another host using the handler got
an SSE stream for GET.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@corespeed-hopper corespeed-hopper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after four review rounds. No findings remain.

The fixes resolve SDK imports from a clean checkout and keep legacy tool execution within the request lifetime. The standalone HTTP handler also rejects unsupported methods.

Verification:

  • Reviewed the changes from merge base e8e1be9 through this head, including each fix.
  • Targeted local checks pass for SDK resolution, legacy request completion, HTTP integration, and unsupported methods.
  • All six required CI checks pass on 99c20bb: static, tests, database, packages, build, and the aggregate gate.
  • CI evidence: https://github.com/corespeed-io/lore/actions/runs/40155356909

The PR description records the product decisions and transport limits.

Reviewer session: e96a2399-e3cb-4a42-a095-eae005bb0c6d.

@spinsirr
spinsirr merged commit cd1d379 into main Oct 26, 2026
7 checks passed
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