Files
lda-wf/docs/mcp_protocol_proxy_inventory.md
T

16 KiB

MCP Protocol Proxy Inventory

This document records observed behavior of the wf-mcp server against real upstream MCP servers. It is not a design spec. It is a fact log for deciding which proxy features need explicit implementation.

Live Everything Server Probe

Probe date: 2026-05-13

Server command in config:

pnpx @modelcontextprotocol/server-everything

Connection id:

everything.default

Proxy mode:

wf-mcp serve

Working

  • tools/list shows Everything tools through wf-mcp.
  • tools/call works for normal tools such as everything.default.echo.
  • Tool metadata is preserved for proxy inventory, including title, description, and JSON input schema.
  • Annotated text content is preserved.
  • Tiny image content returns through the proxy.
  • Structured content returns structured JSON.
  • trigger-long-running-operation completes through the proxy as a normal tool call.
  • toggle-simulated-logging and toggle-subscriber-updates return success.
  • Native listed resources are namespaced by FastMCP mount/proxy transforms.
  • Native listed resource templates are namespaced by FastMCP mount/proxy transforms.
  • Namespaced static and dynamic resources can be read through wf-mcp.

Example listed resource mapping:

upstream: demo://resource/static/document/instructions.md
proxied:  demo://everything/default/resource/static/document/instructions.md

Gaps

  • Resource links embedded inside tool results are not rewritten by the proxy. For example, get-resource-links returns raw upstream URIs such as:

    demo://resource/dynamic/text/2
    

    wf-mcp cannot read that raw URI. The manually namespaced URI works for normal dynamic resources:

    demo://everything/default/resource/dynamic/text/2
    
  • Session resource links from gzip-file-as-resource are not currently usable through wf-mcp. The tool returned:

    demo://resource/session/probe.txt
    

    Neither the raw URI nor the manually namespaced URI was readable through wf-mcp during the live probe. Direct everything read also failed in this Codex session, so this may be session-affinity, lifecycle, or returned-link behavior rather than only URI rewriting.

  • simulate-research-query is discovered, but calling it fails cleanly:

    Tool simulate-research-query requires task augmentation (taskSupport: 'required')
    

    This is a task/protocol support gap, not an ordinary tool-call failure.

  • Logging and subscriber update tools return success, but no separate logging or resource-update notification was visible in the Codex tool channel during this probe.

  • Sampling was not visible as a direct listed tool in the current Everything server inventory.

  • Dynamic tool-list adoption is harness-dependent. On 2026-05-17, after enabling playwright.default and reloading wf-mcp, the server-side admin inventory showed the new proxy tools immediately. After restarting Codex, the UI could show playwright.default.browser_navigate, but the current model turn still did not receive a callable tool binding for it. That means a host can observe an updated tools/list without rebuilding the tool schema supplied to an already-running model interaction.

Current Classification

  • Proxy tool projection: healthy for ordinary tools.
  • Proxy resource list/read projection: healthy for listed resources and templates.
  • Tool-result resource links: ordinary embedded resource URIs are now rewritten by a local proxy transform so downstream clients receive namespaced URIs.
  • wf_mcp.proxy_results contains pure typed helpers for rewriting mcp.types.ResourceLink content and a small FastMCP workaround transform that wraps proxied tools until upstream FastMCP handles this projection itself.
  • Session resources: unresolved; needs a focused test because session affinity may matter.
  • Tasks: unsupported; task-required tools should remain clearly diagnosed until MCP task support is implemented.
  • Notifications/logging/subscriptions: unverified; current client did not show forwarded notifications.
  • Dynamic tool-list adoption: server-side updates work, but LLM harnesses may keep a stale or separately-materialized callable tool schema even after a UI refresh or reconnect.

Next Test Targets

  • Add automated tests for listed resource/template namespacing through unified mode.
  • Keep the fixture regression proving ordinary tool-returned resource_link URIs are rewritten into downstream-facing namespaced URIs.
  • Investigate session resource lifecycle separately from URI rewriting.
  • Inventory current FastMCP/MCP SDK APIs for tasks, logging notifications, and resource update notifications before implementing protocol forwarding.

Notification Inventory

Spec version checked: 2025-11-25

Official spec pages checked:

Installed local packages checked:

  • fastmcp==3.3.0
  • mcp==1.27.0

Protocol Notifications

The MCP spec defines these relevant server-to-client notifications:

Event MCP notification Notes
Tool catalog changed notifications/tools/list_changed Requires server tools.listChanged capability.
Resource catalog changed notifications/resources/list_changed Requires server resources.listChanged capability.
Prompt catalog changed notifications/prompts/list_changed Requires server prompts.listChanged capability.
Specific resource updated notifications/resources/updated Requires resource subscription flow. Params include resource uri.
Request progress notifications/progress Requires an active request with _meta.progressToken.
Logging message notifications/message Uses MCP logging level and client logging preferences.
Task status notifications/tasks/status Used by MCP task-augmented execution.

The spec also defines client-to-server requests that matter for notifications:

Request Purpose
resources/subscribe Client subscribes to updates for a specific resource URI.
resources/unsubscribe Client stops receiving updates for a specific resource URI.
request _meta.progressToken Client asks for progress notifications for that request.

FastMCP / SDK Reality

MCP SDK has concrete notification types in mcp.types, including:

  • ToolListChangedNotification
  • ResourceListChangedNotification
  • PromptListChangedNotification
  • ResourceUpdatedNotification
  • ProgressNotification
  • LoggingMessageNotification
  • TaskStatusNotification

FastMCP low-level server advertises notification support for all three list changed capabilities by default:

prompts_changed=True
resources_changed=True
tools_changed=True

FastMCP Context exposes request-scoped helpers:

  • ctx.send_notification(notification)
  • ctx.report_progress(progress, total=None, message=None)
  • ctx.log(...), plus convenience ctx.info, ctx.warning, ctx.error, etc.
  • ctx.enable_components(...), ctx.disable_components(...), and ctx.reset_visibility(...), which send list-changed notifications for the current session.

Important constraint: no global FastMCP.notify_all(...) style API was found. The obvious notification path is request/session scoped through FastMCP Context or lower-level server sessions. This matters because our internal event bus can emit outside a currently executing MCP tool call, while MCP notifications need a connected client session.

Observed Upstream Relay Behavior

Fixture probe date: 2026-05-16

The fixture server exposes emit_notifications_tool, which emits these notifications during one ordinary tool call:

Notification Direct upstream client Through unified proxy
notifications/tools/list_changed observed not observed
notifications/resources/list_changed observed not observed
notifications/prompts/list_changed observed not observed
notifications/resources/updated observed not observed
notifications/message observed not observed

This is covered by tests/wf_mcp/test_protocol_relay.py.

The current conclusion is narrow but important: ordinary mounted proxy tool calls do not automatically relay upstream server notifications to the downstream client, even though the same upstream emits those notifications correctly to a direct MCP client. The upstream logging notification is consumed locally enough for FastMCP to log it, but it is not re-emitted downstream.

Still unproven:

  • notifications/progress, because it requires a request with _meta.progressToken
  • notifications/tasks/status, because task-augmented execution is not implemented yet
  • whether future explicit relay code should project list-changed notifications one-for-one, coalesce them, or translate them into local catalog refresh events first
  • whether future explicit relay code should use FastMCP's public proxy hooks or a small local wrapper around the upstream message handler

Capability Negotiation Inventory

Fixture probe date: 2026-05-16

This pass compares one direct fixture-server initialization with the same fixture mounted through unified proxy mode.

Capability field Direct fixture server Unified proxy
tools.listChanged false true
resources.subscribe false false
resources.listChanged false true
prompts.listChanged false true
logging absent present
extensions["io.modelcontextprotocol/ui"] absent present

This is covered by tests/wf_mcp/test_protocol_capabilities.py.

Two conclusions matter:

  1. Unified proxy mode currently advertises the FastMCP server surface it can serve locally, not a union of the mounted upstream server capability objects.
  2. The missing upstream notification relay is not explained by current capability negotiation. In the same fixture, direct upstream calls receive notifications even though the fixture advertises listChanged=false, while unified proxy advertises listChanged=true for its own local surface and still does not forward upstream notifications.

Current truthful interpretation:

  • tools.listChanged, resources.listChanged, and prompts.listChanged are truthful for local proxy reload behavior because wf.admin.reload_config emits those notifications to the current downstream session.
  • They do not imply upstream list-changed notifications are relayed.
  • resources.subscribe remains false, which matches current lack of subscription ownership/relay.
  • The public initialize result does not yet expose per-upstream differences; that still belongs in future admin inventory rather than the coarse top-level capability object.

Mapping From wf-mcp Events

Current internal events that can map to MCP notifications:

Internal event Candidate MCP notification Caveat
tools_changed notifications/tools/list_changed Needs a session-aware sink.
resources_changed notifications/resources/list_changed Needs a session-aware sink.
prompts_changed notifications/prompts/list_changed Needs a session-aware sink.
future resource_updated notifications/resources/updated Requires subscription ownership and namespaced URI mapping.
future workflow_run_progress notifications/progress Only valid when a caller supplied a progress token.
future broker log events notifications/message Needs client logging level behavior.
future workflow task events notifications/tasks/status Should wait for task execution support.

Implementation Guidance

Do not emit MCP notifications directly from WfMcpService.

Recommended shape:

WfMcpService
  -> EventBus
    -> InMemoryEventSink
    -> later: McpSessionNotificationSink

Implemented local notification pieces:

  • pure internal-event to mcp.types.ServerNotification mapping
  • recording sink for protocol projection tests
  • FastMCP Context sink for request-scoped notification emission
  • wf.admin.reload_config sends tool/resource/prompt list-changed notifications to the current client session

This is still not a global broadcast system. It only emits through an active request context, which matches FastMCP's available public API today.

Concrete Notification Plan

Implement notifications in this order.

  1. Done: add a pure mapping layer from internal events to MCP notification objects.
tools_changed     -> ToolListChangedNotification
resources_changed -> ResourceListChangedNotification
prompts_changed   -> PromptListChangedNotification

This layer should not know about FastMCP sessions. It should be easy to test with plain mcp.types objects.

  1. Done: add a fake/test notification sink.

The first sink should only record which MCP notification objects would be sent. This proves the event-to-notification mapping without depending on Codex, Inspector, stdio behavior, or Streamable HTTP behavior.

  1. Done: add a FastMCP Context notification sink.

This sink can call:

ctx.send_notification(...)

It is only valid while handling a request that has an active FastMCP context. This should be treated as a session-scoped projection, not a global broadcast system.

  1. Partly done: wire local admin operations first.

Best first live target:

wf.admin.reload_config

Current behavior:

  • performs the reload
  • sends tool/resource/prompt list-changed MCP notifications to the current client session when a FastMCP context is available

Remaining cleanup:

  • also emit internal catalog/tool/resource/prompt change events from the transparent runtime path, so broker history and protocol notifications share one source of truth

This is intentionally local. It does not require solving upstream notification forwarding.

  1. Verify with Inspector/Codex.

Expected outcomes:

  • clients that honor list-changed notifications refresh their tool/resource/ prompt lists
  • clients that ignore notifications can still manually call list/search tools
  • no workflow or broker correctness depends on notification delivery
  1. Done: investigate baseline upstream notification forwarding.

Direct fixture-server tests show that list-changed, resource-updated, and logging notifications are emitted upstream but are not forwarded automatically through mounted proxy tool calls. Any relay support here will be explicit work, and notifications/resources/updated will also need namespaced resource URIs.

Deferred Notification Work

  • notifications/resources/updated: requires explicit resource subscription ownership and URI rewriting.
  • notifications/progress: requires an active request _meta.progressToken.
  • notifications/message: should respect client logging level behavior.
  • notifications/tasks/status: should wait for task-augmented execution.
  • Upstream-to-downstream notification forwarding: proven absent for ordinary mounted proxy tool calls; needs an explicit bridge if we want it.

Open Questions

  • Which upstream notifications should be forwarded one-for-one, and which ones should be converted into local catalog refresh events first?
  • Can we attach a durable notification sink to every active FastMCP session without relying on private APIs?
  • Does Codex surface list-changed, resource-updated, logging, or progress notifications from an MCP server in this environment?
  • For streamable HTTP, does FastMCP event-store support make notification replay possible for disconnected clients?