Skip to content
advanced

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…

Published 2026-09-11Updated 2026-09-1214 min read
Close-up view of a car's RPM gauge on the dashboard with clear readings.
Close-up view of a car's RPM gauge on the dashboard with clear readings. Photo by Damir K on Pexels.

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.

Two teams use the same MCP server and protocol version. Which difference can still make one integration safer and more auditable than the other?
Misconception Check

Focus: Distinguish MCP wire-level interoperability from harness-level reliability and safety decisions.

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.

PrimitiveProtocol roleWho requests itModel directly invokes?State/control it carriesFailure if misused
ToolsModel-controlled invocation of external operationsModel (often with client approval)YesTyped inputs/outputs; side effectsModel invokes something your code should have gated
ResourcesRead-oriented context supplyApplication or clientNo — surfaced by hostURI-addressed content; no side effectsContext arrives without a decision point
PromptsReusable interaction templatesUser or applicationNo — retrieved explicitlyArgumented template; shapes interactionTreated as a capability instead of a consistency mechanism
SamplingDelegated text generation back to client's modelServerN/A — server initiatesInverts control direction; consumes client model budgetSurprise 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.

An application must surface URI-addressed, read-oriented context to the model without making the model choose an external operation. Which primitive best matches that design?
Comparison Reasoning

Focus: Select the MCP primitive whose protocol role and control model match a requested capability.

The Lifecycle Trace: Where Control Actually Lives

A left-to-right flow shows MCP capabilities moving from server discovery to host policy, model-visible definitions, a request, server execution, and result admission. Policy gates appear before the model sees capabilities, before the request crosses the wire, and before the result enters model context.
MCP standardizes the exchange, while the harness controls exposure, approval, execution admission, and result admission around it.

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.

A server returns a very large tool result containing sensitive fields. At which lifecycle point can the harness truncate or redact it before the model receives it?
Scenario Interpretation

Focus: Identify where a harness can control what reaches model context after a server response.

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.

A server notification adds a new tool during an active session. What should a cautious harness do before treating the tool as automatically available to the model?
Scenario Interpretation

Focus: Apply the article's discovery and trust model to a capability change during an active session.

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.

A harness retries a timed-out write operation, and the operation may have completed before the timeout. Which fix follows the article's guidance?
Question 1 of 2Debugging

Focus: Assign retry and idempotency handling to the correct architectural layer when an MCP call may be repeated.

A capability is used by one client, owned by one team, and remains a stable in-process function. Based on the article's decision rule, what is the better default?
Question 2 of 2Comparison Reasoning

Focus: Choose between an MCP boundary and an in-process function using client and integration growth as the decision criteria.

Related sites

Build the foundations behind advanced AI systems

Use LearnLLMFast for practical LLM application foundations and LearnPyFast for the Python mechanisms that support implementation work.

LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast
Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast

Keep exploring

Related AI engineering tutorials

Continue with adjacent system layers, implementation patterns, and current AI engineering ideas.