Full transcript
0:00Hey folks, thank you for joining us for
0:02this session on why your insecure MCP
0:05server won't survive production.
0:08My name is Tin Shway and I lead AI at
0:10Lenses and day-to-day I'm an AI engineer
0:13and you can connect with me here on
0:14LinkedIn.
0:17And I'm Jeremy Fronae. I work on AI
0:19engineering at Lenses.
0:24First, a quick note on where we work.
0:26Lenses is a data operating fabric that
0:28sits between your agents and
0:32Lenses is the de facto streaming data
0:34layer for providing trusted real-time
0:36context to agentic AI.
0:39Companies work with us because we have
0:40governance, security and large scale at
0:43the top of mind.
0:47Here are a selection of our customers
0:48which gives us exposure to lots of
0:50different industry use cases at large
0:53scale.
0:55And we are here today of course because
0:57we have an open source MCP server that
0:59we're applying our learnings to from the
1:01field. So, please give us a star to
1:03follow the project.
1:06And here's what we'll cover in this
1:07session. The takeaways we want you to
1:09have are the ways of thinking about
1:11designing MCP servers and in fact any
1:13interface to make it robust for agentic
1:16AI systems. And since this is a talk
1:18about MCP, we'll ensure you have tips on
1:21how to approach securing your MCP
1:23service for production.
1:24I'll cover the first few sections and in
1:26his sections Jeremy will go over the
1:29OAuth flows.
1:33So, let's go straight into why most MCP
1:36servers aren't great.
1:39I like the way Jeremy Lowin, who is the
1:41creator of Fast MCP, put it. Um he said
1:44that agents deserve their own interface
1:46that is optimized for their use cases.
1:49To approach designing for agents through
1:51a product engineering lens.
1:53I want to take that approach one step
1:55further.
1:56A badly designed MCP server is also a
1:58badly secured one.
2:00Poor design and poor security compound
2:03each other.
2:05Jeremy put forward three dimensions in
2:07how humans and agents differ from one
2:10another and to consider these three
2:11dimensions when you're designing for MCP
2:14or any agentic interface.
2:16The extra layer I wanted to emphasize is
2:18security that each one casts a security
2:21shadow.
2:24First, there's discovery.
2:26When you use the new API, you pull up
2:27the docs, you scan through them once,
2:30you find the three end points that you
2:31need and you never look at those docs
2:33again.
2:34An agent can't do that.
2:36Every time it connects to an MCP server,
2:38it enumerates every single tool and
2:40reads every single description. And
2:42that's expensive in tokens.
2:44But here's the security set shadow.
2:47Every one of those tool descriptions is
2:48a surface for tool poisoning.
2:51Attackers can embed hidden instructions
2:52inside descriptions that are invisible
2:54in the UI, but the model will follow
2:56them without question.
2:58More tools means more surface area for
3:00injection.
3:03Second is iteration. If your script
3:05fails, you run it again. It takes a
3:07second. When an agent retries, it sends
3:11the full conversation history over the
3:12wire.
3:14And here's its security shadow.
3:17An agent iterating over a poorly scoped
3:19MCP server is broadcasting your data
3:21with every retry.
3:23The full conversation history goes over
3:25the wire including any sensitive data
3:27returned by previous tool calls. Each
3:29round trip is a chance for data leakage.
3:34Third, context. You and I have decades
3:37of memories and experiences and
3:39intuition. An agent has roughly 200,000
3:42tokens and that's it.
3:44The security shadow is detailed in
3:47OWASP's MCP top 10 list which I
3:49recommend you all to go and read. And
3:51there it's listed as number 10, context
3:54injection and oversharing.
3:56If your server dumps unfiltered data
3:58into that limited window, you're handing
4:00off PII, credentials, internal system
4:03details to a model that can be tricked
4:05into exfiltrating them.
4:08An agent has to load all the context in
4:11before it can make a decision.
4:13It makes it suitable for finding
4:14specific things, but it comes at the
4:16cost of latency and context bloat. So,
4:19you think of it as finding a needle in a
4:20haystack.
4:22If some of that hay is poisoned, the
4:24agent just won't notice.
4:27So, you should think about curation.
4:29Curate the MCP tools available to the
4:31agent and aim to expose the smallest
4:34amount of information.
4:36The less you expose, the less can be
4:39attacked. And here, less is more.
4:43Next, I'll go over what I consider five
4:46key rules for secure agentic design. Uh
4:49to think with your product engineering
4:51hat on and to apply it to MCP servers.
4:54The thing I want you to take away from
4:56this section is that good MCP design and
4:58good MCP security are the same
5:00discipline. If you get the design wrong,
5:03no amount of OAuth will save you.
5:05I've got five principles here and they
5:07all give you protection against the
5:08OWASP MCP top 10 before you write even a
5:11single line of OAuth code.
5:14So, number one,
5:16shrink the attack surface by design.
5:18Think in terms of outcomes. The idea
5:20here is to squash all the fine-grained
5:22operations or underlying API calls into
5:24a single coarse-grained operation that
5:26produces a desired outcome.
5:28Every tool you expose is a door.
5:31Don't give the agent access to delete
5:32users when all it needs is to check an
5:34order.
5:35Consolidate related operations behind a
5:37single tool call with a well-defined
5:39outcome. So, you have one permission
5:41check, one audit log entry, one place to
5:44enforce authorization. So, think fewer
5:47doors with fewer locks to manage.
5:51Number two, constrain your inputs at the
5:53schema level.
5:55You've got to accept the top-level
5:56primitives like the enums, that will be
5:58the best approach. Um dictionaries are
6:00also fine as long as they're not nested
6:02and to introduce more strictness, you
6:04could use a typing library like
6:06Pydantic.
6:08The aim is to reject free-form nested
6:10payloads to avoid command injection
6:12flaws where the root cause is almost
6:14always unconstrained string arguments
6:16that get passed downstream to a shell, a
6:19query engine or an API. Constrained
6:21inputs are easier to validate and harder
6:23to exploit.
6:26Number three, treat your documentation
6:28as a defensive layer.
6:30Tool poisoning is number three on the
6:32OWASP MCP guide and it works by
6:34embedding malicious instructions in tool
6:36descriptions that are invisible in the
6:38UI, but executed by the model.
6:41If you don't write clear, complete
6:42instructions, an attacker-controlled
6:45tool description in a neighboring MCP
6:47server can shadow yours.
6:49If your documentation is complete and
6:51unambiguous for every tool, it crowds
6:53out the space that a poisoned
6:55neighboring server would try to fill.
6:58Number four,
7:00return only what the agent needs.
7:02Oversharing data in tool responses is
7:04number 10 in OWASP's MCP guide and it
7:07turns the agent's context window into a
7:09liability.
7:10PII, internal identifiers, credentials,
7:14system details, all sitting in the
7:16context, they're all just one prompt
7:18injection away from exfiltration. So,
7:20strip your payloads to the minimum. If
7:22the agent doesn't need a piece of data
7:24for its immediate task, then don't
7:26return it.
7:29And number five,
7:31minimize the blast radius. Scope
7:33permissions at the tool and resource
7:35level, not the session level. Use the
7:37MCP read-only annotation for
7:39non-destructive tools so that clients
7:41can enforce boundaries or if an MCP tool
7:45is intended to have read-only access,
7:47then consider turning it into an MCP
7:49resource.
7:50Also, remember that every tool you
7:51remove is an attack vector that you
7:53eliminate.
7:59And you're building an interface, not a
8:00tool. So, this is the mindset to go in
8:02with.
8:03An agent will use anything you provide
8:05it with confidence, so you have to
8:07provide that trust layer.
8:11So, now you've designed your server
8:13well. You followed the five principles.
8:15Now you need to actually deploy it and
8:17this is where most teams hit what I call
8:19the security cliff.
8:21If you're running MCP in standard IO
8:23mode, life is pretty comfortable. It's a
8:26local process, a single user, no no
8:29network exposure, no authentication
8:31needed. Your MCP host talks directly to
8:34the server process on your machine. It's
8:36a walled garden and it works beautifully
8:37for single-player developer
8:39productivity.
8:40But production requires something
8:42completely different.
8:44You need the streamable HTTP transport.
8:47Um this enables remote deployment,
8:49multiple clients connecting to the same
8:51server. You can horizontally scale and
8:54you can centralize your governance. And
8:55this is really where MCP becomes
8:57genuinely valuable to an organization
9:00where you go from one developer on one
9:02laptop to a shared capability that an
9:05entire team or entire fleet of agents
9:06can use.
9:08MCP becomes the single interface that
9:10all clients can use without having to
9:12worry about whether they're the latest
9:13version of an API or considering the
9:15resources needed to scale.
9:19The problem is there's no gradual
9:20on-ramp. You go from zero security
9:23surface to a huge list of concerns all
9:25at once. You're suddenly needing OAuth,
9:28token management, CORS configuration,
9:31TLS, rate limiting and more. And you
9:33need it all at once. So, there's no
9:35halfway house because you can't do a
9:37little bit of production. You're either
9:39behind the wall or you're standing out
9:41in the open.
9:44And you can't just stay local and hope
9:46for the best. Stack lock ran low tests
9:48on standard IO transport and the results
9:50were brutal. 20 out of 22 requests
9:52failed with just 20 simultaneous
9:54connections. Standard IO falls over the
9:57moment you add concurrency. So, if you
9:59want to scale out, you have to cross the
10:00chasm.
10:03And how do you start crossing that
10:04chasm? I'm going to hand it over to
10:06Jeremy to continue.
10:10Yes, so implementing an authorization
10:12server for MCP isn't that simple.
10:15Let's look at the list of RFCs to
10:17implement.
10:20With the core flow, the OAuth client
10:23discovery, and the metadata, and the
10:24management of the token life cycle, we
10:27already have more than 10 specifications
10:29to implement.
10:31Now, let's say we read all these RFCs,
10:34and I'm ready to implement an
10:35authorization [clears throat] server for
10:36MCP.
10:38What does the enterprise grade
10:39authorization look like?
10:44So, let's start by reviewing the local
10:46versus remote MCP server setups and
10:48their respective OAuth flows.
10:51Tune talked about the walled garden, the
10:54local MCP server
10:56running over standard IO with an API
10:58key.
10:59Let's look at the flow diagram.
11:01The MCP server runs on my machine.
11:03The client connects via standard IO.
11:06The user must set the key as a parameter
11:09in the MCP client config.
11:11And the parameter will be stored as an
11:13environment variable
11:15passed by the MCP server with its
11:17request to the external service.
11:24That might be good for local setups, but
11:27I need to provision, store, and maintain
11:30the key.
11:32This key is long-lived, is rarely
11:34rotated, and it isn't scoped to the
11:36specific actions that my client perform.
11:40Even worse, these keys are often shared
11:43across systems.
11:45So, the key is stored in a config file,
11:47an environment variable, and it isn't
11:49verified by the MCP server.
11:52Now, let's look at a remote MCP server.
11:56In this case, the MCP server runs on a
11:59remote server.
12:01The client connects via HTTP.
12:03The user must set the key in the HTTP
12:05authorization header. Again, we can see
12:08the MCP client config here on screen.
12:12So, phase one is the generation of the
12:14token and the configuration of a client.
12:19On step two, runtime,
12:21we can see the client performing a
12:22request, attaching this API key in the
12:24authorization header.
12:27This API key is validated or not by the
12:30MCP server itself, and will be passed
12:32through
12:33to the upstream API, where it will be
12:36this time verified. Whether the API key
12:38is validated or not, we'll get a 200
12:41response or 401 response, in which case
12:44the user will need to rotate the token
12:46manually.
12:48That's how a majority of remote MCP
12:50servers are configured today.
12:52The key is long-lived, it isn't scoped
12:54to the specific action of my agent,
12:56either. The key is stored in a config
12:59file, and it isn't always verified by
13:01the MCP server.
13:04Either the key is simply passed through
13:06to the API, creating a confused deputy
13:08vulnerability, where malicious clients
13:10obtain authorization without the proper
13:12user consent,
13:14or sometimes the key might be mapped to
13:16another key and token
13:18for the API access itself.
13:20Now, we have a single shared credential
13:23serving many users. That credential is
13:25even more powerful, harder to revoke per
13:27user, and if leaked, it compromises
13:30everyone.
13:32This approach works for long-lived,
13:34unscoped credential setups. It still
13:36represents more than 50% of the MCP
13:38servers out there.
13:40But what we see the ecosystem moving
13:42towards is short-lived, scoped tokens
13:45via OAuth 2.1.
13:47We even see token exchange for least
13:49privilege access.
13:51Traditional OAuth assumes you know your
13:53clients up front.
13:55You register them in a developer portal,
13:57you get a client ID, and you move on.
14:00This works when you have five to 10 apps
14:03connecting to your service.
14:05But with MCP,
14:07this flow breaks completely.
14:09Think about what MCP's architecture
14:11actually looks like.
14:13Any client, Cloud Desktop, Cursor, VS
14:16Code, a CLI tool, a random agent,
14:19can discover and connect to any MCP
14:21server at runtime.
14:25Pre-registration requires too much
14:27effort in a highly viable setting.
14:30It's an unbounded number of clients
14:32connected to an unbounded number of
14:34servers.
14:35You can't ask every developer to
14:37manually register their app with every
14:39MCP server they might ever want to talk
14:41to.
14:42So, that's where the dynamic client
14:44registration comes in.
14:47In this case, we still have an MCP
14:49server running on a remote server,
14:52but now it's protected by an OAuth
14:54authorization server.
14:57The client can self-register itself
14:59against the authorization server,
15:01and will get a new client ID on every
15:04registration.
15:06So, on phase one, the discovery,
15:09our MCP client, in this case Cursor,
15:12will perform a request on {slash} MCP
15:14against the MCP server.
15:16We can see the MCP server returning a
15:18401 response because we do not have a
15:20token to pass yet,
15:22but it also passes a WWW-Authenticate
15:25header containing the resource metadata
15:28that can be used by our client in order
15:31to discover the MCP server and its
15:33metadata.
15:36The document itself looks a bit like
15:38this.
15:39It describes the resource we're trying
15:41to access and the authorization server
15:43protecting it.
15:45This lets our client point at the
15:47authorization server itself and
15:48discover, this time, the metadata
15:51exposed by the authorization server
15:52itself.
15:55Now that our client knows how to
15:57authorize itself for an MCP server
15:59access, it needs to register itself
16:01against the authorization server.
16:04That is done via a request, a post
16:06request on {slash} register.
16:10As we mentioned earlier, the
16:11authorization server will generate and
16:13persist on disk a new client ID and
16:16return it to the client.
16:18Now, we know who we're talking to.
16:20Next, it's time to authorize our client
16:24against the authorization server.
16:26And for that, the MCP spec is mandating
16:29to use the PKCE, the proof key for code
16:32exchange protocol. So, our MCP client is
16:34first generating a code verifier and a
16:37code challenge
16:38that it does pass through a request to
16:41{slash} authorize
16:43in order to obtain
16:45an authorization code.
16:47Our authorization server will validate
16:50this request and the code challenge.
16:53And since we don't have a running
16:54session yet for this user, it will
16:56redirect the user to its identity
16:58provider, so that's your single sign-on
17:01form, in order for the user to log in.
17:04Upon successful log in, the user will be
17:06redirected to a consent page where they
17:08can grant different scopes
17:10to their client.
17:14Now that we issued a valid authorization
17:17code for client, it's time to use it in
17:20order to get a token, an access token.
17:23Does that by sending a request on
17:26{slash} token and passing the
17:28authorization code and the code verifier
17:30that we generated for the PKCE protocol
17:32earlier.
17:33Authorization server will validate
17:35the PKCE challenge and the authorization
17:38code,
17:40and it will then mint a brand new token.
17:42In this case, we're using JSON web
17:44tokens
17:45in order to return an access token for
17:48client to now use the MCP server.
17:52The final step is actually to
17:55use the MCP server.
17:58This is when your MCP client is going to
18:00perform a tool call, for example.
18:02We can see it will pass the access token
18:05we just issued
18:07in the authorization as the bearer
18:09value.
18:11Our MCP server will validate this token,
18:14check the valid scopes,
18:16and it will now
18:18perform the token exchange flow in order
18:20to change this delegation token
18:23for a session token.
18:25This means our MCP server now is
18:27actually a OAuth client
18:29for a new resource server,
18:32our API,
18:33but it's using the exact same
18:34authorization server in order to get a
18:36token.
18:38So, that is the token exchange flow that
18:39is defined in RFC 8693.
18:43And as we complete the flow,
18:45our MCP server can use this new session
18:47token in order to perform an API call,
18:50bypassing
18:51the token in the authorization header.
18:55So, DCR solves the self-registration,
18:58the dynamic registration of the client,
19:01so that our user doesn't have to go and
19:02pre-register, pre-generate uh static
19:05credentials, and set it on the client,
19:07but it does have its own problems.
19:09First, every time a user connects a
19:12client to an MCP server,
19:14a new registration is created.
19:17Registrations are not portable, so using
19:19Cloud on Windows and then on macOS
19:22creates two distinct client
19:23registration.
19:26DCR is vulnerable to phishing attacks
19:29because it doesn't provide a reliable
19:31way to verify client identities.
19:34Anyone can post to that endpoint, the
19:36{slash} register endpoint, including
19:38attackers.
19:40Finally, the server is just trusting
19:42whatever metadata the client
19:44self-asserts.
19:45It means a malicious client can claim to
19:47be Cloud,
19:49and the server has no way to know
19:50otherwise.
19:54So, the MCP community had to come up
19:56with a better way to let clients
19:58self-register.
20:00And that is CIMD, the client ID metadata
20:03document.
20:05Here in this case, we still have an
20:07authorization server in front of our MCP
20:10server,
20:11but the client owner exposes the client
20:13ID on a public URL.
20:16This will let our MCP server fetching
20:18the client ID during the authorization.
20:21Let's have a look at the diagram.
20:24So, phase one is still the discovery.
20:26Our client hits the MCP server without a
20:29token,
20:30gets a 401 response, and uh the resource
20:33metadata URL. It can follow this URL,
20:36discover the MCP server, and it will get
20:40to discover the authorization server.
20:42But this time, the authorization server
20:45isn't mentioning it needs a slash
20:47register request.
20:50It means the client, the MCP client, can
20:52go straight to the authorization phase.
20:56We generate again the PKCE code
20:57verifier, and we perform a slash
21:00authorize request. But this time, our
21:02client passes its unique ID, and we can
21:05see it here. It's actually a valid URL
21:08where the metadata for the client is
21:11being exposed.
21:14This lets our authorization server fetch
21:16this metadata,
21:18and register a new client with a unique
21:20ID
21:21that is a URL that is exposed by the
21:24client owner.
21:27And we can move to the authentication
21:29phase.
21:32Again, the authorization server will
21:33redirect to the identity provider, wait
21:36for a valid login on our user's side,
21:38present a consent screen
21:40uh for the user to grant some scopes,
21:43and
21:44we are ready to issue
21:46the delegation token and the session
21:48token
21:49for a token use by the MCP server.
21:53So here, CIMD has no growing database of
21:57client registration to maintain.
22:00Proving that you control
22:02https://cloud.ai
22:04is meaningful,
22:05unlike proving that you can post on the
22:07registration endpoint.
22:10The redirect URIs that are explicitly
22:12bound to the client in its metadata
22:14document are making it harder for
22:16attackers to sneak in malicious
22:17callbacks.
22:20And the authorization server can
22:21selectively allow or deny clients.
22:26So, in summary, DCR is a good start, but
22:29it does create problems.
22:32CIMD is a leap forward, and it is the
22:35preferred approach since November 2025.
22:40But becoming enterprise grade
22:42requires adding other layers of security
22:44and confidence.
22:46For permissions,
22:47OAuth scopes gets you part of the way
22:49there,
22:50but it's scoped to the session.
22:53True enterprise grade role-based access
22:54control means scoping permissions at the
22:57individual tool and resource level, not
22:59just the session.
23:03Data masking is how you deal with the
23:05PII fields such as email,
23:07phone, and national insurance numbers.
23:10They may need to be masked before the
23:12agent sees them, because agents should
23:14never be exposed to data that they have
23:17no business handling.
23:22You will need to log what's happening in
23:24each interaction.
23:25Which agent called which tool, with what
23:28parameters, and what data was returned.
23:31For compliance with regulations such as
23:33the EU AI Act, regulators will expect
23:36this level of transparency and detail
23:38for autonomous AI systems.
23:43Finally, you'll need to be able to
23:45observe the full request.
23:47This means the client request validation
23:50through the execution,
23:51data retrieval, and the generated
23:53response.
23:55If you cannot trace what an agent did
23:57end to end, you cannot govern it.
24:00Tracing for agent AI follows the same
24:03principles as distributed system
24:05observability, but applied to autonomous
24:07decision-making.
24:11Thanks very much, Jeremy, and thank you
24:13all for tuning in to this session. We'd
24:16love to know how your journey with
24:18productionizing MCP services is going,
24:20so please leave us a comment or send us
24:22a message. You know where to find us.
24:24And please do check out our MCP server
24:26and give us a star. So, hopefully we'll
24:28see you again soon. Thanks, and bye.
24:31Thank you.