Repository navigation
Proposal: Standard MCP-to-WebMCP tool format converter #84
Description
Activity
Adding the point of view based on a converter that's already in use.
I work on snyk.io. We are wiring a read-only content-search tool up as a server-side MCP tool and registering it in
document.modelContextthroughmcp-handler'sexperimental_webMcpbridge. Not in production yet. The bridge is close to what is presented in Option B: the server serves a script fromGET /mcp?webmcp-scriptthat runsinitializeandtools/list, keeps an allowlisted subset, callsregisterTool()for each, and forwardsexecutetotools/call. Source:src/webmcp/script-handler.ts.Against the four things the proposal says developers currently do by hand:
-
Schema mapping was a non-issue.
nameandinputSchemapass through unchanged. The one mismatch isdescription, required onModelContextTooland optional in MCP, so the bridge falls back totitleand thenname. -
Response format is where the sketch in this issue and the bridge seem to disagree. The sketch returns
result.resultwhencontentis present. The bridge returns the wholeCallToolResultfromexecute, and WebMCP JSON-serializes whatever that resolves to, so the agent sees MCP's envelope (content,structuredContent,isError) instead of something more WebMCP-shaped. Our tool fills bothcontentandstructuredContent, so in this way the agent receives both. I don't think either choice is obviously right (which is the useful part). -
Errors are the one part a converter can't paper over. An
isError: trueresult resolves normally and the text reaches the agent. A JSON-RPC or HTTP error rejectsexecute, and the spec completes that as a failure while only optionally logging the reason, so the agent learns the call failed and not why. The sketch avoids that by returning the error as content. That split seems worth writing down, and it depends on No structured way to signal a tool's refusal, distinct from success or a schema-validation error #282 and Define specific errors for tool execution failures #323, from what I could see. -
Metadata: only
readOnlyHintis copied.idempotentHintandopenWorldHinthave nowhere to go.destructiveHintis dropped instead of mapped ontoconsequentialHint, so a destructive MCP tool arrives looking neither read-only nor consequential.outputSchemais dropped becauseModelContextToolhas no field for it yet (based on IntroduceoutputSchema(was: Should output also have a schema?) #9).
On the questions at the end: I'd keep a converter as informative guidance. Streaming isn't handled, so the bridge reads one finite SSE response and ignores progress notifications. Resources, prompts and sampling are ignored, tools only. The part I'd want written down anyway is the error mapping, forcing
destructiveHintto collapse toconsequentialHint: false.One thing about Option B as sketched,
registerMCPTools(tools, { endpoint, headers }): it registers every tool and sends the caller's headers. The bridge won't start without an allowlist. We allowlist one read-only tool and setcredentials: "omit", because anything in the page can invoke a registered tool as the visitor. That feels like part of the advice even though it isn't part of the format.-
Context
This proposal complements #83 (API for connecting to remote MCP servers). Even if WebMCP doesn't natively support remote MCP connections, there should be a standardized way to convert MCP tool definitions to WebMCP format.
Problem
MCP and WebMCP tool schemas are similar but not identical. Currently, developers bridging MCP servers to WebMCP must manually handle:
inputSchema→ WebMCPinputSchema(mostly compatible, but edge cases exist)Current Manual Conversion
Proposed Solution
A utility function or class in the WebMCP spec that standardizes this conversion:
Benefits
Considerations
This would lower the barrier for developers wanting to expose existing MCP tools to browser AI agents.