Deepanshu's Diary

Enforcing Safe Tool Calls with a Model Context Protocol (MCP) Mediator

--mcpaitools

I remember one engineering meeting where the team debated whether our voice assistant should “just call” an external booking API when the model asked for it. The tension was simple: models can be amazingly helpful calling tools, but they can also act unpredictably — making unauthorized calls, exposing secrets, or taking irreversible actions. We needed a small, enforceable contract between the model and the world. Enter the Model Context Protocol (MCP) as a design pattern, applied pragmatically to real systems I work on.

Concrete mental model

Think of MCP as a receptionist that sits between the model and any tool/provider. The receptionist maintains a short, structured contract that the model can read and (optionally) write against. That contract contains:

- what actions are allowed; - the parameters and their types; - required approvals or consent state; - any privacy constraints; and - a minimal call/response log for auditing.

This keeps the model’s “intent” separate from policy enforcement and execution. The model suggests; the receptionist checks, annotates, and either permits, requires approval, or rejects.

Architecture and data flow example

Below is a compact flow I’ve used across a few projects (voice agent, scrapers, and local knowledge services):

User utterance -> Model -> MCP Mediator -> Policy & Consent Store -> Executor/Gateway -> Provider

Here are the steps in more detail:

1. User utterance arrives (voice or text). The model produces an intent and a proposed tool call in a constrained schema. 2. The MCP Mediator validates the schema, checks policy (allowed actions, tenant scope, provider availability), and checks consent/memory flags. 3. If the call requires human approval, the mediator emits an audit event and pauses until approval is given via operator UI; otherwise it forwards to the Executor. 4. The Executor (a provider-agnostic runtime) holds encrypted provider keys, performs the call, and returns a structured response and provenance metadata back into the MCP contract. 5. The mediator logs the audit event and returns a sanitized result to the model for follow-up.

Component responsibilities (compact table)

| Component | Responsibility | |---|---| | Model | Produce constrained intent/call proposals (structured) | | MCP Mediator | Validate schema, check policy/consent, emit audit events | | Policy & Consent Store | Tenant-scoped rules, consented memory flags | | Executor/Gateway | Provider-agnostic invocation, encrypted keys, retry/timeout | | Operator UI | Approval gates, audit review |

What I built around this idea

I applied this pattern across multiple repos and components in my portfolio. Key implemented pieces:

- Amazon Voice Agent: provider-agnostic mock-first runtime, multilingual detection, consented memory, audit events, approval-gated calls, encrypted provider keys, health probes, and a full operator UI. These elements implement the MCP receptionist, approval flow, and executor abstraction. (Gate: live streaming, barge-in, transfer, and real provider acceptance remain operational gates.)

- ARIL local monorepo: tenant-scoped knowledge bases, chunking, embeddings, pgvector retrieval, citations, and protected routes. This gives the mediator a trustworthy policy and context source. (Gate: real production Postgres migration is an environment gate.)

- Google WhatsApp Scraper & Universal Scraper: adversarial safety suites, consent/review/approved-send gates, and staged extraction pipelines that slot directly into an MCP mediator for human-review flows. (Gate: sending messages or scraping outside allowed robots; review-only for now.)

Evidence and source code for these components are available in my public repos and the portfolio index [4][5].

Implemented vs operational/deployment gates

- Implemented: schema-validated MCP mediator, operator approval UI, encrypted provider keys, audit events, consented memory flags, tenant-scoped knowledge retrieval, mock-first provider runtime. - Gates (not yet live): real provider acceptance for some runtimes, production Postgres migrations, live streaming and barge-in on voice channels, sending messages to external users, and long-term durable cloud session persistence.

Where this breaks (failure modes and mitigations)

1) Model proposes out-of-spec or adversarial calls - Failure: model generates tool calls that don’t match the schema or try to escalate privileges. - Mitigation: strict schema validation, reject-and-reprompt flow, and a permissive-but-logged safe-fallback that returns a non-action summary to the user.

2) Stale capability or policy list (model assumes a capability exists) - Failure: model asks for a provider call that’s been revoked or providers changed. - Mitigation: dynamic capability discovery stored in the mediator; include capability TTLs and preflight checks before any proposal is accepted.

3) Compromised provider credentials or executor bug - Failure: Executor misroutes or leaks keys. - Mitigation: encrypted keys at rest, short-lived tokens, strict executor sandboxing, regular key rotation, and mandatory audit logs for all provider-bound requests.

4) Approval latency or human bottleneck - Failure: approval-gated flows block user experience. - Mitigation: tiered approval model (automated rule checks for low-risk actions, batched approvals, operator SLAs), and clear UI for escalation.

5) Retrieval/context drift and hallucinated facts - Failure: the model uses stale ARIL embeds or wrong tenant docs and imagines facts. - Mitigation: citation-first retrieval (return source ids), short context windows for critical decisions, and “citations required” checks before any action that has external consequences.

A practical checklist (5–8 checks)

1. Enforce a strict, versioned schema for model->tool proposals. 2. Store and check tenant-scoped policy/consent flags before execution. 3. Maintain encrypted provider keys and prefer short-lived credentials. 4. Emit and persist audit events for every proposed and executed action. 5. Require citations or evidence for any action that changes external state. 6. Provide operator approval gates with SLAs and a fallback for low-risk automation. 7. Implement preflight capability discovery with TTLs. 8. Test adversarial model outputs with fixture tests and replayable mocks.

Where this idea helped (practical note)

Using MCP as a pattern let us separate responsibilities: models stay focused on language and intent; the mediator enforces safety and policy; the executor handles provider specifics. That separation made approval flows and audits far easier to implement and reason about across projects like the Amazon Voice Agent and ARIL.

Conclusion

MCP is not a magic bullet, but treating model-tool interaction as a small, versioned contract keeps systems auditable and safer. The real gains come when you implement enforced validation, consent checks, and human-in-the-loop approval gates, while keeping the executor layer provider-agnostic and secure. If you’re connecting models to the real world, make the receptionist the first piece you build.

References

[1] Anthropic — Model Context Protocol announcement: https://www.anthropic.com/news/model-context-protocol

[2] Model Context Protocol introduction: https://modelcontextprotocol.io/introduction

[3] Toolformer — teaching models to use tools (relevant research): https://arxiv.org/abs/2307.03172

[4] My portfolio and project collection: https://github.com/deepanshuvermaa/my-portfolio

[5] Example repos mentioned (voice, scraper, infra): https://github.com/deepanshuvermaa/air-canvas, https://github.com/deepanshuvermaa/go2-payroll, https://github.com/deepanshuvermaa/trading-engine

Copied!
Back to all posts