BYOKchat Blog

MCP Architecture Explained for App Developers

Understand the current Model Context Protocol architecture: clients, servers, tools, resources, prompts, transports, stateless requests, extensions, permissions, and application state.

· 8 min read

On this page
  1. The four layers to keep separate
  2. MCP is a capability protocol
  3. Tools
  4. Resources
  5. Prompts
  6. Extensions
  7. The current protocol is stateless at the core
  8. Stateless protocol does not mean stateless application
  9. Discovery is optional
  10. The host needs its own server registry
  11. Capability discovery needs provenance
  12. The model should receive a normalized tool view
  13. MCP does not define your permission policy
  14. Separate model proposal from tool execution
  15. Results are untrusted input too
  16. Multi-round interaction changed in modern MCP
  17. Do not trust requestState as application authority
  18. Long-running work belongs in Tasks
  19. Notifications are opt-in streams
  20. Transport and application lifecycle are different
  21. Authentication is not tool authorization
  22. Version support must be explicit
  23. Persist tool rounds semantically
  24. Keep secrets out of conversation history
  25. A practical client architecture
  26. Conversation orchestrator
  27. Tool coordinator
  28. Server registry
  29. Transport/client layer
  30. Failure modes to design for
  31. What the user should be able to see
  32. Test the boundaries, not just the happy path
  33. Where BYOKchat fits
  34. Further reading

Model Context Protocol is easiest to understand when you stop thinking of it as an “agent framework” and instead treat it as a protocol boundary between an AI application and external capabilities.

The model is not the MCP client.

The MCP server is not the model.

The protocol does not decide whether a tool is safe to run.

A production application sits in the middle and owns those decisions.

Diagram illustrating the surrounding section

This separation matters because every security, UX, and reliability decision depends on knowing which layer owns which responsibility.

The four layers to keep separate

A useful architecture has four conceptual layers:

  1. Host application — conversation state, provider selection, permissions, UI, persistence, recovery.
  2. Model provider — produces text, reasoning, and tool-call proposals.
  3. MCP client implementation — speaks MCP to one or more servers.
  4. MCP server — exposes capabilities such as tools, resources, prompts, and extensions.

If these layers collapse into one abstraction, problems follow quickly:

model output accidentally becomes authorization
server metadata becomes trusted application state
protocol retries duplicate side effects
transport state leaks into conversation state

Keep them separate even if one SDK hides some of the boundaries.

MCP is a capability protocol

At a high level, an MCP server can expose several kinds of capabilities.

Tools

Tools are executable operations.

Examples:

search_issues
create_ticket
read_calendar
send_message
query_database
resize_image

A tool normally has:

  • a name;
  • a human-readable description;
  • an input schema;
  • optionally output metadata or annotations;
  • a result returned after execution.

The host should treat a tool definition as a contract offered by the server, not as permission to invoke it automatically.

Resources

Resources expose readable data addressed by a URI-like identifier.

Examples can include:

file:///project/readme.md
repo://acme/mobile/issues/42
calendar://events/today

A resource is not necessarily a local file. The identifier is part of the server’s namespace.

The important client-side question is:

What data will be read, under which authorization context, and how will that content be presented to the model?

Prompts

Prompts expose reusable prompt templates or prompt-building operations.

A host can surface them as optional workflows, but should not confuse a server-provided prompt with a trusted system instruction.

Server content is still external input.

Extensions

Modern MCP also has an extension mechanism for behavior that should not live permanently in the core protocol.

Tasks are a key example in the 2026-07-28 revision.

A host should model extension support explicitly instead of assuming every server or SDK supports every extension.

The current protocol is stateless at the core

The 2026-07-28 MCP revision changed one of the most important architectural assumptions.

The protocol-level initialize / initialized handshake is gone from the modern wire format, and Mcp-Session-Id is gone as well.

A modern request carries the information needed to process that request.

Conceptually:

POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search

with a JSON-RPC body describing the call.

That means two sequential requests may land on different server instances behind a normal load balancer.

Diagram illustrating the surrounding section

The protocol no longer requires sticky routing for modern requests.

Stateless protocol does not mean stateless application

This distinction is critical.

A server may still need business state:

shopping basket
browser session
workflow draft
search cursor
job handle
transaction preview

The modern pattern is to make that state explicit.

For example:

{
  "basket_id": "b_42"
}

The model or host then passes basket_id back to later tools.

That is different from hiding state in transport-session metadata.

Explicit handles are easier to:

  • persist;
  • inspect;
  • audit;
  • replay;
  • route across server instances;
  • include in permission decisions.

Discovery is optional

A modern client can ask a server for capabilities through server/discover.

But discovery is not a mandatory handshake that must happen before every request.

This changes client architecture.

Do not assume:

connect -> initialize -> hold session -> call tools

The modern mental model is closer to:

know endpoint
optionally discover
issue self-describing request
receive result

A client can still cache discovered metadata, but the cache needs freshness rules and invalidation behavior.

The host needs its own server registry

MCP does not replace application-level connection management.

A multi-server client still needs records such as:

interface MCPConnection {
  id: string
  displayName: string
  endpoint: URL
  authProfileID?: string
  enabled: boolean
  trustMode: "local" | "remote"
  protocolPolicy: ProtocolPolicy
}

The application should not key everything by display name.

Use a stable local identity for each configured connection.

Two servers can expose a tool with the same name.

server A -> search
server B -> search

The real identity should include the server connection.

(connection_id, tool_name)

This becomes important for permissions, history, and debugging.

Capability discovery needs provenance

Suppose two servers both expose:

create_issue

They may have different schemas, behavior, auth, and side effects.

The host should persist or display provenance:

GitHub Work / create_issue
Internal Tracker / create_issue

A permission granted to one must not silently apply to the other.

See How MCP Tool Discovery Works.

The model should receive a normalized tool view

The model provider probably does not understand MCP directly.

The host usually translates MCP tool definitions into the provider’s tool-calling schema.

Diagram illustrating the surrounding section

The reverse path also needs normalization:

provider tool proposal
-> host tool identity
-> MCP tools/call
-> MCP result
-> provider-specific tool-result message

That is why MCP support belongs in the application architecture, not in one provider adapter.

MCP does not define your permission policy

A server advertising a tool means:

This operation exists.

It does not mean:

The model may execute it without user approval.

A host can implement policies such as:

Ask
Always Allow
Disabled

or richer scopes:

Allow read-only tools
Ask for writes
Disable destructive tools

The permission decision should bind to:

  • server identity;
  • tool identity;
  • normalized arguments;
  • current user/account context;
  • side-effect class.

See How to Build an MCP Client Permission System.

Separate model proposal from tool execution

The execution path should look like this:

Diagram illustrating the surrounding section

The model never skips the host’s validation gate.

Results are untrusted input too

Tool results can contain:

  • stale data;
  • malicious instructions;
  • user-generated content;
  • HTML or Markdown;
  • URLs;
  • binary references;
  • secrets the model should not receive.

Treat result content as data from an external source.

Do not grant it system-prompt authority.

See How to Prevent Prompt Injection From Tool Results.

Multi-round interaction changed in modern MCP

Older MCP designs used server-to-client requests such as elicitation during an active exchange.

The 2026-07-28 design introduces multi-round-trip requests for stateless operation.

A server can return an input-required result rather than relying on a persistent request channel.

Conceptually:

{
  "resultType": "input_required",
  "inputRequests": {
    "confirm": {
      "type": "elicitation",
      "message": "Delete these files?"
    }
  },
  "requestState": "opaque-state"
}

The client gathers input and retries the original operation with the responses.

This has two application consequences:

  1. interactive state must survive between attempts;
  2. requestState must be treated as untrusted opaque data from the server.

See MCP Multi-Round-Trip Requests Explained.

Do not trust requestState as application authority

If the server sends:

requestState = ...

then the client may need to echo it back.

But the host should not interpret that opaque value as proof that:

  • permission was granted;
  • a user approved something;
  • a previous side effect occurred;
  • a particular account is active.

Application permissions belong in application-owned state.

If a server encodes sensitive state into such a token, it is the server’s responsibility to protect integrity and confidentiality appropriately.

Long-running work belongs in Tasks

Some tool calls cannot finish inside one request lifetime.

Examples:

large export
repository indexing
video processing
long deployment
bulk migration

The modern Tasks extension allows a server to return a task handle and let the client query progress later.

The important architecture rule is:

A task is durable application work, not merely a slow HTTP request.

The host should persist task identifiers and state if it wants recovery across app restarts.

See MCP Tasks Explained.

Notifications are opt-in streams

Modern MCP no longer assumes an unsolicited long-lived notification channel as part of every session.

For supported notification types, clients can open subscriptions/listen and request what they want to receive.

Examples include changes to tool/resource/prompt listings or resource updates.

This is operationally different from treating a transport connection as permanent application state.

A client should be prepared to:

  • open a subscription deliberately;
  • reconnect when it drops;
  • reconcile authoritative state after reconnect;
  • avoid assuming every notification was observed.

Transport and application lifecycle are different

A network request can end while the conversation continues.

A subscription stream can reconnect while the server configuration remains the same.

A task can outlive both.

Model them separately:

MCP connection configuration  -> durable
HTTP request                   -> transient
subscription stream            -> transient/reconnectable
task handle                     -> durable until terminal
conversation tool round         -> durable app history

This separation makes crash recovery much easier.

Authentication is not tool authorization

A remote MCP server may use OAuth.

Successful OAuth means roughly:

The client has an access token accepted for this protected resource.

It does not answer:

Should the model invoke delete_repository right now?

You still need host-side tool policy.

Keep these three layers distinct:

user authentication
OAuth/resource authorization
tool-execution approval

See OAuth for MCP Explained and How to Secure Remote MCP Servers.

Version support must be explicit

MCP has changed materially across revisions.

A robust client should know which behavior comes from which protocol generation.

Do not implement logic such as:

if endpoint contains /mcp, assume modern semantics

Instead, maintain explicit capability/version state and compatibility paths.

For example:

interface MCPProtocolProfile {
  revision: string
  statelessCore: boolean
  supportsMRTR: boolean
  supportsTasksExtension: boolean
  supportsSubscriptionsListen: boolean
}

The exact representation is application-specific, but version-sensitive behavior must not be guessed from server names.

See How to Migrate MCP Clients Across Protocol Versions.

Persist tool rounds semantically

A conversation record should not save only rendered text.

A useful durable record can include:

model tool proposal
server identity
tool identity
normalized arguments
permission decision
MCP request ID
result status
sanitized result summary / durable result payload
next model response

This supports:

  • crash recovery;
  • auditing;
  • debugging;
  • export;
  • model/provider switching.

Do not rely on one provider’s raw response object as the only history format.

Keep secrets out of conversation history

MCP authentication material belongs in secure credential storage.

Do not persist:

Authorization header
refresh token
client secret
session cookie
private custom header

inside normal conversation/tool records.

Store references to credential profiles instead.

A practical client architecture

Diagram illustrating the surrounding section

Each component has a narrow responsibility.

Conversation orchestrator

Owns:

  • current turn;
  • model/provider selection;
  • multi-round limits;
  • cancellation;
  • context rebuilding.

Tool coordinator

Owns:

  • tool identity resolution;
  • schema validation;
  • permission checks;
  • MCP execution;
  • result normalization;
  • retries only where safe.

Server registry

Owns:

  • configured endpoints;
  • protocol/capability profile;
  • credential reference;
  • enabled state;
  • discovery cache.

Transport/client layer

Owns:

  • JSON-RPC encoding;
  • HTTP headers;
  • protocol-version behavior;
  • MRTR transport mechanics;
  • subscription streams;
  • extension messages.

This decomposition makes it possible to update MCP behavior without rewriting chat logic.

Failure modes to design for

A production MCP client should expect:

server unreachable
auth expired
tool disappeared
schema changed
malformed result
request timed out
subscription dropped
user denied approval
MRTR input cancelled
task failed
app restarted mid-task
provider asked for same tool again

Each needs a different recovery policy.

Do not turn them all into one generic “tool failed” state internally.

What the user should be able to see

For trust and debugging, expose at least:

  • which MCP server is being used;
  • which tool is requested;
  • the arguments or a safe human-readable preview;
  • whether approval is required;
  • whether the operation is running, waiting for input, or a background task;
  • the final status.

A model that says “I checked your calendar” is not enough evidence by itself.

Test the boundaries, not just the happy path

Useful tests include:

same tool name on two servers
server changes schema after discovery
OAuth expires before tools/call
client crashes after approval before execution
server returns input_required twice
requestState is malformed or unexpectedly large
task survives app restart
subscription drops and changes occur while offline
provider repeats a side-effecting call
legacy server requires older lifecycle behavior

The difficult bugs happen between layers.

Where BYOKchat fits

A provider-neutral BYOK client is a natural MCP host because the tool layer can remain independent from the selected model provider.

The model proposes an operation, but the application still owns:

  • which MCP servers are enabled for the chat;
  • which tools are exposed;
  • per-tool permission policy;
  • approval UI;
  • tool-round persistence;
  • multi-round orchestration.

That architecture keeps MCP useful without giving external servers or model output implicit authority over the user’s device or accounts.

Further reading

Keep reading