Summary
The specification puts two enforcement duties on the host:
Host MUST reject tools/call requests from apps for tools that don't include "app" in visibility
Cross-server tool calls are always blocked for app-only tools
Both are expressed as properties of the caller — is this call coming from an app, and from which server's app. Nothing on the wire carries that. A tools/call arriving at a server is identical whether the model made it or an app did, so the rules are enforceable only inside a host that keeps app and model calls on separate internal paths, and nowhere else.
Two consequences:
1. An aggregator collapses the cross-server boundary. When a host is connected to a gateway or proxy federating several servers, "another server" no longer exists at the host: every upstream arrives on one connection. An app from vendor A is, to the host, same-server with vendor B's app-only tools. The intermediary is the only component that still knows the boundary — and it cannot enforce it, because it cannot tell an app-initiated call from a model-initiated one.
2. Servers cannot enforce visibility themselves. A server that marks a tool visibility: ["app"] is trusting every host in the chain to police it. It has no way to verify, or to refuse a call that came from the wrong side.
The reference host does not implement the first rule today: src/app-bridge.ts installs oncalltool as a verbatim forward to the server, and the visibility filtering in examples/basic-host/src/index.tsx applies only to the list handed to the model. That is worth noting not as a bug report but as evidence that the rule is easy to omit precisely because nothing on the wire represents it.
Possible direction
A marker on app-initiated calls — e.g. params._meta on tools/call naming the originating app's resource URI, set by the host and not settable by the app — would let a server enforce its own visibility, and let an intermediary re-establish the cross-server boundary it is currently forced to drop. It also makes the host's obligation checkable rather than assumed.
Trade-offs worth weighing: it is a new trust dependency on the host (a host that lies or omits it is indistinguishable from today), and it adds a field servers may read before hosts reliably send it, so an absent marker has to keep meaning "unknown" rather than "not an app".
Filing to raise the question rather than to propose a specific encoding. Happy to help with wording or a prototype if this is a direction the group wants to explore.
Context
Found while adding MCP Apps support to an MCP gateway, where the aggregation case above is the deployment rather than an edge case. Related: #745, on app-initiated calls and rewritten tool names.
Summary
The specification puts two enforcement duties on the host:
Both are expressed as properties of the caller — is this call coming from an app, and from which server's app. Nothing on the wire carries that. A
tools/callarriving at a server is identical whether the model made it or an app did, so the rules are enforceable only inside a host that keeps app and model calls on separate internal paths, and nowhere else.Two consequences:
1. An aggregator collapses the cross-server boundary. When a host is connected to a gateway or proxy federating several servers, "another server" no longer exists at the host: every upstream arrives on one connection. An app from vendor A is, to the host, same-server with vendor B's app-only tools. The intermediary is the only component that still knows the boundary — and it cannot enforce it, because it cannot tell an app-initiated call from a model-initiated one.
2. Servers cannot enforce
visibilitythemselves. A server that marks a toolvisibility: ["app"]is trusting every host in the chain to police it. It has no way to verify, or to refuse a call that came from the wrong side.The reference host does not implement the first rule today:
src/app-bridge.tsinstallsoncalltoolas a verbatim forward to the server, and thevisibilityfiltering inexamples/basic-host/src/index.tsxapplies only to the list handed to the model. That is worth noting not as a bug report but as evidence that the rule is easy to omit precisely because nothing on the wire represents it.Possible direction
A marker on app-initiated calls — e.g.
params._metaontools/callnaming the originating app's resource URI, set by the host and not settable by the app — would let a server enforce its ownvisibility, and let an intermediary re-establish the cross-server boundary it is currently forced to drop. It also makes the host's obligation checkable rather than assumed.Trade-offs worth weighing: it is a new trust dependency on the host (a host that lies or omits it is indistinguishable from today), and it adds a field servers may read before hosts reliably send it, so an absent marker has to keep meaning "unknown" rather than "not an app".
Filing to raise the question rather than to propose a specific encoding. Happy to help with wording or a prototype if this is a direction the group wants to explore.
Context
Found while adding MCP Apps support to an MCP gateway, where the aggregation case above is the deployment rather than an edge case. Related: #745, on app-initiated calls and rewritten tool names.