Skip to content

Add provider-neutral per-tool MCP approval policy projection #24

Description

@Beckers26

Problem

agent-adaptor currently exposes transport/auth/required fields on mcp.Server, but it cannot express a provider-neutral per-tool approval mode. Codex supports this natively:

[mcp_servers.yangxia-agent-ui.tools.yangxia_ui_open]
approval_mode = "approve"

Without that projection, an Agent using the normal Ask policy maps to Codex approvalPolicy: "on-request". Codex sends mcpServer/elicitation/request for the tool call, while codex/appserver.Client deliberately rejects every unsolicited server request. The model observes user rejected MCP tool call and a run-scoped UI tool never executes.

Using global AutoApprove/approvalPolicy: "never" is not an acceptable workaround: it changes shell, file, permission, and unrelated third-party MCP behavior. A generic server-request auto-accept callback is also unnecessarily broad and easy to misuse.

This was reproduced with agent-adaptor v1.1.3-0.20260812201432-6432e5e4f727 and Codex 0.146: a bearer-bound loopback run service repeatedly called yangxia_ui_open; every call was declined before the MCP endpoint received it.

Proposed provider-neutral API

Extend the MCP declaration itself, so approval authority is coupled to one exact MCP server and exact tool names:

package driver

type MCPToolApprovalMode string

const (
    MCPToolApprovalPrompt  MCPToolApprovalMode = "prompt"
    MCPToolApprovalApprove MCPToolApprovalMode = "approve"
)

type MCPToolPolicy struct {
    ApprovalMode MCPToolApprovalMode
}

type MCPServerSpec struct {
    // existing fields...
    Tools map[string]MCPToolPolicy
}

Ergonomic construction:

srv := mcp.HTTP(
    "yangxia-agent-ui",
    loopbackURL,
    mcp.WithBearerTokenEnv("YANGXIA_AGENT_UI_RUN_TOKEN"),
    mcp.Required("run-scoped A2UI bridge"),
    mcp.WithToolApproval("yangxia_ui_open", mcp.ToolApprovalApprove),
    mcp.WithToolApproval("yangxia_ui_patch", mcp.ToolApprovalApprove),
    mcp.WithToolApproval("yangxia_ui_wait_action", mcp.ToolApprovalApprove),
    mcp.WithToolApproval("yangxia_ui_close", mcp.ToolApprovalApprove),
)

Suggested public names are illustrative; the important contract is:

  • the override lives on the exact MCPServerSpec, not on the Agent/global policy;
  • tool names are explicit and normalized/sorted;
  • only closed enum values are accepted;
  • duplicate declarations for one tool conflict unless byte-for-byte equivalent;
  • the field participates in MCP/runtime/profile/session compatibility fingerprints;
  • no secret or bearer value is stored in the policy;
  • unsupported drivers fail closed when a non-default override is requested; they must not silently drop it.

Codex projection

For Codex, render:

[mcp_servers.<server>.tools.<tool>]
approval_mode = "prompt" | "approve"

This uses Codex's existing per-tool policy and avoids handling mcpServer/elicitation/request in agent-adaptor for auto-approved tools. The existing app-server behavior (reject all unsolicited server requests) can remain unchanged for command/file/permission requests and for MCP tools that still require a prompt.

The projection must work for both construction-time WithMCP and call-scoped RunServiceProvider attachments. Call-scoped policies must be removed during detach/profile restoration exactly like the corresponding server.

Security model

A host that can construct a RunServiceProvider is already trusted to attach executable/remote MCP capability to the run. The new field does not make arbitrary model tools trusted: it lets that host narrowly declare which tools on that exact attached server do not need an additional provider UI prompt.

Required negative boundaries:

  • another MCP server with the same tool name remains unchanged;
  • another tool on the same server remains unchanged;
  • shell, file-change, request_permissions, URL/auth elicitation, and unknown app-server methods remain unchanged;
  • literal secrets, headers, URLs, or arguments never enter the approval policy;
  • profile/runtime fingerprint changes when the allowlist changes;
  • an unsupported provider returns a pre-launch capability/config error rather than executing without the requested policy.

For extra defense, hosts should combine approve with an authenticated run-scoped endpoint. In the motivating case the service is exact loopback, bearer-bound to one run, and each tool validates surface/run/binding authority; the approval override is not a replacement for those checks.

Tests / acceptance

  1. Public API/golden tests cover stable clone, normalization, ordering, duplicate detection, and JSON/TOML-independent fingerprinting.
  2. Codex profile test renders exactly four requested tool tables and no server-wide default_tools_approval_mode.
  3. Exact fake Codex app-server run under global on-request proves an approved tool reaches MCP without mcpServer/elicitation/request.
  4. A fifth tool on the same server still requests approval and is rejected by the current client.
  5. Same tool name on a different server still requests approval.
  6. Command/file/permission server requests remain rejected and do not inherit MCP policy.
  7. RunService attach/detach and resume tests prove policy is call-scoped, stable across endpoint secret rotation, and changes compatibility when the tool policy changes.
  8. Claude/Cursor/CodeBuddy either map an equivalent exact per-tool primitive or fail pre-launch; never silently downgrade.

Non-goals

  • Globally disabling approvals.
  • Auto-accepting arbitrary mcpServer/elicitation/request payloads.
  • Treating Required, ReuseKey, server name, loopback URL, or bearer presence alone as trust.
  • Making approval decisions from model-provided arguments.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions