Bounded Automation
MCP is not an authority model
Model Context Protocol standardizes how an AI application discovers and calls a tool. It does not decide which tools should exist, which identity they use, or what counts as human approval.
MCP solves a protocol problem, not an authority problem
The easiest way to misunderstand Model Context Protocol is to treat it as a security layer.
MCP defines a client-server protocol for exchanging context and invoking capabilities. Its data layer uses JSON-RPC and includes lifecycle negotiation, tools, resources, prompts, and other optional features. Its transport layer supports local standard input/output and remote Streamable HTTP.
That is valuable. A client can discover a tool by name, description, and JSON Schema instead of relying on a custom integration for every application.
But the protocol does not answer the operational questions that determine whether the integration is safe:
- Which process identity launches the server?
- Which downstream credentials can it reach?
- Which inputs are merely well-formed and which are authorized?
- When must a person approve an action?
- What prevents an approval from being replayed?
- What evidence proves the requested state actually changed?
- What happens when the tool succeeds but the business operation fails?
Those decisions belong to the implementation.
The pattern I built
I built one local MCP server to provide a common capability layer for several agent workflows on the same machine. It uses the standard-input/output transport: each MCP client launches a server process and communicates with it directly.
The server exposes a small set of purpose-built tools plus an allowlisted catalog of read-only operating commands. During this publication audit, the server:
- negotiated MCP protocol version
2025-11-25, - returned a valid tool listing,
- exposed 16 purpose-built tools and a 20-command allowlisted catalog,
- passed JavaScript syntax validation,
- and passed all six focused security tests for public URL and document-path validation.
The complete command-catalog execution check was not completed during this review, so I do not claim that every catalog entry was live-tested here.
The important design choices are reusable without publishing my private configuration.
Fixed commands, no shell
The generic diagnostic runner accepts a command identifier, looks it up in a
manifest, and executes a fixed argument vector. Parameters replace whole
arguments only after validation. It uses Node's execFile, which launches the
program directly without a shell by default.
That removes pipes, command substitution, globbing, and shell metacharacters from the normal execution path. It does not make every command harmless; it makes the permitted behavior smaller and easier to review.
Canonical document boundaries
Document extraction resolves the real filesystem path before use. It checks that the resulting regular file remains under an approved root, denies credential-shaped names and sensitive directories, restricts file types, and enforces a size limit.
This matters because checking a path prefix before resolving symlinks is not a reliable boundary.
Public web fetch with redirect revalidation
The web tool allows HTTP and HTTPS only. It rejects URLs containing embedded credentials, resolves all destination addresses, blocks local, private, link-local, documentation, and metadata ranges, limits redirects, and validates each redirect target before following it.
The redirect check is essential. Validating only the first URL still allows a public endpoint to redirect the server toward an internal service.
Dry-run before external actions
Write-capable wrappers generate a preview unless the caller explicitly requests execution. The preview reports bounded metadata such as recipients, subject, body length, or attachment count without echoing every sensitive value.
This is a useful default. It is not the complete approval design.
What sharing one server actually improves
A shared implementation can reduce three kinds of drift.
Behavioral drift
Every client reaches the same validation code. A blocked path, rejected private address, or dry-run rule does not have to be reimplemented in several plugins.
Maintenance drift
The wrapper for a downstream API can change once when the API, credential flow, or error format changes.
Evidence drift
Tools can return a common result structure containing status, bounded output, failure reason, and evidence needed for the next step.
That makes the shared layer useful. It also creates concentration risk. If every agent launches the same server with the same environment and downstream credentials, they may inherit the same authority even when their roles differ.
One codebase should not mean one universal permission set.
The five boundaries I would define
The reusable pattern has five separate boundaries:
| Boundary | Question | Enforcement point |
|---|---|---|
| Transport | Is this a local process or a remote multi-user service? | Stdio process boundary or HTTP authorization |
| Identity | Which caller and downstream account are acting? | Per-client configuration and scoped credentials |
| Capability | What exact operation is available? | Narrow tool and input schema |
| Approval | Who may authorize this exact write? | Client UI or out-of-band approval service |
| Evidence | What proves the final state? | Read-back, receipt, or deterministic check |
MCP carries the request across these boundaries. It does not collapse them into one.
Where MCP implementations break
1. Treating annotations as access control
MCP tool annotations such as readOnlyHint, destructiveHint, and
openWorldHint are explicitly hints. The specification warns clients not to
make security decisions from annotations supplied by an untrusted server.
A tool labeled read-only still needs server-side implementation and tests that make it read-only.
2. Treating a Boolean as human approval
If the model can call a tool with { "confirm": true }, the field proves only
that the request contained true. It does not prove that a person saw or
approved the final recipients, amount, target, or content.
A stronger pattern is:
- validate the request and generate a dry-run preview,
- hash or canonically identify the exact proposed operation,
- present that proposal to the person outside the model-controlled argument,
- issue a short-lived, single-use approval bound to the operation, actor, and target,
- execute once,
- read the downstream state back,
- store a receipt that does not contain unnecessary sensitive content.
MCP's elicitation feature can support client-mediated interaction, and the specification requires clients to make the requesting server clear and allow users to accept, decline, or cancel. The protocol still does not mandate one universal approval interface, so the host application must implement it well.
3. Giving every local client the same environment
The MCP authorization specification applies to HTTP-based transports. For stdio, the official guidance is to retrieve credentials from the environment or an embedded credential library.
That makes process launch configuration part of the security model. Two clients starting the same server should receive different credentials or capability profiles when their roles differ. A low-trust assistant should not inherit a high-trust operator's mailbox, secret store, or production account merely because they share an executable.
4. Building one giant “do anything” tool
A generic shell, SQL, filesystem, or HTTP tool moves policy into model judgment. The input schema may validate the shape while leaving the authority almost unbounded.
Prefer one tool per business intent, or a small allowlisted manifest where the executable and arguments are fixed. If a capability cannot be described without “and anything else,” it is probably too broad.
5. Securing the first URL but not the redirect
A server-side fetcher can become a route into loopback services, cloud metadata endpoints, or private networks. URL syntax checks alone are insufficient.
Resolve the destination, classify every returned IPv4 and IPv6 address, restrict schemes, reject embedded credentials, and either disable redirects or revalidate every hop. Network egress policy should backstop application checks where possible.
6. Logging the request instead of the evidence
Full prompts, email bodies, document contents, tokens, and command lines may contain more sensitive data than an operator needs for reconstruction.
Log bounded metadata: operation ID, caller, tool version, target class, input digest, approval reference, result status, evidence reference, duration, and failure category. Capture raw content only when there is a defined need, retention rule, and access boundary.
7. Using one downstream token as a universal pass-through
For remote MCP servers, the current authorization guidance treats the MCP server as its own OAuth resource and requires tokens to be intended for that resource. The MCP security guidance forbids simply passing a client's token through to a downstream API. Audience validation, per-client consent, and separate downstream authorization preserve accountability and reduce confused-deputy risk.
8. Reporting tool success as workflow success
An API can return a successful status while dropping fields, queuing work that later fails, or changing a different record than intended.
The tool result should distinguish:
- request accepted,
- downstream operation completed,
- final state read back,
- and business acceptance criteria satisfied.
The last two often require a separate deterministic check.
A tool contract I would reuse
Before implementing a tool, I would write this contract:
- Intent: one sentence describing the business operation.
- Authority class: read, draft, additive write, destructive write, or external communication.
- Caller: which client identities or profiles may discover and invoke it.
- Downstream identity: the exact scoped account used.
- Inputs: JSON Schema plus semantic validation and size limits.
- Safe default: what happens without approval.
- Approval: who approves, how the proposal is bound, and when it expires.
- Idempotency: what prevents duplicate execution.
- Evidence: the read-back or receipt required before success.
- Failure: stop condition, retry policy, and escalation owner.
- Data handling: what may enter model context, logs, and persistent storage.
- Recovery: how the action is reversed or contained.
This contract is more important than the tool description shown to the model.
Local stdio or remote HTTP?
Use local stdio when:
- the server runs on the same trusted machine as the client,
- process-level configuration can provide the correct scoped identity,
- one client owns each connection,
- and the capability does not need to serve many users.
Use Streamable HTTP when:
- the capability is centrally operated,
- multiple users or machines need it,
- per-user OAuth, scope minimization, revocation, and audit are required,
- and the team can operate the additional network and authorization boundary.
Remote does not automatically mean safer. It makes the identity and resource boundary more explicit, while adding a service that must be secured, monitored, and recovered.
What I would change next in my implementation
The current server is a useful working layer, but I would not describe it as a finished enterprise control plane. The next improvements are clear:
- Replace Boolean confirmation as the final write gate with single-use, operation-bound approvals.
- Define separate capability and credential profiles for each client role.
- Add output schemas and consistent operation IDs across every tool.
- Add rate limits, idempotency keys, and deduplication to external actions.
- Store metadata-only approval and read-back receipts.
- Test every manifest command in a bounded environment and retain the result.
- Add negative tests proving lower-authority profiles cannot discover or invoke higher-authority tools.
- Document removal, credential revocation, and downstream outage behavior.
The business value
The value of a shared MCP layer is not that every assistant gets more tools.
It is that the organization can define an operation once, attach an owner and a boundary to it, test it once, and give each client only the version of that capability it is allowed to use.
That can reduce duplicated integration work and make failures more consistent. It can also make an unsafe action consistently available everywhere. The design is successful only when reuse and least privilege improve together.
Where to start
If you are adding MCP to an internal workflow, start with one operation and ask: what exact authority does the server have, and how would we prove a person approved the final write?
That answer will tell you more about production readiness than the number of tools in the registry.
References
- MCP architecture overview — host, client, server, data-layer, transport, and primitive boundaries.
- MCP tools specification, 2025-11-25 — tool discovery, schemas, model-controlled invocation, human-in-the-loop guidance, and the non-authoritative nature of annotations.
- MCP lifecycle specification, 2025-11-25 — protocol-version and capability negotiation.
- MCP authorization specification, 2025-11-25 — HTTP authorization, resource indicators, token audience, and scope minimization.
- MCP security best practices — confused-deputy risk, per-client consent, and the token-passthrough anti-pattern.
- MCP elicitation specification, 2025-11-25 — client-mediated user interaction and security requirements.
- Node.js
child_process.execFile— direct process execution without a shell by default.
Further reading
- OWASP SSRF Prevention Cheat Sheet — URL, address, DNS, redirect, and network-layer defenses.
- OWASP MCP Security Cheat Sheet — broader MCP threat patterns and layered mitigations.
- MCP transports specification, 2025-11-25 — stdio and Streamable HTTP behavior and transport security notes.
- MCP schema reference, 2025-11-25 — tool schemas, annotations, elicitation, and structured protocol types.
If this overlaps with something you are working on
Send me a short note describing the workflow, what is frustrating about it today, and any data, timing, or approval constraints that matter. Start a conversation.
Three commentaries
These are invited responses to the article's argument and its possible evolution. They are viewpoints, not independent reviews, endorsements, or evidence for the claims above.
Codex
The article correctly insists that protocol shape is not an authorization model; the useful artifact is the action contract around a tool. Its strongest next move is intentionally narrow: convert one real write tool from a generic confirmation to an operation-bound, expiring approval and publish the corresponding negative tests. That would turn the argument into a demonstrated control.
Qwen, local model
Correctly identifying MCP as a transport layer rather than a security model is crucial for safe implementation. The critique of Boolean confirmation as a proxy for human approval is particularly sharp. The proposed single-use, operation-bound approval is a necessary improvement. The idea should evolve to specify concrete cryptographic mechanisms for binding approvals to specific operations, such as signed hashes of the exact payload, to ensure that the approval token cannot be replayed or misapplied to a slightly different, potentially dangerous, subsequent request.
Claude
The core claim holds: a protocol that standardizes discovery says nothing about who may act. The five-boundary table is the reusable artifact here. What I'd flag is the distance between argument and implementation—the piece concedes its own write gate is still the Boolean it criticizes. I'd narrow that first: ship operation-bound, single-use approval on one write tool, with the negative tests published alongside.