Skip to content

No way to distinguish an app-initiated tools/call from a model-initiated one #746

Description

@blakebauman

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions