← All AI Engineer talks

AI Engineer Europe 2026

Your Insecure MCP Server Won't Survive Production — Tun Shwe, Lenses

Read the talk

Designing MCP Servers That Can Survive Production

A secure MCP service starts with the tools and data an agent can see, then carries those boundaries through remote transport, OAuth, upstream delegation and operational tracing.

From a talk by Tun Shwe and Jeremy Frenay

Before you start: Familiarity with HTTP requests, API keys and MCP tools will help; the OAuth discovery, PKCE and token-exchange flows are explained step by step.

What should an agent be allowed to see?

What changes when an API’s caller is an agent that reads tool descriptions, chooses operations and carries the results into its next decision? The interface now influences both what the caller can do and what it believes it should do. Tun Shwe and Jeremy Frenay approach that problem from their AI engineering work at Lenses, a streaming-data layer around Apache Kafka that supplies agents with real-time context. Their open-source MCP server brings that work into an interface where governance and security matter alongside access to data.

FastMCP creator Jeremiah Lowin’s product-engineering framing supplies the starting point: agents need interfaces designed for their use cases. Shwe extends it into a security principle: poor interface design and poor security compound each other. Discovery, iteration and context—the ways an agent learns about and uses an interface—each introduce a corresponding security exposure.

Slide states that agents deserve a purpose-built interface, not a REST API in a magical trenchcoat, and that a badly designed MCP server is also badly secured.
Agents need a purpose-built interface; poor MCP design also weakens security.
0:080:17
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:08 · section reference included

Discovery, iteration and context

Discovery starts differently for a human and an agent. A developer can scan API documentation, select three endpoints and work with that subset. In Shwe’s example, the agent enumerates the server’s tools and reads their descriptions whenever it connects. That consumes tokens, but it also exposes the model to more potentially hostile text. Tool poisoning places malicious instructions inside descriptions, including text a client’s interface may not display. More exposed tools create more opportunities for injection.

Iteration can repeatedly move sensitive information across a boundary. A failed script may simply run again; an agent retry can send the conversation history back to the model, including sensitive results from earlier tool calls. The exposure therefore depends on more than the current request: data returned several steps ago can travel with later attempts. Full-history retries are the interaction pattern under discussion, not a requirement imposed by MCP.

Context is both a working resource and a place where sensitive data can accumulate. Shwe uses roughly 200,000 tokens as his context-window example, not as a limit shared by all agents. The OWASP MCP Top 10 identifies context injection and oversharing as item 10. Returning unfiltered PII, credentials and internal system details puts those values into a context that an attacker may try to manipulate into exfiltrating them. Loading all that material also adds latency and consumes space needed for the task.

Finding the answer becomes a needle-in-a-haystack problem, except that some of the hay may contain hostile instructions. The practical response is curation: choose the tools the agent needs and expose the smallest useful amount of information. Reducing unnecessary context reduces both the work of finding an answer and the material available to an attacker.

2:242:34
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

2:24 · section reference included

Design for an outcome, then constrain its inputs

The first two of Shwe’s five design rules concern the operations an agent can request. OAuth can authenticate a caller and authorize access, but it cannot repair an interface that exposes unnecessary capabilities or accepts dangerous inputs. Those boundaries need to exist before authentication code is added.

First, expose a defined outcome. An agent checking an order should not also receive a tool for deleting users. Consolidate the related underlying API calls behind one operation that answers the order question, keeping the orchestration inside the server. That creates one place to authorize the requested outcome and record its execution, instead of asking the agent to navigate a collection of fine-grained operations. Fewer exposed operations mean fewer permission boundaries to manage.

Slide titled “1. Design for the Desired Outcome” says every exposed tool is a door and recommends burying orchestration in one tool with a clear outcome.
Design tools around a clear outcome, keeping orchestration inside one tool.

Second, constrain inputs at the schema boundary. Prefer top-level primitives, enums and flat dictionaries over arbitrary nested payloads. Shwe suggests Pydantic for stronger typing. The goal is to prevent unconstrained strings and structures from flowing into shells, query engines or downstream APIs. For the order-checking example, a Python request model can restrict both the identifier’s form and the requested view:

python

from typing import Literal

from pydantic import BaseModel, ConfigDict, Field


class CheckOrderInput(BaseModel):
    model_config = ConfigDict(extra="forbid", strict=True)

    order_id: str = Field(pattern=r"^ORD-[0-9]{6}$")
    view: Literal["status", "delivery"] = "status"


request = CheckOrderInput.model_validate(
    {"order_id": "ORD-000123", "view": "status"}
)

This Pydantic v2 example gives the caller a small vocabulary instead of a generic command field. Validation narrows the accepted request; downstream operations still need safe query construction and authorization for the particular order.

4:434:54
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

4:43 · section reference included

Make descriptions precise, responses small and permissions narrow

Third, treat documentation as part of the defensive design. OWASP lists tool poisoning as item 3. An attacker-controlled description on a neighboring MCP server may attempt to influence how the agent uses your tools, especially where your own instructions leave room for interpretation. Shwe argues for complete, unambiguous descriptions that state each tool’s purpose and expected use. Clear documentation reduces ambiguity; it is not an enforcement boundary or a guarantee against poisoning.

Fourth, return only what the immediate task needs. An order-status answer need not include the customer’s full record. PII, internal identifiers, credentials and system details all become potential exfiltration targets once returned to the agent. Filter the response before it enters the context window, rather than relying on the model to ignore fields it should never have received.

Fifth, minimize the blast radius. Enforce permissions at the individual tool and resource level, rather than granting broad access for an entire session. A non-destructive tool can advertise readOnlyHint, but the MCP schema treats annotations as hints, not authorization mechanisms; clients must not trust annotations from untrusted servers as a basis for tool-use decisions. Read-only access must be enforced by the server and its upstream credentials.

Where the operation is fundamentally about exposing readable data, consider an MCP resource instead of a tool. Removing an unnecessary tool removes an avenue for misuse. The larger design responsibility is to supply a trustworthy interface to a caller that may confidently use whatever capabilities and information it receives.

6:266:41
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

6:26 · section reference included

Crossing the deployment security cliff

Once the interface is designed, deployment changes the trust boundary. In the local standard-I/O setup, the MCP host talks directly to a process on the developer’s machine. There is no listening network endpoint between that host and server, and no separate network authentication exchange is needed on that link. That makes the setup convenient for a single developer, although local processes, credentials and dependencies still carry security risks.

Streamable HTTP makes the MCP server a shared service. Multiple clients can connect remotely, deployments can scale horizontally, and governance can be centralized. The MCP interface also gives clients a consistent entry point while the service manages underlying API versions and capacity.

ConcernLocal standard I/ORemote Streamable HTTP
ConnectionHost to local processClient to network service
Typical useOne developerTeams and agent fleets
Service managementLocal process lifecycleShared scaling and governance
Added exposureLocal execution and secretsNetwork access and multiple callers

The remote service needs OAuth, token lifecycle management, TLS, appropriate CORS configuration and rate limiting together. This abrupt expansion of responsibilities is Shwe’s security cliff.

Shwe cites a Stacklok transport benchmark as a reason to examine the scaling path carefully. In Stacklok’s ToolHive standard-I/O container-attachment test, 20 of 22 actual requests failed at 20 simultaneous connections. The workload used a yardstick echo server on local kind Kubernetes with port forwarding, offering 10 requests per second for five seconds: 50 requests were expected, but only 22 were recorded and two succeeded. That result describes the tested transport and attachment configuration, not an inherent concurrency ceiling for every local standard-I/O server.

8:118:21
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

8:11 · section reference included

What API-key plumbing leaves unresolved

Frenay begins the authorization walkthrough with the implementation burden: he counts more than ten specifications across core OAuth flows, client discovery and metadata, and token lifecycle management. Before following that machinery, it helps to trace the credentials in the simpler setups it replaces.

In the local example, the user provisions an API key and puts it in the MCP client configuration. The client supplies it to the local server through an environment variable, and the server uses it when requesting the external service. The user must store, maintain and rotate that credential. In the depicted setup, the key is long-lived, rarely rotated, not scoped to the client’s specific actions and sometimes shared across systems. The MCP server does not verify it before using it upstream.

Moving the same arrangement to HTTP changes where the key travels, not its lifecycle:

  1. Generate the key and store it in the client configuration.
  2. Attach it to the HTTP Authorization header on a runtime request.
  3. Let the MCP server optionally validate it, then pass it to the upstream API for verification.
  4. Receive the illustrated 200 or 401 response; a failed credential requires manual replacement in this setup.

The credential remains long-lived and lacks action-specific scope even though the server is now remote.

Two upstream arrangements introduce different problems:

  • Pass the incoming credential through. Frenay connects this to access without proper user consent. More precisely, the MCP Security Best Practices prohibit token passthrough that accepts and forwards tokens without validating that they were issued for the MCP server. The guidance’s specific confused-deputy scenario also involves upstream client identity, dynamic registration and consent handling; passthrough and that attack are related but not interchangeable terms.
  • Map callers to a shared upstream credential. A single powerful key then serves many users. Revoking one user’s upstream access becomes harder, and a leak can compromise everyone using the shared credential.

Frenay estimates that long-lived, unscoped credential setups represent more than 50% of MCP servers, without supplying a survey or population definition. The architectural direction he describes is toward short-lived, scoped tokens through OAuth 2.1.

10:1010:20
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

10:10 · section reference included

Discovering a server and registering a client

Short-lived tokens can be combined with token exchange to narrow upstream access, but first the authorization server needs to recognize the client. Traditional OAuth registration assumes a known collection of applications: a developer visits a portal, registers an app and receives a client ID. That is manageable for a handful of integrations. MCP permits Claude Desktop, Cursor, VS Code, CLI tools and arbitrary agents to discover servers at runtime. Manually registering every possible client-server pair does not fit that open-ended arrangement.

Dynamic Client Registration, or DCR, lets the client register itself with the authorization server protecting a remote MCP service. In Frenay’s walkthrough, each registration receives a new client ID. It removes the prerequisite that a user or developer manually create client credentials before connecting.

Dynamic Client Registration slide lists remote hosting, authorization-server protection, client self-registration, and a new ID each time, alongside a short HTTP server configuration.
Dynamic client registration lets the client self-register with the authorization server.

The Cursor example begins without a token and discovers what it needs from the server:

  1. Request /mcp. The MCP server returns 401 because the request lacks an access token.
  2. Follow the challenge. The WWW-Authenticate header points the client toward protected-resource metadata.
  3. Discover the authorization server. The resource document identifies the protected resource and its authorization server. Cursor then retrieves that authorization server’s metadata.
  4. Register the client. Cursor sends POST /register. The illustrated authorization server generates a client ID, persists it on disk and returns it.

The client now has a registration identifier. User authentication and permission to access the resource still come next.

13:4714:00
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

13:47 · section reference included

From user consent to an upstream API call

The authorization-code flow uses PKCE, Proof Key for Code Exchange, as required by the MCP authorization specification. The client generates a secret code verifier and derives a challenge from it. The /authorize request carries the challenge; the verifier stays with the client until token exchange. The core S256 calculation in Python is:

python

import base64
import hashlib
import secrets

verifier = secrets.token_urlsafe(32)
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier.encode("ascii")).digest()
).rstrip(b"=").decode("ascii")

pkce_authorization_parameters = {
    "code_challenge": challenge,
    "code_challenge_method": "S256",
}

The challenge binds the later redemption of the authorization code to possession of the verifier.

Without an existing user session, the authorization server redirects the user to an identity provider for single sign-on. After login, the user sees a consent screen and can grant scopes to the client. The resulting authorization code is then sent to /token together with the original verifier. The authorization server validates the code and checks the verifier against the earlier challenge before issuing an access token. Frenay’s example uses a JWT; JWT is the token format chosen for this architecture.

The client can now invoke a tool with the access token in Authorization: Bearer <access_token>. The MCP server validates the token and checks its scopes before performing the operation. Validation must establish that the token is intended for this MCP server, not merely that some authorization server signed it.

The illustrated architecture then separates MCP access from upstream API access. The MCP server becomes an OAuth client for the upstream resource server and asks the same authorization server to exchange the incoming token for an appropriate upstream token under RFC 8693. Frenay calls these the delegation token and session token. The MCP server puts the exchanged session token in the upstream request’s Authorization header.

Request boundaryCredential in the walkthroughReceiver
Client → MCP serverDelegation/access tokenMCP server
MCP server → authorization serverToken-exchange requestAuthorization server
MCP server → upstream APIExchanged session tokenUpstream resource server

These token names describe the walkthrough, not universal RFC terminology. This is also an architectural flow rather than a claim about the current Lenses repository implementation: its README describes introspection and forwarding, rather than establishing this exchange path.

16:2016:33
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

16:20 · section reference included

Replacing self-asserted registration with a metadata URL

DCR removes manual registration, but it leaves both administrative and identity problems. In the example, using Claude on Windows and then macOS creates distinct registrations rather than one portable client identity. An open /register endpoint also accepts attacker submissions. If the authorization server trusts self-asserted metadata, a malicious client can claim to be Claude; possession of a registration ID does not establish that claim. That creates opportunities for misleading consent screens and phishing.

Client ID Metadata Documents, or CIMD, change where client metadata comes from. The client owner publishes a document at a public URL, and that URL serves as the client ID. During authorization, the authorization server can retrieve the document rather than accept a fresh identity claim through a registration request.

Client ID Metadata Document slide describes a remote MCP server, authorization-server protection, a publicly exposed client ID, and fetching that ID during authorization, beside an HTTP server configuration.
Client ID Metadata Document: the client owner exposes the client ID at a public URL.

The beginning of the flow remains familiar, but the separate registration step disappears:

  1. Discover the protected service. An unauthenticated request receives 401 and a resource-metadata location. The client discovers the MCP resource and authorization server.
  2. Proceed directly to authorization. There is no instruction to send POST /register. The client generates its PKCE verifier and challenge, then calls /authorize with its URL-valued client ID.
  3. Fetch the client metadata. The authorization server retrieves the document and uses its URL as the unique client identity.
  4. Authenticate and obtain consent. The identity provider handles user login, the authorization server presents the requested scopes, and the token issuance and upstream exchange flow follow.

CIMD changes client identification; it does not remove user login, consent or token validation.

This avoids continually accumulating independent registration identities and gives policy a more stable reference. Controlling the published metadata under claude.ai, for example, is more meaningful than merely submitting a registration request that claims that name. The document binds allowed redirect URIs to the client, making callback substitution harder, and the authorization server can explicitly allow or deny client identities. Domain-controlled metadata still does not authenticate every running client instance: metadata fetching needs SSRF defenses, and localhost callback impersonation remains a concern.

Frenay describes CIMD as the preferred approach since November 2025. The versioned specification expresses that preference precisely: implementations SHOULD support CIMD and MAY support DCR. CIMD is therefore preferred in that specification, not mandatory. DCR remains a useful step beyond manual credential setup, with different identity and administration tradeoffs.

18:5519:07
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

18:55 · section reference included

Govern the operation after authorization

Successful OAuth authorization is not the end of the permission check. Frenay calls for role-based access control at the individual tool and resource level. A scope granted to a session must translate into a decision about the particular operation and data being requested. For the order-checking interface, that means deciding whether this caller may read this order, not merely whether the caller has an authenticated session.

The response needs its own boundary. Email addresses, phone numbers and national insurance numbers may need masking before the agent sees them. If a field is unnecessary, omit it; if the task needs a restricted representation, mask it before returning the result. The model should not become the mechanism responsible for concealing information it was never authorized to handle.

Audit records must make individual interactions explainable: which agent called which tool, with which parameters, and what data was returned. Frenay invokes the EU AI Act as a motivation for this transparency, rather than establishing a blanket legal requirement to retain every raw parameter and payload. The audit design must support accountability without undoing the data-protection boundaries applied to tool responses.

Finally, trace the whole request: client call, validation, tool execution, data retrieval and generated response. An isolated tool log cannot show where authorization was applied, where sensitive fields were removed or how an upstream result became the answer. Distributed-system observability provides the foundation, extended to the decisions and actions taken by agents. Governing an agent requires being able to reconstruct what it did end to end.

22:4023:03
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

22:40 · section reference included

Resources

From the talk

  • Attack scenarios and mitigations for token passthrough, confused deputies, SSRF and local MCP compromise.

Read the complete timestamped transcript
  1. 0:00

    Hey, folks. Thank you for joining us for this session on why your insecure MCP server won't survive production.

  2. 0:08

    My name is Tun Shwe, and I lead AI at Lenses, and day-to-day, I'm an AI engineer, and you can connect with me here on LinkedIn.

  3. 0:17

    And I'm Jeremy Frenay. I work on AI engineering at Lenses.

  4. 0:24

    First, a quick note on where we work. Lenses is a data operating fabric that sits between your agents and any combination of Apache Kafka. Lenses is the de facto streaming data layer for providing trusted real-time context to agentic AI.

  5. 0:39

    Companies work with us because we have governance, security, and large scale at the top of mind.

  6. 0:47

    Here are a selection of our customers, which gives us exposure to lots of different industry use cases at large scale.

  7. 0:55

    And we are here today, of course, because we have an open source MCP server that we're applying our learnings to from the field. So please give us a star to follow the project.

  8. 1:06

    And here's what we'll cover in this session. The takeaways we want you to have are the ways of thinking about designing MCP servers, and in fact, any interface to make it robust for agentic AI systems.

  9. 1:17

    And since this is a talk about MCP, we'll ensure you have tips on how to approach securing your MCP servers for production. I'll cover the first few sections, and h-- in his sections, Jeremy will go over the OAuth flows.

  10. 1:34

    So let's go straight into why most MCP servers aren't great.

  11. 1:39

    I like the way Jeremiah Lowin, who is the creator of FastMCP, put it. Um, he said that agents deserve their own interface that is optimized for their use cases to approach designing for agents through a product engineering lens.

  12. 1:53

    I want to take that approach one step further. A badly designed MCP server is also a badly secured one. Poor design and poor security compound each other.

  13. 2:05

    Jeremiah put forward three dimensions in how humans and agents differ from one another, and to consider these three dimensions when you're designing for MCP or any agentic interface. The extra layer I wanted to emphasize is security, that each one casts a security shadow.

  14. 2:24

    First, there's discovery. When you use the new API, you pull up the docs, you scan through them once, you find the three endpoints that you need, and you never look at those docs again.

  15. 2:34

    An agent can't do that. Every time it connects to an MCP server, it enumerates every single tool and reads every single description, and that's expensive in tokens. But here's the security set shadow.

  16. 2:47

    Every one of those tool descriptions is a surface for tool poisoning. Attackers can embed hidden instructions inside descriptions that are invisible in the UI, but the model will follow them without question.

  17. 2:58

    More tools means more surface area for injection.

  18. 3:03

    Second is iteration. If your script fails, you run it again. It takes a second. When an agent retries, it sends the full conversation history over the wire.

  19. 3:15

    And here's its security shadow. An agent iterating over a poorly scoped MCP server is broadcasting your data with every retry. The full conversation history goes over the wire, including any sensitive data returned by previous tool calls.

  20. 3:29

    Each round trip is a chance for data leakage.

  21. 3:34

    Third, context. You and I have decades of memories and experiences and intuition. An agent has roughly two hundred thousand tokens, and that's it. The security shadow is detailed in OWASP's MCP Top Ten list, which I recommend you all to go and read.

  22. 3:51

    And there it's listed as number ten, context injection and oversharing. If your server dumps unfiltered data into that limited window, you're handing off PII credentials, internal system details to a model that can be tricked into exfiltrating them.

  23. 4:08

    An agent has to load all the context in before it can make a decision. It makes it suitable for finding specific things, but it comes at the cost of latency and context bloat.

  24. 4:19

    So you think of it as finding a needle in a haystack.

  25. 4:22

    If some of that hay is poisoned, the agent just won't notice.

  26. 4:28

    So you should think about curation. Curate the MCP tools available to the agent and aim to expose the smallest amount of information.

  27. 4:37

    The less you expose, the less can be attacked. And here, less is more.

  28. 4:43

    Next, I'll go over what I consider five key rules for secure agentic design, uh, to think with your product engineering hat on and to apply it to MCP servers.

  29. 4:54

    The thing I want you to take away from this section is that good MCP design and good MCP security are the same discipline. If you get the design wrong, no amount of OAuth will save you.

  30. 5:05

    I've got five principles here, and they all give you protection against the OWASP MCP Top Ten before you write even a single line of auth code.

  31. 5:15

    So number one, shrink the attack surface by design. Think in terms of outcomes. The idea here is to squash all the fine-grained operations or underlying API calls into a single coarse-grained operation that produces a desired outcome.

  32. 5:28

    Every tool you expose is a door. Don't give the agent access to delete users when all it needs is to check an order. Consolidate related operations behind a single tool call with a well-defined outcome.

  33. 5:40

    So you have one permission check, one audit log entry, one place to enforce authorization. So think fewer doors with fewer locks to manage.

  34. 5:51

    Number two, constrain your inputs at the schema level. You've got to accept the top-level primitives like the enums. That will be the best approach. Um, dictionaries are also fine as long as they're not nested.

  35. 6:03

    And to introduce more strictness, you could use a typing library like Pydantic. The aim is to reject freeform nested payloads to avoid command injection flaws, where the root cause is almost always unconstrained string arguments that get passed downstream to a shell, a query engine, or an API.

  36. 6:21

    Constrained inputs are easier to validate and harder to exploit.

  37. 6:26

    Number three, treat your documentation as a defensive layer. Tool poisoning is number three on the OWASP MCP guide, and it works by embedding malicious instructions in tool descriptions that are invisible in the UI but executed by the model.

  38. 6:41

    If you don't write clear, complete instructions, an attacker-controlled tool description in a neighboring MCP server can shadow yours. If your documentation is complete and unambiguous for every tool, it crowds out the space that a poisoned neighboring server would try to fill.

  39. 6:58

    Number four, return only what the agent needs. Oversharing data in tool responses is number ten in OWASP's MCP guide, and it turns the agent's context window into a liability.

  40. 7:11

    PII, internal identifiers, credentials, system details, all sitting in the context, it-- they're all just one prompt injection away from exfiltration. So strip your payloads to the minimum. If the agent doesn't need a piece of data for its immediate task, then don't return it.

  41. 7:29

    And number five, minimize the blast radius. Scope permissions at the tool and resource level, not the session level. Use the MCP read-only annotation for non-destructive tools so that clients can enforce boundaries.

  42. 7:43

    Or if an MCP tool is intended to have read-only access, then consider turning it into an MCP resource. Also, remember that every tool you remove is an attack vector that you eliminate.

  43. 7:59

    And you're building an interface, not a tool. So this is the mindset to go in with. An agent will use anything you provide it with confidence, so you have to provide that trust layer.

  44. 8:11

    So now you've designed your server well. You followed the five principles. Now you need to actually deploy it, and this is where most teams hit what I call the security cliff.

  45. 8:21

    If you're running MCP in standard IO mode, life is pretty comfortable. It's a local process, a single user, no, no network exposure, no authentication needed. Your MCP host talks directly to the server process on your machine.

  46. 8:36

    It's a walled garden, and it works beautifully for single player developer productivity. But production requires something completely different.

  47. 8:45

    You need the streamable HTTP transport. Um, this enables remote deployment, multiple clients connecting to the same server. You can horizontally scale, and you can centralize your governance. And this is really where MCP becomes genuinely valuable to an organization, where you go from one developer on one laptop to a shared capability that an entire team or entire fleet

  48. 9:06

    of agents can use. MCP becomes the single interface that all clients can use without having to worry about whether they're the latest version of an API or considering the resources needed to scale.

  49. 9:19

    The problem is there's no gradual on-ramp. You go from zero security surface to a huge list of concerns all at once. You're suddenly needing OAuth, uh, token management, CORS configuration, TLS, uh, rate limiting, and more, and you need it all at once.

  50. 9:35

    So there's no halfway house because you can't do a little bit of production. You're either behind the wall or you're standing out in the open.

  51. 9:44

    And you can't just stay local and hope for the best. StackLock ran load tests on standard IO transport, and the results were brutal. Twenty out of twenty-two requests failed with just twenty simultaneous connections.

  52. 9:55

    Standard IO falls over the moment you add concurrency, so if you want to scale out, you have to cross the chasm.

  53. 10:03

    And how do you start crossing that chasm? I'm going to hand it over to Jeremy to continue.

  54. 10:10

    Yes. So implementing an OAuth authorization server for MCP isn't that simple. Let's look at the list of RFCs to implement.

  55. 10:20

    With the core flow, the OAuth client discovery and metadata, and the management of the token life cycle, we already have more than ten specifications to implement.

  56. 10:31

    Now, let's say we read all these RFCs, and I'm ready to implement an authorization server for MCP. What does the enterprise-grade authorization look like?

  57. 10:44

    So let's start by reviewing the local versus remote MCP server setups and their respective OAuth flows.

  58. 10:51

    Tun talked about the walled garden, the local MCP server running over standard IO with an API key. Let's look at the flow diagram. The MCP server runs on my machine.

  59. 11:04

    The client connects via standard IO. The user must set the key as a parameter in the MCP client config, and the parameter will be stored as an environment variable passed by the MCP server with its request to the external service.

  60. 11:24

    That might be good for local setups, but I need to provision, store, and maintain the key.

  61. 11:32

    This key is long-lived, it's rarely rotated, and it isn't scoped to the specific actions that my client perform.

  62. 11:40

    Even worse, these keys are often shared across systems.

  63. 11:45

    So the key is stored in a config file, an environment variable, and it isn't verified by the MCP server.

  64. 11:52

    Now let's look at a remote MCP server.

  65. 11:57

    In this case, the MCP server runs on the remote server. The client connects via HTTP. The user must set the key in the HTTP authorization header. Again, we can see the MCP client config here on screen.

  66. 12:12

    So phase one is the generation of the token and the configuration of a client.

  67. 12:19

    On step two, runtime, we can see the client performing a request, attaching this API key in the authorization header.

  68. 12:27

    This API key is validated or not by the MCP server itself and will be passed through to the upstream API, where it will be this time verified. Whether the API key is validated or not, we get a two hundred response or a four one response, in which case the user will need to rotate the token manually.

  69. 12:48

    That's how a majority of remote MCP servers are configured today. The key is long-lived. It isn't scoped to the specific action of my agent either. The key is stored in a config file, and it isn't always verified by the MCP server.

  70. 13:04

    Either the key is simply passed through to the API, creating a confused deputy vulnerability where malicious clients obtain authorization without the proper user consent. Or sometimes the key might be mapped to another key and token for the API access itself.

  71. 13:21

    Now we have a single shared credential serving many users. That credential is even more powerful, harder to revoke per user, and if leaked, it compromises everyone.

  72. 13:32

    This approach works for long-lived, unscoped credential setups, and it still represents more than fifty percent of the MCP servers out there. What we see the ecosystem moving towards is short-lived scoped tokens via OAuth two point one.

  73. 13:47

    We even see token exchange for least privileged access. Traditional OAuth assumes you know your clients up front. You register them in a developer portal, you get a client ID, and you move on.

  74. 14:00

    This works when you have five to ten apps connecting to your service. But with MCP, this flow breaks completely. Think about what MCP's architecture actually looks like. Any client, Cloud Desktop, Cursor, VS Code, a CLI tool, a random agent, can discover and connect to any MCP server at runtime.

  75. 14:25

    Pre-registration requires too much effort in a highly variable setting. It's an unbounded number of clients connected to an unbounded number of servers. You can't ask every developer to manually register their app with every MCP server they might ever want to talk to.

  76. 14:43

    So that's where the dynamic client registration comes in.

  77. 14:47

    In this case, we still have an MCP server running on a remote server, but now it's protected by an OAuth authorization server.

  78. 14:57

    The client can self-register itself against the authorization server and will get a new client ID on every registration.

  79. 15:07

    So on phase one, the discovery, our MCP client, in this case Cursor, will perform a request on slash MCP against the MCP server. We can see the MCP server returning a four one response because we do not have a token to pass yet.

  80. 15:22

    But it also passes a WW-Authenticate header containing the resource metadata that can be used by our client in order to discover the MCP server and its metadata.

  81. 15:36

    The document itself looks a bit like this. It describes the resource we're trying to access and the authorization server protecting it. This lets our client point at the authorization server itself and discover this time the metadata exposed by the authorization server itself.

  82. 15:55

    Now that our client knows how to authorize itself for an MCP server access, it needs to register itself against the authorization server. That is done via a request, a POST request on slash register.

  83. 16:10

    As we mentioned earlier, the authorization server will generate and persist on disk a new client ID and return it to the client. Now we know who we're talking to.

  84. 16:20

    Next, it's time to authorize our client against the authorization server. And for that, the MCP spec is mandating to use the PKCE, the Proof Key for Code Exchange, protocol.

  85. 16:33

    So our MCP client is first generating a code verifier and a code challenge that it does pass through a request to slash authorize in order to obtain an authorization code.

  86. 16:48

    Authorization server will validate this request and the code challenge. And since we don't have a running session yet for this user, it will redirect the user to its identity provider.

  87. 16:59

    So that's your single sign-on form in order for the user to log in. Upon successful login, the user will be redirected to a consent page where they can grant different scopes to their client.

  88. 17:14

    Now that we issued a valid authorization code for our client, it's time to use it in order to get a token, an access token. Does that by sending a request on slash token and passing the authorization code and the code verifier that we generated for the PKCE protocol earlier.

  89. 17:33

    Authorization server will validate the PKCE challenge and the authorization code, and it will then meet a brand-new token. In this case, we are using JSON web tokens in order to return an access token for our client to now use the MCP server.

  90. 17:52

    So the final step is actually to use the MCP server. This is when your MCP client is gonna perform a tool call, for example. We can see it will pass the access token we just issued in the authorization as the bearer value.

  91. 18:11

    Our MCP server will validate this token, check the valid scopes

  92. 18:16

    And it will now perform the token exchange flow in order to change this delegation token for a session token. This means our MCP server now is actually an OAuth client for a new resource server or API, but it's using the exact same authorization server in order to get a token.

  93. 18:38

    So that is the token exchange flow that is defined in RFC eight six nine three. And as we complete the flow, our MCP server can use this new session token in order to perform an API call, bypassing the token in the authorization header.

  94. 18:55

    So DCR solves the self-registration, the dynamic registration of the client, so that our user doesn't have to go and pre-register, pre-generate, uh, static credentials and set it on their client.

  95. 19:07

    But it does have its own problems. First, every time a user connects a client to an MCP server, a new registration is created. Registrations are not portable, so using Claude on Windows and then on macOS creates two distinct client registration.

  96. 19:27

    DCR is vulnerable to phishing attacks because it doesn't provide a reliable way to verify client identities. Anyone can post to that endpoint, the slash register endpoint, including attackers.

  97. 19:40

    Finally, the server is just trusting whatever metadata the client self-asserts. It means a malicious client can claim to be Claude, and the server has no way to know otherwise.

  98. 19:54

    So the MCP community had to come up with a better way to let clients self-register.

  99. 20:00

    And that is CIMD, the Client ID Metadata Document. Here in this case, we still have an OAuth authorization server in front of our MCP server, but the client owner exposes the client ID on a public URL.

  100. 20:16

    This will let our MCP server fetching the client ID during the authorization. Let's have a look at the diagram.

  101. 20:24

    So phase one is still the discovery. Our client hits the MCP server without a token, gets a four one response and, uh, the resource metadata URL. It can follow this URL, discover the MCP server, and it will get to discover the authorization server.

  102. 20:42

    But this time, the authorization server isn't mentioning it needs a slash register request.

  103. 20:50

    It means the client, the MCP client, can go straight to the authorization phase.

  104. 20:56

    We generate again the PKCE code verifier, and we perform a slash authorize request. But this time, our client passes its unique ID, and we can see it here. It's actually a valid URL where the metadata for the client is being exposed.

  105. 21:14

    This lets authorization server fetch this metadata and register a new client with a unique ID that is the URL that is exposed by the client owner

  106. 21:27

    and we can move to the authentication phase.

  107. 21:32

    Again, the authorization server will redirect to the identity provider, wait for a valid login on our user's side, present a consent screen, uh, for the user to grant some scopes, and we are ready to issue the delegation token and the session token for a token used by the MCP server.

  108. 21:53

    So here, CIMD has no growing database of client registration to maintain, proving that you control https claude.ai is meaningful, unlike proving that you can post on the registration endpoint.

  109. 22:10

    The redirect URIs that are explicitly bound to the client in its metadata document are making it harder for attackers to sneak in malicious callbacks.

  110. 22:20

    And the authorization server can selectively allow or deny clients.

  111. 22:26

    So in summary, DCR is a good start, but it does create problems.

  112. 22:32

    CIMD is a leap forward, and it is the preferred approach since November twenty twenty-five.

  113. 22:40

    But becoming enterprise-grade requires adding other layers of security and confidence. For permissions, OAuth scopes gets you part of the way there, but it's scoped to the session. True enterprise-grade role-based access control means scoping permissions at the individual tool and resource level, not just the session.

  114. 23:03

    Data masking is how you deal with the PII fields such as email, phone, and national insurance numbers. They may need to be masked before the agent sees them because agents should never be exposed to data that they have no business handling.

  115. 23:22

    You will need to log what's happening in each interaction, which agent called which tool with what parameters and what data was returned. For compliance with regulations such as the EU AI Act, regulators will expect this level of transparency and detail for autonomous AI systems.

  116. 23:43

    Finally, you need to be able to observe the full request. This means the client request, validation, tool execution, data retrieval, and the generated response. If you cannot trace what an agent did end-to-end, you cannot govern it.

  117. 24:01

    Tracing for agentic AI follows the same principles as distributed system observability, but applied to autonomous decision-making.

  118. 24:11

    Thanks very much, Jeremy, and thank you all for tuning in to this session. We'd love to know how your journey with productionizing MCP service is going, so please leave us a comment or send us a message.

  119. 24:23

    You know where to find us, and please do check out our MCP server and give us a star. So hopefully we'll see you again soon. Thanks, and bye.

  120. 24:31

    Thank you.