Loading the parity record
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Parity / fastmcp
Every number on this page was produced by running both implementations over the same cases: the real fastmcp package pinned at 3.4.6 answers first, and its answer is the expectation the Go port is held to. Nothing is a hand-written expectation, so a new upstream release re-scores the port on its own. See fastmcp for the port's own documentation. Source: github.com/malcolmston/fastmcp.
Not a generic diagram: every node below names an artefact of this harness — the pinned package it installed, the runner files it started, the case files it streamed, and the counts it wrote out.
| Case group | Cases | Match | Mismatch | Group parity |
|---|---|---|---|---|
| tools-list | 26 | 8 | 18 | 30.8% |
| tools-call | 22 | 13 | 9 | 59.1% |
| resources | 17 | 12 | 5 | 70.6% |
| initialize | 15 | 8 | 7 | 53.3% |
| prompts | 15 | 7 | 8 | 46.7% |
| protocol | 15 | 7 | 8 | 46.7% |
Every exported symbol of the upstream package, and what the port offers for it. The upstream list is derived mechanically, never from a README: cd python
(1) the MCP wire-method inventory: every literal `method` value declared by a
Request/Notification model in the pinned mcp package
./.venv/bin/python - <<'PY'
import inspect, typing, mcp.types as t
methods = set()
for name, obj in vars(t).items():
if inspect.isclass(obj) and (name.endswith("Request") or name.endswith("Notification")):
f = getattr(obj, "model_fields", {}).get("method")
if f is not None:
args = typing.get_args(f.annotation)
if args:
methods.add(args[0])
print(len(methods)); print("\n".join(sorted(methods)))
PY…. A symbol with no case is untested, never a match.
| Upstream symbol | Go symbol | Status | Cases | Note |
|---|---|---|---|---|
| initialize | Server.handleInitialize | differs | init-* (15) | capabilities differ; version echoed not negotiated |
| ping | Server.route("ping") | match | ping, sequence-list-then-call | |
| tools/list | Server.handleToolsList | differs | tools-list-*, schema-*, descriptor-* (26) | see JSON Schema table |
| tools/call | Server.handleToolsCall | differs | call-*, tools-call-* (26) | defaults not applied; no structuredContent/isError |
| resources/list | Server.handleResourcesList | match | resources-list-full, resources-list-uris, resource-descriptor-* | |
| resources/read | Server.handleResourcesRead | differs | resources-read-* (7) | -32602 instead of -32002; no percent-decoding |
| resources/templates/list | Server.handleResourceTemplatesList | match | templates-list-full, template-descriptor-user | server matches; the port's **client** cannot call it |
| resources/subscribe | Server.handleSubscribe | differs | resources-subscribe | port implements, upstream 3.4.6 answers -32601 |
| resources/unsubscribe | Server.handleUnsubscribe | differs | resources-unsubscribe | as above |
| prompts/list | Server.handlePromptsList | differs | prompts-list-full, prompt-* (6) | required:false omitted; no schema hint in descriptions |
| prompts/get | Server.handlePromptsGet | differs | prompts-get-* (7) | missing required argument tolerated |
| completion/complete | Server.handleComplete | extra | complete-prompt-arg, complete-resource-template, complete-bad-ref-type | Go-only in 3.4.6; advertised even with no completer |
| logging/setLevel | — | missing | cap-logging-setlevel, cap-logging-setlevel-bad-level | advertised but unimplemented |
| tasks/get | — | missing | — | MCP tasks not ported |
| tasks/list | — | missing | — | |
| tasks/cancel | — | missing | — | |
| tasks/result | — | missing | — | |
| notifications/initialized | Server.route (no-op) | match | every rpc case (handshake) | both accept and return no reply |
| notifications/cancelled | Server.route (no-op) | untested | — | accepted and ignored by both |
| notifications/progress | Context.Progress / Server.route | untested | — | requires an out-of-band notification channel to score |
| notifications/message | Context.Log | untested | — | as above |
| notifications/tools/list_changed | Server.NotifyToolsChanged | untested | — | as above |
| notifications/prompts/list_changed | Server.NotifyPromptsChanged | untested | — | as above |
| notifications/resources/list_changed | Server.NotifyResourcesChanged | untested | — | as above |
| notifications/resources/updated | Server.NotifyResourceUpdated | untested | — | as above |
| notifications/roots/list_changed | — | missing | — | not ported |
| notifications/tasks/status | — | missing | — | not ported |
| notifications/elicitation/complete | — | missing | — | not ported |
| sampling/createMessage | Context.CreateMessage | untested | — | server→client; needs a scripted client |
| roots/list | Context.ListRoots | untested | — | as above |
| elicitation/create | elicit package | untested | — | as above |
| FastMCP.name | Server.Name | match | init-serverinfo | |
| FastMCP.version | WithVersion / Server.Version | match | init-serverinfo | |
| FastMCP.instructions | WithInstructions | match | init-instructions | |
| FastMCP.tool | Server.Tool | differs | schema-* | schema generation |
| FastMCP.add_tool | Server.Tool | differs | schema-* | |
| FastMCP.call_tool | Server.handleToolsCall | differs | call-* | |
| FastMCP.list_tools | Server.handleToolsList | differs | tools-list-* | |
| FastMCP.resource | Server.Resource / BinaryResource | match | resources-read-* | |
| FastMCP.add_resource | Server.Resource | match | resource-descriptor-* | |
| FastMCP.add_template | Server.ResourceTemplate | match | template-descriptor-user | |
| FastMCP.list_resources | Server.handleResourcesList | match | resources-list-* | |
| FastMCP.list_resource_templates | Server.handleResourceTemplatesList | match | templates-list-full | |
| FastMCP.read_resource | Server.handleResourcesRead | differs | resources-read-unknown | error code, percent-decoding |
| FastMCP.prompt | Server.Prompt | differs | prompt-args-summarize | |
| FastMCP.add_prompt | Server.Prompt | differs | prompt-descriptor-review | |
| FastMCP.list_prompts | Server.handlePromptsList | differs | prompts-list-full | |
| FastMCP.render_prompt | Server.handlePromptsGet | differs | prompts-get-missing-required | |
| FastMCP.get_prompt | — | missing | — | no exported registry getter |
| FastMCP.get_tool | — | missing | — | |
| FastMCP.get_tool_by_hash | — | missing | — | |
| FastMCP.get_resource | — | missing | — | |
| FastMCP.get_resource_template | — | missing | — | |
| FastMCP.remove_tool | — | missing | — | registration is append-only in the port |
| FastMCP.disable / .enable | — | missing | — | no runtime enable/disable |
| FastMCP.icons | — | missing | — | no Icon type |
| FastMCP.website_url | — | missing | — | |
| FastMCP.lifespan | — | missing | — | no lifespan hook |
| FastMCP.generate_name | — | missing | — | |
| FastMCP.add_provider | — | missing | — | providers not ported |
| FastMCP.local_provider | — | missing | — | |
| FastMCP.transforms | — | missing | — | tool transformation not ported |
| FastMCP.add_transform | — | missing | — | |
| FastMCP.wrap_transform | — | missing | — | |
| FastMCP.add_tool_transformation | — | missing | — | |
| FastMCP.remove_tool_transformation | — | missing | — | |
| FastMCP.docket | — | missing | — | task queue not ported |
| FastMCP.get_tasks | — | missing | — | |
| FastMCP.get_app_tool | — | missing | — | apps not ported |
| FastMCP.from_fastapi | — | missing | — | no FastAPI analogue |
| FastMCP.from_openapi | openapi package | untested | — | out of scope for wire parity |
| FastMCP.mount | mount package | untested | — | |
| FastMCP.import_server | mount package | untested | — | |
| FastMCP.as_proxy | proxy package | untested | — | |
| FastMCP.add_middleware | middleware package | untested | — | |
| FastMCP.custom_route | Server.HTTPHandler | untested | — | HTTP transport not scored |
| FastMCP.http_app | Server.HTTPHandler | untested | — | |
| FastMCP.run | Server.Run | untested | — | transport, not wire behaviour |
| FastMCP.run_async | Server.Run | untested | — | |
| FastMCP.run_stdio_async | Server.ServeStdio | untested | — | used *by* the harness, not scored |
| FastMCP.run_http_async | Server.ServeHTTP | untested | — |
Every case the harness streamed to both runners, with the exact upstream symbol and Go symbol it exercised. A deliberate, documented difference is a deviation and is counted apart from a mismatch.
| Case | Group | Upstream symbol | Go symbol | Status | Note |
|---|---|---|---|---|---|
| init-full | initialize | mcp.server.lowlevel.Server.create_initialization_options | fastmcp.Server.handleInitialize | mismatch | the whole InitializeResult |
| init-capabilities | initialize | mcp.server.lowlevel.Server.get_capabilities | fastmcp.Server.handleInitialize | mismatch | the advertised capabilities object only |
| init-cap-tools | initialize | mcp.types.ServerCapabilities.tools | fastmcp.Server.handleInitialize | match | |
| init-cap-prompts | initialize | mcp.types.ServerCapabilities.prompts | fastmcp.Server.handleInitialize | match | |
| init-cap-resources | initialize | mcp.types.ServerCapabilities.resources | fastmcp.Server.handleInitialize | mismatch | upstream advertises subscribe:false because it registers no subscribe handler |
| init-cap-logging | initialize | mcp.types.ServerCapabilities.logging | fastmcp.Server.handleInitialize | match | both advertise it; see cap-logging-setlevel for whether it works |
| init-cap-completions | initialize | mcp.types.ServerCapabilities.completions | fastmcp.Server.handleInitialize | mismatch | Go advertises completions; upstream 3.4.6 does not |
| init-cap-experimental | initialize | mcp.types.ServerCapabilities.experimental | — | mismatch | |
| init-serverinfo | initialize | mcp.types.Implementation | fastmcp.Server.Name/Version | match | |
| init-instructions | initialize | fastmcp.FastMCP(instructions=...) | fastmcp.WithInstructions | match | |
| init-protocol-latest | initialize | mcp.types.LATEST_PROTOCOL_VERSION | fastmcp.ProtocolVersion | match | client asks for the current spec revision |
| init-protocol-2025-06-18 | initialize | mcp.shared.session negotiation | fastmcp.Server.handleInitialize | match | |
| init-protocol-2024-11-05 | initialize | mcp.shared.session negotiation | fastmcp.Server.handleInitialize | match | the version the Go port pins as its own default |
| init-protocol-unsupported | initialize | mcp.shared.session negotiation | fastmcp.Server.handleInitialize | mismatch | upstream negotiates down to a version it supports; the port echoes anything |
| init-protocol-garbage | initialize | mcp.shared.session negotiation | fastmcp.Server.handleInitialize | mismatch | |
| prompts-list-full | prompts | FastMCP._list_prompts_mcp | fastmcp.Server.handlePromptsList | mismatch | |
| prompt-descriptor-review | prompts | mcp.types.Prompt | fastmcp.Server.Prompt | mismatch | |
| prompt-args-summarize | prompts | mcp.types.PromptArgument | fastmcp.PromptArgument | mismatch | upstream appends a JSON-schema hint to each argument description |
| prompt-arg-required-text | prompts | mcp.types.PromptArgument.required | fastmcp.PromptArgument.Required | match | |
| prompt-arg-required-style | prompts | mcp.types.PromptArgument.required | fastmcp.PromptArgument.Required | mismatch | the port omits required:false entirely (omitempty), upstream emits false |
| prompt-arg-names | prompts | mcp.types.PromptArgument.name | fastmcp.PromptArgument.Name | match | |
| prompts-get-summarize | prompts | FastMCP._get_prompt_mcp | fastmcp.Server.handlePromptsGet | match | |
| prompts-get-summarize-default | prompts | FastMCP._get_prompt_mcp | fastmcp.Server.handlePromptsGet | match | |
| prompts-get-review | prompts | FastMCP._get_prompt_mcp | fastmcp.Server.handlePromptsGet | match | |
| prompts-get-description | prompts | mcp.types.GetPromptResult.description | fastmcp.Server.handlePromptsGet | match | |
| prompts-get-missing-required | prompts | fastmcp.exceptions.PromptError | fastmcp.Server.handlePromptsGet | mismatch | the port renders the prompt with an empty argument instead of failing |
| prompts-get-unknown | prompts | fastmcp.exceptions.NotFoundError | fastmcp.Server.handlePromptsGet | mismatch | |
| prompts-get-no-name | prompts | mcp.types.GetPromptRequest validation | fastmcp.Server.handlePromptsGet | match | |
| complete-prompt-arg | prompts | mcp.server.lowlevel.Server.completion | fastmcp.Server.handleComplete | mismatch | the port advertises completions:{}; upstream 3.4.6 registers no completion handler |
| complete-resource-template | prompts | mcp.server.lowlevel.Server.completion | fastmcp.Server.handleComplete | mismatch | |
| ping | protocol | mcp.types.PingRequest | fastmcp.Server.route ("ping") | match | |
| cap-logging-setlevel | protocol | FastMCP._set_logging_level_mcp | — | mismatch | both advertise capabilities.logging; only upstream implements the method |
| cap-logging-setlevel-bad-level | protocol | mcp.types.SetLevelRequest validation | — | mismatch | |
| unknown-method | protocol | mcp.types.ClientRequest validation | fastmcp.Server.route (default) | mismatch | upstream rejects unknown methods as -32602 (schema validation); the port returns -32601 |
| unknown-namespaced-method | protocol | mcp.types.ClientRequest validation | fastmcp.Server.route (default) | mismatch | |
| tools-call-no-params | protocol | mcp.types.CallToolRequest validation | fastmcp.Server.handleToolsCall | match | |
| tools-call-params-not-object | protocol | mcp.types.CallToolRequest validation | fastmcp.Server.handleToolsCall | mismatch | |
| tools-call-name-wrong-type | protocol | mcp.types.CallToolRequest validation | fastmcp.Server.handleToolsCall | match | |
| tools-call-arguments-not-object | protocol | mcp.types.CallToolRequest validation | fastmcp.Server.handleToolsCall | mismatch | |
| tools-call-unknown-tool | protocol | fastmcp.exceptions.NotFoundError | fastmcp.Server.handleToolsCall | mismatch | upstream reports an unknown tool as isError:true; the port as JSON-RPC -32602 |
| tools-list-with-cursor | protocol | mcp.types.ListToolsRequest.params.cursor | — | match | the port ignores pagination entirely |
| prompts-get-arguments-not-object | protocol | mcp.types.GetPromptRequest validation | fastmcp.Server.handlePromptsGet | match | |
| complete-bad-ref-type | protocol | mcp.types.CompleteRequest validation | fastmcp.Server.handleComplete | mismatch | |
| sequence-list-then-call | protocol | FastMCP._call_tool_mcp | fastmcp.Server.Dispatch | match | two requests on one session, to prove the session is reusable |
| sequence-read-then-read | protocol | FastMCP._read_resource_mcp | fastmcp.Server.handleResourcesRead | match | resource reads must be idempotent |
| resources-list-full | resources | FastMCP._list_resources_mcp | fastmcp.Server.handleResourcesList | match | |
| resources-list-uris | resources | FastMCP.list_resources | fastmcp.Server.handleResourcesList | match | |
| resource-descriptor-greeting | resources | mcp.types.Resource | fastmcp.Server.Resource | match | |
| resource-descriptor-logo | resources | mcp.types.Resource | fastmcp.Server.BinaryResource | match | |
| templates-list-full | resources | FastMCP._list_resource_templates_mcp | fastmcp.Server.handleResourceTemplatesList | match | |
| template-descriptor-user | resources | mcp.types.ResourceTemplate | fastmcp.Server.ResourceTemplate | match | |
| resources-read-text | resources | FastMCP._read_resource_mcp | fastmcp.Server.handleResourcesRead | match | |
| resources-read-json | resources | FastMCP._read_resource_mcp | fastmcp.Server.handleResourcesRead | match | |
| resources-read-blob | resources | mcp.types.BlobResourceContents | fastmcp.resourceBlobContents | match | bytes are compared as standard base64 |
| resources-read-template | resources | FastMCP._read_resource_mcp | fastmcp.resourceTemplateEntry.match | match | |
| resources-read-template-encoded | resources | FastMCP._read_resource_mcp | fastmcp.resourceTemplateEntry.match | mismatch | percent-encoded template variable |
| resources-read-template-slash | resources | FastMCP._read_resource_mcp | fastmcp.resourceTemplateEntry.match | mismatch | a slash inside a template variable must not match |
| resources-read-unknown | resources | mcp.shared.exceptions.McpError(RESOURCE_NOT_FOUND) | fastmcp.Server.handleResourcesRead | mismatch | compare the JSON-RPC code only: upstream -32002, the port -32602 |
| resources-read-missing-uri | resources | mcp.types.ReadResourceRequest validation | fastmcp.Server.handleResourcesRead | match | |
| resources-read-uri-wrong-type | resources | mcp.types.ReadResourceRequest validation | fastmcp.Server.handleResourcesRead | match | |
| resources-subscribe | resources | mcp.server.lowlevel.Server.subscribe_resource | fastmcp.Server.handleSubscribe | mismatch | the port advertises resources.subscribe:true; upstream 3.4.6 advertises false and answers -32601 |
| resources-unsubscribe | resources | mcp.server.lowlevel.Server.unsubscribe_resource | fastmcp.Server.handleUnsubscribe | mismatch | |
| call-add | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | mismatch | |
| call-add-content | tools-call | mcp.types.CallToolResult.content | fastmcp.toContent | match | |
| call-add-structured | tools-call | mcp.types.CallToolResult.structuredContent | — | mismatch | upstream wraps scalar results as {"result": ...}; the port emits none |
| call-add-iserror | tools-call | mcp.types.CallToolResult.isError | fastmcp.Server.handleToolsCall | mismatch | upstream always sets isError; the port omits it on success |
| call-add-negative | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-greet-both | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-greet-default | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | mismatch | the declared default must actually be applied when the argument is omitted |
| call-search-defaults | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | mismatch | |
| call-search-explicit | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-echo-map | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-nickname-set | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-nickname-unset | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-nickname-null | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | match | |
| call-stats-structured | tools-call | mcp.types.CallToolResult.structuredContent | fastmcp.Server.ToolWithOutput | match | |
| call-stats-content | tools-call | mcp.types.CallToolResult.content | fastmcp.toContent | mismatch | float rendering: upstream writes 6.0, encoding/json writes 6 |
| call-stats-empty | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.ToolWithOutput | match | |
| call-fail-iserror | tools-call | fastmcp.exceptions.ToolError | fastmcp.Server.handleToolsCall | match | a raising tool is a successful JSON-RPC result with isError:true |
| call-fail-content-type | tools-call | fastmcp.exceptions.ToolError | fastmcp.Server.handleToolsCall | match | |
| call-wrong-arg-type | tools-call | pydantic validation | fastmcp.buildToolEntry (json.Unmarshal) | match | |
| call-missing-required-arg | tools-call | pydantic validation | fastmcp.buildToolEntry (json.Unmarshal) | mismatch | the port silently zero-fills a missing required argument |
| call-extra-arg | tools-call | additionalProperties:false | fastmcp.buildToolEntry (json.Unmarshal) | mismatch | |
| call-no-arguments-key | tools-call | FastMCP._call_tool_mcp | fastmcp.Server.handleToolsCall | mismatch | |
| tools-list-full | tools-list | FastMCP._list_tools_mcp | fastmcp.Server.handleToolsList | mismatch | the whole ListToolsResult |
| tools-list-names | tools-list | FastMCP.list_tools | fastmcp.Server.handleToolsList | mismatch | |
| schema-add | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | mismatch | two required integers, both described |
| schema-add-required | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | match | |
| schema-add-prop-a | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.fieldSchema | match | |
| schema-greet | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | mismatch | a defaulted scalar must not be required |
| schema-greet-required | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | mismatch | known port bug: greeting has a default yet is listed required |
| schema-greet-prop-greeting | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.parseJSONSchemaTag | match | |
| schema-search | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | mismatch | |
| schema-search-required | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.reflectStructSchema | mismatch | |
| schema-search-default-int | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.parseJSONSchemaTag | mismatch | default must keep its JSON type (number, not string) |
| schema-search-default-bool | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.parseJSONSchemaTag | mismatch | |
| schema-search-default-array | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.parseJSONSchemaTag | mismatch | |
| schema-search-prop-tags | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.typeSchema | match | |
| schema-echo-map | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.buildToolEntry (dynamic map form) | mismatch | known port bug: dynamic map tools lose all argument information |
| schema-echo-map-properties | tools-list | fastmcp.tools.FunctionTool.parameters | fastmcp.buildToolEntry (dynamic map form) | mismatch |