# How do I keep an MCP server as a thin adapter over existing API logic and recheck authorization on every tool call?

**Short answer:** Treat the MCP server as a narrow protocol adapter in front of the APIs and services you already trust, not as a place to reinvent business rules. Map each tools/call to one existing API operation with a strict input schema, validate arguments, then call that API with a credential scoped to the caller — not a shared admin token. Re-check authorization on every tools/call (and every protected HTTP request), not only when the session starts: validate the access token's audience and expiry, decide whether this identity may run this tool with these arguments on this object, and fail closed if the check errors. Keep ownership, quotas, refunds, pricing and other business logic in the API (or a policy engine next to it); the MCP layer should translate, authorize, rate-limit, sanitize outputs and audit. The MCP authorization profile binds tokens to the MCP resource and forbids token passthrough to upstream APIs; the tools security considerations say servers must validate inputs and implement access controls, and clients should confirm sensitive operations and show full tool inputs before calling.

## Checklist
1. Inventory the existing API operations the agent needs. Add one MCP tool per operation (or a small, named group), with a strict JSON Schema (additionalProperties: false) — no catch-all run_anything tools.
2. Put business rules in the API or a shared policy library the API already uses. The MCP handler should validate, authorize, call, map errors and audit — not reimplement refunds, quotas or ownership checks in prompt text.
3. On every tools/call (and every protected HTTP request): validate the bearer token (issuer, audience, expiry, signature, required scopes), then run an authorization check for this user/client/agent, this tool, these arguments and this object/tenant.
4. Deny by default and fail closed: if the policy engine times out or errors, the tool does not run.
5. Never forward the MCP access token to an upstream API. Exchange or mint a separate, audience-bound credential for the downstream call (OAuth token exchange / OBO where you use it).
6. Filter tools/list to tools the caller is allowed to see. A tool the model never learns about is still not a substitute for a per-call check.
7. Require human confirmation (or a separate approval record) for writes, payments, deletes and other side effects; show the full arguments, not a model summary.
8. Append every allow/deny with tool name, argument hash, principal, decision and policy version outside the agent's editable session (for example with record_ai_run).

## Why keep MCP as a thin adapter over existing APIs?
- MCP is a way for models to discover and invoke tools. Your product's rules already live in APIs that enforce tenancy, ownership, rates and money movement. Duplicating those rules inside MCP handlers creates a second, weaker path that prompt injection and confused-deputy bugs can abuse.
- A thin adapter maps tool name + validated arguments → one existing operation, returns a bounded result, and leaves pricing, refunds, inventory and identity to the API. OWASP's MCP Security Cheat Sheet and Secure MCP Development guidance both push least privilege, narrow tool scope and authorization before every sensitive call.
- Keeping logic in one place also makes audits simpler: the same API logs and policy versions cover human UI traffic and agent traffic.

## Why recheck authorization on every tools/call, not only at connect?
- MCP authorization over HTTP is OAuth 2.1 at the transport: the access token goes on every request. That is authentication of the client to the MCP resource server — it is not a standing warrant for every tool and every object.
- OWASP's MCP cheat sheet says to validate authorization on every protected request. The current Streamable HTTP transport has no protocol-level sessions; if you still use older session ids, never treat a session id as authentication.
- Build the decision input from: authenticated principal, MCP client/agent id, tool name, validated arguments, resource ownership/tenant, token scopes and any required approval state. Re-check when context changes (revoked role, expired approval, tightened policy) instead of trusting one earlier allow.
- MCP servers must not accept tokens that were not issued for them, and must not pass client tokens through to upstream APIs — that is the token-passthrough anti-pattern in the MCP security best practices.

## What should stay out of the MCP layer?
- Business outcomes: whether a refund is allowed, whether a price is valid, whether a user owns a record. Call the API that already decides those.
- Long-lived shared secrets: no personal admin PATs shared across agents; use short-lived, audience-bound credentials per server and prefer read-only scopes until a write is approved.
- Trust in tool annotations alone: readOnlyHint and friends are hints; clients must treat them as untrusted unless the server is one you pin and trust. Enforce with credentials, allowlists and per-call checks.

## What can go wrong?
- Auth runs only in initialize or on first connect; later tools/call requests skip the check while a role or spend grant was revoked.
- The MCP server holds a god-mode service account and applies 'who may call' only in prompt instructions the model can ignore.
- The MCP access token is forwarded upstream, so rate limits and audit trails see the wrong caller (confused deputy / token passthrough).
- tools/list shows every tool to every client; a confused or injected agent picks a write tool the caller was never meant to have.
- Authorization ignores arguments, so 'delete ticket 12' and 'delete ticket 9999' look the same once the tool is allowed.

## Ask Rightshold (free, no key)
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code (one-click setup: https://www.rightshold.com/start?ref=seo), then ask: "Record a signed receipt for this MCP tools/call authorization decision: hashes of the principal, client/agent id, tool name, argument set and policy version, plus allow or deny — without storing the access tokens."

```json
{
  "tool": "record_ai_run",
  "arguments": {
    "model": "mcp-authz-adapter",
    "provider": "self-hosted",
    "prompt_sha256": "<hash of request context without secrets>",
    "output_sha256": "<hash of allow|deny + reason code>",
    "params": {
      "tool": "create_issue",
      "decision": "allow",
      "policy_version": 7,
      "arguments_sha256": "<sha256 of canonical args>",
      "principal_sha256": "<sha256 of user/agent id>"
    },
    "ran_at": "<ISO-8601>"
  }
}
```
- Needs your API key. record_ai_run returns a signed receipt you can verify later with verify_ai_run (free) without uploading tokens or full argument bodies.
- precall_check (free, no key) can probe an unfamiliar MCP server's listing and integrity before you wire it as an adapter.
- request_human_approval for write/pay/delete tools; bind the yes to tool name and arguments_sha256 so a changed call needs a new approval.
- How to re-check agent authority at execution time covers short-lived grants and policy_version stamps for non-MCP runners too.

_Rightshold records signed run receipts and human approvals for agents that call it, and can probe public MCP listings. It does not sit inside your MCP server, validate your OAuth tokens, or enforce your API's business rules. Keep authorization and business logic in your adapter and APIs. Not legal advice._

## Sources
- [MCP specification (2025-11-25): Tools — security considerations](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)
- [MCP specification (2025-11-25): Authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
- [MCP: Security Best Practices (token passthrough, session guidance)](https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices)
- [OWASP Cheat Sheet Series: MCP Security](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html)
- [OWASP MCP Security Testing Guide: Secure MCP Development](https://github.com/OWASP/MCP-Security-Testing-Guide/blob/main/04.%20Secure%20MCP%20Development.md)

## Related
- [How to re-check agent authority at execution time](https://www.rightshold.com/answers/how-to-re-check-agent-authority-at-execution-time)
- [How to do OAuth token exchange so an MCP server never forwards user tokens](https://www.rightshold.com/answers/how-to-do-oauth-token-exchange-so-an-mcp-server-never-forwards-user-tokens)
- [How to require human confirmation before MCP write tools run](https://www.rightshold.com/answers/how-to-require-human-confirmation-before-mcp-write-tools-run)
- [How to separate read-only MCP tools from side-effecting and lazy-load by task](https://www.rightshold.com/answers/how-to-separate-read-only-mcp-tools-from-side-effecting-and-lazy-load-by-task)

_Not legal advice. Updated 2026-11-07. JSON: https://www.rightshold.com/answers/how-to-keep-mcp-as-a-thin-adapter-and-recheck-authorization-on-every-tool-call.json_