Expose the publish API as a remote MCP endpoint at /mcp#30
Open
koomen wants to merge 1 commit into
Open
Conversation
Claude Code (or any MCP client) can now publish and manage Scratchwork
projects with no CLI installed: `claude mcp add --transport http
scratchwork <app-origin>/mcp`, authenticate via /mcp (the OAuth surface
from the previous commit), and use tools backed by the same contract
endpoints the CLI calls.
- api-routes.ts: extract invokeEndpoint — the transport-independent
endpoint pipeline (bearer requirement, project read gate with its
existence mask, strict payload decode, registered handler). runRoute
delegates to it, byte-for-byte behavior-preserving, so MCP tools
inherit route policy instead of reimplementing it (invariant 4).
- mcp/jsonrpc.ts + mcp/transport.ts: a deliberately stateless MCP
streamable-HTTP transport — single JSON responses, no Mcp-Session-Id,
every POST self-contained — because Lambda/Workers instances share no
memory; hand-rolled over @effect/ai's session-oriented McpServer
(rationale in the module header). Bearer-only via requireMcpUser after
the standard cross-origin gate; the 401 carries the WWW-Authenticate
resource-metadata pointer that starts a client's OAuth flow.
- mcp/tools.ts: seven tools at CLI-operation altitude (publish,
list_projects, project_info, share_project, unpublish_project,
delete_project, whoami), each wrapping one contract endpoint; publish
takes {path, content|contentBase64} files and feeds the existing
shared bundle decode and publish caps. tools/list schemas are derived
from the same Effect Schemas that validate calls.
- Tests: framing/negotiation/statelessness suite plus a policy-parity
matrix deriving every tool's expected outcome from API_POLICY; e2e
gains a full spec-client loop (401 challenge → discovery → DCR →
authorize/consent → token → refresh → stateless tool publishes with
served-byte verification) on all three lanes — local Bun, miniflare,
LocalStack — since serverless statelessness is the claim under test.
- README: "Connect from Claude Code" section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of two PRs (stacked on #29). Adds the
/mcpendpoint itself: after this lands, connecting from Claude Code isthen
/mcpto authenticate in the browser — no CLI install. See the new README section.Design
application/jsonmessage (spec-permitted alternative to SSE), noMcp-Session-Idis ever issued, and every POST is self-contained — required by the Lambda/Workers targets where consecutive calls land on different instances. Hand-rolled over@effect/ai's McpServer, which is session-oriented and routes throughHttpRouter; rationale recorded in thetransport.tsheader (invariant 1 judgment call — the stateless tools-only subset is five methods over Effect Schemas).invokeEndpointrefactor (api-routes.ts): the endpoint pipeline — bearer requirement, project read gate with its existence mask, strict payload decode, registered handler — extracted fromrunRouteand shared. Behavior-preserving by construction (argsis a lazy effect, so the HTTP path's exact ordering is unchanged; the untouched policy matrix is the regression gate). MCP tools reach handlers only through it, so tool authorization is theAPI_POLICYregistry (invariant 4).publish(takes{path, content|contentBase64}files — the agent reads local files itself; the server builds the bundle through the existing shared decode and publish caps),list_projects,project_info,share_project,unpublish_project,delete_project,whoami.tools/listinput/output JSON Schemas are derived from the same Effect Schemas that validate calls.requireMcpUserchokepoint from Add MCP OAuth 2.1 surface: discovery, DCR, authorize/consent, token #29, after the standard cross-origin gate; unauthenticated calls get theWWW-Authenticateresource-metadata 401 that triggers a client's OAuth flow. Domain failures surface asisErrortool results carrying exactly the masked messages the HTTP routes emit.Tests
mcp.test.ts: framing (-32700/-32600/-32601/-32602, batch rejection, notifications → 202), version negotiation for both supported revisions, statelessness (tools/call with no prior initialize, no session header), 401/403/405/413 edges, per-tool round trips with served-byte verification, and a policy-parity matrix: every tool × credential × visibility cell derives its expected outcome fromAPI_POLICY[tool.endpoint]— strangers get the same "Project not found" / "already taken" masks the HTTP routes send.bun run cigreen.🤖 Generated with Claude Code