MCP for Agent Capabilities: Resources, Tools, Prompts, Discovery, and Runtime Context
A team wires up an MCP server, watches the tools appear in the client, and concludes the integration works. Then a tool gets renamed upstream, a resource…

Key topics
A team wires up an MCP server, watches the tools appear in the client, and concludes the integration works. Then a tool gets renamed upstream, a resource starts carrying untrusted text, and a retry fires twice against a non-idempotent endpoint. "It works" told them nothing about who controls invocation, where context lives, or what happens when the server changes underneath them.
That gap is the whole subject of this article. MCP standardizes the wire. It does not standardize your architecture, and treating the two as the same thing is how teams end up with integrations that demo cleanly and fail expensively.
The Wire Is Not the Architecture
The Model Context Protocol is a JSON-RPC client-server protocol. A client or host initiates sessions, discovers what a server exposes, and orchestrates communication. The server exposes capabilities and enforces its own access control. That is the contract.
What the protocol does not specify is everything that determines whether your agent is reliable:
- When a capability is invoked
- Who approves that invocation
- How results are persisted
- How failures are retried
- How context is budgeted across competing capabilities
Those decisions live in the harness — the software layer around the model that supplies capabilities, state, execution, policy, and observability. MCP is one way to plug capabilities into that layer. It is not a replacement for it.
The observable consequence: two teams running identical MCP servers can have completely different reliability, cost, and audit behavior, because their harnesses differ. One team gates every write behind human approval and logs raw results. The other lets the model call freely and discards the response body after parsing. Same server, same protocol version, different systems.
Protocol conformance guarantees interoperability. It does not guarantee correctness, safety, or good context management.
Carry that invariant through everything below. Every primitive, every discovery mechanism, every authorization pattern is a wire-level agreement. The judgment about what to do with it stays yours.
Knowledge check
Check your understanding
Answer this question before you continue.
Four Primitives, Four Control Models
Most MCP coverage lists the primitives as features. That framing is why teams pick the wrong one. The useful axis is control: who decides that this thing happens — the model, the application, or the user.
| Primitive | Protocol role | Who requests it | Model directly invokes? | State/control it carries | Failure if misused |
|---|---|---|---|---|---|
| Tools | Model-controlled invocation of external operations | Model (often with client approval) | Yes | Typed inputs/outputs; side effects | Model invokes something your code should have gated |
| Resources | Read-oriented context supply | Application or client | No — surfaced by host | URI-addressed content; no side effects | Context arrives without a decision point |
| Prompts | Reusable interaction templates | User or application | No — retrieved explicitly | Argumented template; shapes interaction | Treated as a capability instead of a consistency mechanism |
| Sampling | Delegated text generation back to client's model | Server | N/A — server initiates | Inverts control direction; consumes client model budget | Surprise recursion and cost you did not budget |
Tools are model-controlled invocation of external operations. When people say "MCP," they usually mean tools. The model decides to call them, often automatically, sometimes with user approval depending on the client. Inputs and outputs are typed, which is what makes them composable and what makes schema drift a real failure class.
Resources are application- or client-controlled context supply. They are read-oriented and URI-addressed. The model does not decide to fetch a resource the way it decides to call a tool; the application surfaces it. This distinction matters more than it looks. If you expose a resource as a tool, you have silently moved a decision from your code into the model's sampling loop.
Prompts are user- or application-invoked reusable interaction templates. They are a consistency mechanism, not a capability mechanism. A prompt template that standardizes how a PR description gets drafted is doing a different job than a tool that fetches the diff. Prompts can take arguments, which makes them more than static strings — but they still shape interaction rather than execute operations.
Sampling is the primitive most architects miss. It lets the server request text generation from the client's model — the server initiates, the client's model produces. This inverts the usual direction of control and creates questions you have to answer explicitly: whose model, whose cost, whose context, and what stops a server from using sampling to pull the client into work the client never agreed to.
The practical rule: before you expose anything, name who decides it happens. If the answer is "the model," it is a tool. If it is "my application," it is a resource. If it is "the user," it is a prompt. Get that wrong and the bug will not look like a bug — it will look like the model behaving unpredictably.
Control ownership is a diagnostic question, not a primitive-selection algorithm. The protocol role of each primitive — URI-addressed content, argumented templates, typed invocation, delegated generation — determines what it can do. Who ultimately triggers it is a separate design decision you make in the harness.
Knowledge check
Check your understanding
Answer this question before you continue.
The Lifecycle Trace: Where Control Actually Lives
The control table tells you what each primitive is. It does not tell you where your harness can intervene. That requires tracing a single capability from discovery to result admission.
1. DISCOVERY
Server advertises capabilities (tools, resources, prompts).
Client receives the list. → Host can filter, reject, or annotate.
2. HOST POLICY
Harness decides what to expose to the model.
→ Host can hide tools, restrict resources, require approval.
3. MODEL-VISIBLE DEFINITION
Model sees tool schemas, resource URIs, prompt names.
→ This is what the model reasons about. Nothing more.
4. REQUEST
Model emits a tool call, or host surfaces a resource, or user selects a prompt.
→ Host can intercept, modify, or block before the wire.
5. SERVER EXECUTION
Server receives the request, enforces its own access control, executes.
→ Server can reject, transform, or fail.
6. RESULT ADMISSION
Response returns to the client. Harness decides what enters model context.
→ Host can validate, truncate, redact, or discard.
Every arrow in that trace is a place where your architecture lives and the protocol does not. The model-visible definition at step 3 is not the same as the discovered capability set at step 1. The result at step 6 is not automatically what the model sees. Teams that collapse these steps into "the model called a tool and got a result" lose every control point that matters.
Knowledge check
Check your understanding
Answer this question before you continue.
Discovery: What the Client Learns and When
Clients learn server capabilities through initialization and listing operations. The tool list is not static knowledge baked into the model. It is discovered at runtime, and that has three consequences that bite in production.
Every discovered tool consumes context and attention. A server that exposes forty endpoints is making a context-budget decision on the client's behalf. The model now has forty more things competing for the same window, and the marginal tool that never gets called still costs tokens and still dilutes the signal for the tools that matter. Curating the exposed surface is part of the server's job, not an afterthought.
The capability set can mutate mid-session. Server-side notifications signal capability changes. That means the client must handle a tool set that changes while the agent is running. A tool that existed at session start may be gone at call time; a new tool may appear that the model has never seen and will now consider fair game. Whether your client surfaces that change to the model, to you, or to neither is a harness decision.
Discovery is a trust boundary. A newly listed tool is new code the model may invoke. Nothing in the protocol vets it. The client sees a name, a description, and a schema — all of which the server controls and none of which are verified against what the tool actually does.
Treat the discovered capability set as configuration with a review path, not as ambient environment.
That is the pattern I would default to. When a server adds a tool, that should be a reviewable change, not a silent expansion of what your agent can do. The protocol gives you the mechanism to notice. It does not give you the policy to care.
Knowledge check
Check your understanding
Answer this question before you continue.
Runtime Context and the Cost of Indirection
Here is where the decision actually gets made. MCP buys real things:
- One integration written once and reused across clients
- Portable configuration that can be committed and shared
- Composability of multiple servers, each owning a domain
- Third parties can ship servers without waiting for first-party support
And it costs real things:
- An extra process or endpoint to run, authenticate, and observe
- A serialization boundary between your harness and the capability
- A version surface between client and server that can drift
- Failure modes that now span two systems instead of one
The translation-layer shortcut deserves a specific warning. Generating MCP tools from an existing OpenAPI spec gets you reach fast — point a gateway at your API, detect the spec, and every endpoint becomes a tool. The problem is that the generated tool surface is usually wider and less curated than a hand-designed one. You inherit every endpoint, including the ones no model should ever call, and you inherit the API's naming conventions rather than descriptions written for a model to reason about. Reach is not the same as a good capability surface.
When is MCP overkill? A single client, a single tool, a stable in-process function. A direct function call has fewer moving parts, no protocol to version, no process to supervise, and no serialization boundary to debug. If that describes your situation, the protocol is indirection you are paying for and not using.
When does MCP earn its keep? Multiple clients, multiple teams, third-party capability providers, or a capability you want to swap without touching the agent. The decision rule I use:
Adopt MCP when the integration count or the client count is growing. Stay in-process when neither is.
That rule is boring, which is why it works. The protocol is a coordination mechanism. Coordination mechanisms pay off when there are multiple parties to coordinate. Below that threshold, they are overhead with a specification.
Authorization, Trust, and the Capability Supply Chain
The security split in MCP is real and easy to get wrong. The server enforces its own access control. The client decides what it lets the model invoke. Neither side can fully compensate for the other, and teams routinely assume one is covering for the other.
Token-based auth is the common pattern. Identity and delegation semantics vary by implementation, so do not assume they are uniform across servers or clients. What one server treats as a scoped token, another may treat as a bearer credential with broad reach.
Prompt injection moves with the capability. A resource that carries untrusted content is an input channel directly into the model's decision loop. If that resource feeds a tool call, the untrusted text is now steering an operation. The protocol does not distinguish trusted from untrusted content; that classification is yours to make and enforce.
Third-party servers are a supply chain. They change. A server you vetted last quarter may have added tools, changed schemas, or altered what its descriptions claim. Re-verification is a continuous problem, not a one-time scan — and the tooling for discovery, vetting, and runtime monitoring of MCP servers is an active, unsettled area. Treat vendor claims about coverage as claims, not guarantees.
Observability and Failure Paths Across the Boundary
Crossing a process boundary introduces failure classes that do not exist in-process:
- Server unreachable
- Session dropped mid-call
- Schema drift between client expectation and server reality
- Tool listed but not invocable
The hardest one to debug is the trace that stops at the tool call. If your instrumentation ends when the model decides to call a tool, you cannot tell whether the model chose badly or the server failed. You need to correlate the model's decision with the server's execution, and that means logging both sides with a shared identifier.
Idempotency and retry policy belong to the harness, not the protocol. A retried non-idempotent tool is a data-corruption bug with a clean stack trace — the kind that looks like success until someone reconciles the records. The protocol will happily deliver your retry. It has no idea whether the operation was safe to repeat.
Timeouts and partial results need an explicit story. A long-running server operation needs a cancellation path and a defined answer to what the model sees when it gives up. "Nothing" is a valid answer, but it should be a decision, not an accident.
Minimum viable instrumentation, per call:
- The discovered capability set at session start
- The invocation arguments
- The raw result
- The latency
- The error class
That list is short on purpose. It is enough to answer the two questions that matter after an incident: what did the model see, and what did the server do?
But it does not verify the article's central claim about control boundaries. To test that, add a second layer:
- Session and capability version (so you can detect mid-session drift)
- Model-visible tool/resource/prompt identifiers (what the model actually saw, not what was discovered)
- Approval or policy decision (who allowed this call, and under what rule)
- Correlation ID shared across host and server (so the trace does not stop at the boundary)
- Result-admission status (was the result transformed, truncated, redacted, or discarded before entering model context)
That second layer is the minimum needed to test whether your harness is actually controlling what you think it controls.
A Small Implementation to Force the Questions
The fastest way to internalize the control-model distinction is to build the smallest server that exposes exactly one tool, one resource, and one prompt. The point is not to ship anything. The point is to make the application/harness boundary observable for each primitive.
Wire it into a client and record explicit traces, not impressions:
For each primitive, log:
- What the host listed at discovery
- What the client requested
- What entered model context
- What produced an invocation
- What policy approved or rejected
Do not ask "which primitive does the model reach for unprompted?" That question conflates client behavior, model behavior, and host presentation policy. Instead, compare the traces. You will likely find that the tool produces a visible invocation, the resource enters context silently, and the prompt requires explicit retrieval. That difference is the mechanism. Record it.
Then break it deliberately:
1. Kill the server mid-session. What does the client surface to the model? To you?
2. Change a tool's schema. Does the client fail loudly or pass stale arguments?
3. Return a malformed result. Does the harness catch it, or does the model hallucinate around it?
4. Add a tool after session start. Does the model see it? Does your policy layer?
Record what happens at each step. Those notes are the artifact that matters — the capability set, the invocation log, and the failure notes become the review material for the next server you add.
Then answer the architecture question with evidence rather than intuition: would a direct function call have been simpler for this capability? At what client count does that stop being true? You will have a real answer instead of a preference.
The Decision Rule
Choose the primitive by protocol role first, then verify control ownership as a design check: tools carry typed invocation and side effects, resources carry URI-addressed context, prompts carry argumented interaction templates, and sampling delegates generation back to the client. Adopt the protocol boundary when client count or integration count is growing. Stay in-process when neither is. Keep the harness responsible for policy, state, retries, and observability no matter what the protocol standardizes.
The next action is concrete. Pick one capability call you already have in production and instrument it end to end — from discovery through result admission. Log the capability set, the model-visible identifiers, the arguments, the raw result, the latency, the error class, the policy decision, and the result-admission status. Then write down two lists: the failures the protocol handled for you, and the failures your harness had to handle. The second list is your actual architecture, and it is the one worth reviewing.
Knowledge check
Final check
Finish the article by checking the ideas you just learned.
References
- A survey of agent interoperability protocols : Model Context Protocol ...
- Unit 3 Solution Walkthrough: Building a Pull Request Agent with MCP · Hugging Face
- App Service Easy MCP: Add AI Agent Capabilities to Your Existing Apps with Zero Code Changes | Microsoft Community Hub
- AIR raises $50M to help companies vet the skills and add-ons AI agents use - TechCrunch
- MCP Configuration Across AI Agents - Uno Platform
Research updated Sep 11, 2026


