modelcontextprotocol / modelcontextprotocol/ext-apps

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

Open
#746 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.9k
Forks
387
Avg merge
3h 21m
Merged PRs (30d)
6

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/app-bridge.ts and examples/basic-host/src/index.tsx, then compare the visibility rules and related issue #745. The current paths forward tools/call verbatim and filter only the model's tool list. Done means the group has decided whether caller identity needs a protocol-level marker and documented or prototyped the resulting enforcement behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.