Connect anywhere¶
Recipes · our side needs pip install "agent-adapter[server]"
One page per stack. Each page has three parts: the exact config on their side, our server on our side, and a checklist for a public host. Every config key was checked against that project's own docs on 2026-10-30; each page links them.
flowchart LR
REG[("Registry")] --> APP["public_app<br/>create_app + bearer_auth"]
APP -->|"POST /mcp"| MCP["MCP hosts:<br/>Hermes · Letta · Mastra · Spring AI<br/>MAF .NET · Genkit · ADK Go · LangGraph.js<br/>OpenAI · Foundry · AgentCore"]
APP -->|"REST /tools, /approvals"| TS["TS client:<br/>Vercel AI SDK · Mastra"]
REV["Reviewer"] -->|"POST /approvals/{id}/decide"| APP
Pick a page¶
| Stack | Language | Attaches through | Page |
|---|---|---|---|
| Hermes Agent | Python | MCP | Hermes Agent |
| Letta | server (Python / TS SDK) | MCP | Letta |
| Mastra | TypeScript | MCP or TS client | Mastra |
| Spring AI | Java | MCP | Spring AI |
| Microsoft Agent Framework | .NET | MCP | MAF .NET |
| Genkit | TypeScript | MCP | Genkit |
| ADK Go | Go | MCP | ADK Go |
| LangGraph.js | TypeScript | MCP | LangGraph.js |
| Vercel AI SDK | TypeScript | TS client or MCP | Vercel AI SDK |
OpenAI Responses mcp tool |
hosted | public MCP | OpenAI hosted MCP |
| Azure AI Foundry Agent Service | hosted | public MCP | Azure AI Foundry |
| Bedrock AgentCore Gateway | hosted | public MCP | Bedrock AgentCore |
Already covered by an integration, no recipe needed: Marvin (through Pydantic AI), txtai agents (through smolagents) and deepagents (through LangGraph). There is no Vercel AI SDK for Python.
Our side, in one file¶
examples/connect_server.py holds every snippet these pages show. Run it as a script and
it replays a remote host offline. --serve runs the public app.
| Function | Use it for |
|---|---|
local_app() |
a framework on the same machine, in development: MCP only, no auth, loopback peers only |
public_app(tools, host, agent_token, reviewer_token) |
anything off the machine: REST and MCP behind one bearer check, Host pinned |
hosted |
the tools a hosted runtime may see: none that need approval |
def public_app(tools: Registry, host: str, agent_token: str, reviewer_token: str) -> Any:
"""REST at /, MCP at /mcp, one bearer check for both: for a host on the internet."""
from mcp.server.transport_security import TransportSecuritySettings
from agent_adapter.server import create_app
tokens = {
agent_token: Principal("hosted-agent"), # the token the remote framework sends
reviewer_token: Principal("reviewer@example.com", scopes=frozenset({APPROVER_SCOPE})),
}
security = TransportSecuritySettings(allowed_hosts=[host], allowed_origins=[f"https://{host}"])
return create_app(
tools,
authenticate=bearer_auth(tokens),
host="0.0.0.0", # a public bind: the public-host checks apply
allowed_hosts=[host], # REST: any other Host header gets 421
mcp_options={"transport_security": security}, # MCP: the same rule
)
Set AA_PUBLIC_HOST (such as tools.example.com), AA_AGENT_TOKEN and
AA_REVIEWER_TOKEN in the environment, then run python examples/connect_server.py --serve
(needs the server extra), behind a TLS proxy. Real deployments load
tokens from a secret store, or pass bearer_auth an async verifier instead of a table.
How approval works from a remote host¶
A tool with confirm=True never runs on the host's say-so: a framework's own
approval never counts alone (Security).
| Step | What happens |
|---|---|
| 1 | the host calls the tool over MCP; the server parks it and returns isError: true, text approval required: <id> |
| 2 | a reviewer decides it: POST /approvals/{id}/decide (202 approved, with a note) |
| 3 | the call runs only when the caller calls again with the pending result's _meta keys agent-adapter/run_id and agent-adapter/call_id |
| 4 | a host that drops those keys parks a new approval on every retry: nothing runs |
Our own import_mcp sends the keys back for you. Most hosts do not. So:
- give hosts that cannot resume the
hostedregistry (no confirm tools); - route confirm tools through REST (TS client or
HTTP server): there,
decideruns the call; - a host's own approval prompt (
require_approval,requireToolApproval,toolApproval,RequireConfirmation) only adds a gate. It never counts as our approval.
Public-host checklist (every page)¶
| Check | Why |
|---|---|
use public_app, never local_app, off the machine |
local_app has no auth; it refuses remote peers, and behind a proxy every peer looks local |
| one token per host, from a secret store | bearer_auth maps each token to a Principal; runs and approvals are scoped to it |
reviewers get APPROVER_SCOPE; hosts do not |
a host cannot approve its own calls |
allowed_hosts and transport_security name your public host |
other Host headers get 421 (DNS rebinding) |
| TLS, rate limits and request timeouts at a reverse proxy | out of scope for the server (security model row 12) |
several workers share approvals= and idempotency= stores |
production settings |
keep client_approval off |
none of these hosts documents a person reviewing an MCP form |