Introducing Gram by Speakeasy. Gram is everything you
need to power integrations for Agents and LLMs. Easily create, curate and host
MCP servers for internal and external use. Build custom tools and remix
toolsets across 1p and 3p APIs. Get on the
waitlist today!
Why MCP is useful: An introduction to MCP for skeptics
You’re right to be skeptical about the Model Context Protocol (MCP) . From the outside, it looks like word salad, and it arrived on the heels of the hyped-up AI darlings known as “vibe coding” and “agentic”. If you’ve dared to poke around, you’re probably left with the feeling that it’s a Rube Goldberg machine, and everyone’s either in on the joke or dazzled by the complexity.
Developers love shiny complexity, but anyone who lived through the early internet’s immature, brittle protocols and torturous RPC standards knows to hit the brakes when YouTube talking heads sing a new protocol’s praises in unison.
This skepticism and care shouldn’t end at MCP, though. The entire generative AI ecosystem is rife with complexity and potential pitfalls. To quote the sagely Samuel Colvin (of Pydantic ):
From a software engineer’s point of view, you can think of LLMs as the worst database you’ve ever heard of, but worse. If LLMs weren’t so bloody useful, we’d never touch them.
Well, if MCP servers weren’t so bloody useful, we’d never touch them either!
We believe MCP will eventually enable powerful AI agent ecosystems, but we’re not here to evangelize. The protocol adds genuine complexity and operational overhead that isn’t justified for many use cases. Function calls or REST APIs are often the simpler, better choice.
But there are specific scenarios where MCP’s architecture genuinely shines - where its standardized discovery, stateful sessions, and cross-platform compatibility solve problems that are painful with alternatives. MCP servers like Desktop Commander and Playwright MCP demonstrate this: they turn any LLM into a powerful automation tool for desktop and web interactions that would require significant custom development otherwise.
The question isn’t whether MCP is inherently good or bad - it’s whether its benefits justify the complexity for your specific needs. Let’s start by acknowledging the legitimate criticisms, then examine exactly when MCP’s complexity pays off.
What people think about MCP
This has made a lot of people very angry and been widely regarded as a bad move.
Douglas Adams
Polarized doesn’t begin to describe the reaction to MCP on the internet. The extremes appear to fall into four clear camps:
The “everything about this sucks” naysayers. Here are a few highlights from our favorite orange site:
MCP is a kitchen sink of anti-patterns. There’s no way it’s not forgotten in a year, just like Langchain will be.
It’s a half-baked, rushed out, speculative attempt to capture developer mindshare and establish an ecosystem/moat early in a (perceived) market.
The deliberately obtuse. I won’t quote these commenters for being ignorant, but you know the people who say, “I can’t get my head around this, so it must be useless.” I believe some of this group’s resistance is driven by the viscerally negative reaction of the naysayers. This one includes the “Isn’t this just an OpenAPI spec?”, “Why not just use REST?”, and “I have yet to see one compelling use case” crowds.
The builders who use MCP to get things done now or help fix what’s broken in the spec. Think of David Cramer learning MCP while building the Sentry MCP server in public - or Samuel Colvin, Armin Ronacher, the developers from Cloudflare, LangChain, Vercel, and others enthusiastically (and in record time) helping to review updates to the MCP Specification . This includes the hackers (the best kind) and tinkerers who built more than 15,000 MCP servers over the past six to eight months.
Vendors and tooling companies. We’re in this group - we build a platform that generates MCP servers from existing APIs. This gives us a front-row seat to both MCP’s potential and its implementation challenges, which we’ll share honestly. We have a biased perspective, of course, but that perspective comes from solving real problems developers face with MCP implementation.
MCP’s naysayers and the deliberately obtuse
We get it. MCP is complex, and the spec is still evolving. But let’s address some common criticisms head-on. I know this first one looks like a straw man, but we’ve seen it more than once:
“But MCP is just another API wrapper”
Yes, it’s a wrapper.
But the underlying API can be just about anything: a database, a web service, a file system, or even a custom tool. MCP servers wrap these APIs in a way that makes them predictable from the LLM client’s perspective. The point is that client code is not tied to a specific API implementation.
The problem with calling it “just” another API wrapper is that you ignore the benefits of the protocol’s design:
Standardized communication: MCP defines a consistent way for clients to interact with servers, regardless of the underlying API. This means you can swap out servers without changing client code.
Dynamic tool discovery: When you connect Claude to a project management MCP server, it can discover available tools at runtime - “Oh, this workspace has custom fields for priority and sprint. I can filter by those.” The LLM adapts to what’s available rather than failing on hardcoded function calls.
Bidirectional communication: MCP servers can send messages back to the LLM client, allowing it to react to events in real time. For example, a database MCP server can notify the MCP client when a new record is added (via the MCP notifications/resources/list_changed message), allowing the LLM client to update context without needing to poll or re-query.
Session management across tools: An LLM helping with data analysis can maintain an authenticated session with your database, keep a Jupyter Notebook kernel running, and preserve Matplotlib figure states - all simultaneously. This benefit is most apparent when you consider an MCP server that wraps a web browser. The MCP server can maintain the state of the browser session, allowing the LLM to interact with web pages as if it were a human user.
If you broaden your definition of “API” beyond web services, a protocol like MCP becomes essential.
In short:
We need a protocol that provides a consistent way to interact with tools and services, regardless of their underlying implementation.
❌ The definition of API, as used by critics of MCP, is too narrow and limited to web services. APIs vary too widely to be pigeonholed into a single category.
✅ MCP, as a wrapper, provides clients with a standardized interface, dynamic tool discovery, bidirectional communication, and session management across tools.
”Why use MCP instead of REST, OpenAPI, or agents.json?”
REST excels at CRUD operations, and it saved us from the horrors of RPC, so why are we reinventing the wheel, or worse, driving in reverse?
The difference is that most of the time, agents are not doing CRUD operations. They often need to perform multistep actions on remote and local services combined, while maintaining state and context.
For example, the Playwright MCP server allows LLMs to interact with web pages as if they were human users. It enables complex interactions like filling out forms, clicking buttons, and navigating pages, all while maintaining context about the user’s session. Achieving this without a stateful protocol like MCP would drive anyone to madness. This is simply not something you can do with REST or OpenAPI.
We’ve seen the AGPL-licensed agents.json Specification mentioned as an alternative to MCP, but it doesn’t have nearly the same capabilities. For the same reasons that REST is not a good fit for complex interactions, agents.json falls short. agents.json only solves the problem of remote tool discovery and invocation, but it doesn’t provide the session management, dynamic tool discovery, or bidirectional communication that MCP does.
In short:
The problems LLMs solve often require stateful interactions with tools and services, not just simple CRUD operations.
REST, OpenAPI, and agents.json
REST, OpenAPI, and agents.json are stateless and unidirectional. The LLM client would need to manage state and rely on polling or re-querying to maintain context.
MCP
MCP is stateful, dynamic, and bidirectional. It allows LLMs to interact with tools and services in a way that maintains context and state across multiple interactions.
This is a common and completely valid question. Function calling is often much simpler to implement than MCP, but here’s where the architectural differences matter:
With function calls, all integration logic lives in the LLM client and extends both ways - to the LLM (with proprietary function call interfaces) and the external services. Want to add Slack integration? The LLM client needs Slack-specific authentication, error handling, rate limiting, and response parsing. Want database access? Add database drivers, connection pooling, and SQL validation to the LLM client. Every new integration bloats the LLM client with tool-specific code.
Yes, you could abstract the detail away with a library, but we’d still end up with custom implementations for each service, LLM, and client combination. This leads to a tangled mess of code that is hard to maintain, test, and scale.
With MCP, the MCP client speaks one protocol to any number of specialized servers. Adding Slack means deploying a Slack MCP server - no client changes required. It’s a plugin architecture that separates concerns: The MCP client handles conversation, MCP servers handle tool execution, and the protocol manages communication between them.
Function calls
All integration logic lives in the LLM client. Each new service requires client-side implementation.
MCP
The MCP client speaks one protocol to multiple specialized servers. New integrations require no client changes.
The architectural difference becomes clear when visualized:
Function calls: All integration logic lives in the LLM client. Each new service requires client-side implementation.
MCP: The MCP client speaks one protocol to multiple specialized servers. New integrations require no client changes.
Function calls
Each LLM provider has its own function call interface, leading to vendor lock-in and limited portability.
MCP
The MCP protocol is vendor-agnostic and portable. Any LLM client that supports MCP can connect to any MCP server.
This isn’t about capability - it’s about managing complexity at scale. Function calls hide the complexity until it’s too late. MCP acknowledges and contains it from the start.
Consider the classic “What’s the weather like in Paris?” example:
With function calling, the LLM client would need to handle each LLM provider’s function call interface, for example:
Using Anthropic’s Python SDK:
With OpenAI’s Python SDK:
Note the subtle differences in how each provider defines the function. The complexity of function calling grows with each new provider, and the LLM client becomes tightly coupled to specific implementations. This makes it hard to switch providers or reuse code across different LLMs.
If you’re only using one LLM provider in your application, and you don’t expect to add more, then function calls are a perfectly valid choice. But if you want to build a portable, reusable client that can work with any LLM provider, MCP is the way to go.
”MCP is just a bad, terrible, no-good excuse for a protocol”
Yes, we’ve seen the hot takes. MCP is “over-engineered,” “unnecessarily complex,” and “solving problems that don’t exist.” One particularly spicy commenter called it “a kitchen sink of anti-patterns” destined to be forgotten within a year.
Let’s address the elephant in the room: MCP is complex. It uses JSON-RPC 2.0 over stdio, SSE, or “Streamable HTTP”. It has its own transport negotiation, capability advertisement, and session management. If you squint, it looks like someone reinvented the Language Server Protocol (LSP) but made it worse.
Could MCP have used REST with webhooks? Sure, if they were all hosted publicly. Could it have been “HTTP” instead of the much-derided stdio? Absolutely. But stdio is so well-supported and easy to implement that it makes complete sense for local servers.
Another common complaint is the omission of WebSocket support. Yes, MCP doesn’t use WebSocket, but it does use Streamable HTTP - a new protocol that allows for streaming responses over HTTP while maintaining the request-response model. This is a design choice, not an oversight.
Using WebSocket brings its own complexities, and getting stuck on the “WebSocket vs HTTP” debate misses the point. MCP is about enabling dynamic, stateful interactions between LLMs and tools, not about picking your favorite transport layer.
We’re not saying MCP is perfect. The specification is under active development, the tooling is immature, and yes, the initial learning curve is steep. But dismissing it as “over-engineered” often comes from people who haven’t grappled with the actual problems it solves. Once the SDKs mature and best practices emerge, much of this complexity will be abstracted away - just like nobody complains about TCP’s three-way handshake anymore.
”The S in MCP is for security”
Ah yes, security - MCP’s favorite talking point. By now, you’ve probably seen the breathless blog posts about “Tool Poisoning Attacks” and “Full-Schema Poisoning” complete with scary diagrams and proof-of-concept exploits.
MCP servers are code you run on your machine. Shocking revelation - if you run malicious code, bad things happen. This isn’t a protocol vulnerability, it’s basic hygiene.
The discovered “vulnerabilities” essentially boil down to:
If you connect to a malicious MCP server, it can trick your LLM into doing bad things.
If an MCP server changes its behavior after you’ve approved it (a “rug pull”), your LLM might leak sensitive data.
If you paste untrusted data into tool descriptions, the LLM might follow those instructions.
In other news, if you install a malicious npm package, it can delete your files. If you pip install from a sketchy source, it might mine cryptocurrency. If you curl | bash from the internet… well, you get the idea.
Yes, MCP has unique attack vectors because LLMs process tool descriptions as instructions. That’s concerning and worth addressing. But the “MCP is fundamentally insecure” takes miss the forest for the trees. The real security model is the same as any code execution environment: Don’t run code you don’t trust.
The practical mitigations are straightforward:
Review MCP servers before connecting to them (just like you’d review any dependency).
Use signed and versioned MCP servers from trusted sources.
Run MCP servers in containers or sandboxed environments.
Implement proper access controls and least-privilege principles.
The MCP ecosystem is already moving in this direction. Docker’s MCP Toolkit runs servers in containers. The community is developing security scanners and best practices. The specification now includes security hints like readOnlyHint and destructiveHint. (I still have my reservations about the annotations, but that’s a topic for another day.)
Perfect security is the enemy of adoption, and MCP chose pragmatism over paranoia. The security concerns are real, but they’re solvable with the same approaches we use everywhere else in software: Trust but verify, defense in depth, and maybe don’t give your AI assistant access to your SSH keys without thinking it through first.
When to use MCP despite its complexity
MCP’s complexity is negligible with the right tools and SDKs. It is only a matter of time before the ecosystem matures enough that complexity becomes a non-issue entirely. But you may need to build tools for agents today, without the luxury of waiting for the ecosystem to catch up. So let’s look at some scenarios where MCP’s benefits outweigh the complexity today.
MCP is useful when you need dynamic tool discovery
If you expect tools to change frequently, or if you want to allow LLMs to discover tools at runtime, MCP’s dynamic tool discovery sets it apart from alternatives like function calls.
With MCP, the LLM client can query the MCP server for available tools at any time, and the MCP server can dynamically add or remove tools without requiring client changes.
With the TypeScript MCP SDK, notifying a client of tool changes is as simple as calling mcpServer.registerTool(), tool.enable(), or tool.disable().
Here’s an example of how you might implement dynamic tool discovery in an MCP server:
Implementing dynamic tool discovery with MCP is straightforward. The MCP server can add or remove tools at runtime, and the MCP client can discover these changes without needing to reconnect or reinitialize.
Doing the same with function calls would require a custom implementation that tracks available functions and updates the LLM client whenever they change. This is possible, but it adds significant complexity to both the LLM client and custom function codebases.
MCP is useful when you need stateful interactions
Many real-world AI interactions aren’t one-shot operations - they’re conversations that build on previous context. This is where MCP’s stateful architecture shines.
Consider a data analysis workflow. An analyst asks their AI assistant to:
Connect to the production database
Run exploratory queries to understand the schema
Identify anomalies in recent transactions
Generate visualizations of the findings
Create a report with recommendations
With function calling, each step is isolated. The LLM would need to keep all the results in its context window.
With MCP, the MCP server can maintain state across multiple tool calls, allowing for an asyncchronous, multi-step workflow that builds on previous interactions. The LLM can focus on reasoning and analysis, while the MCP server handles the underlying state management.
Here’s a concrete example with our WhatsApp MCP server. Imagine implementing a “conversation summary” feature:
This statefulness becomes even more powerful with tools like the Playwright MCP server, which maintains a browser session across interactions. The AI can navigate to a page, fill out a form, handle authentication, and scrape results - all while maintaining cookies, session state, and page context. Try doing that with stateless function calls!
MCP is useful when you need to support multiple LLM APIs
The fragmentation in LLM function calling is painfully subtle. As we showed earlier, each provider has its own format, quirks, and limitations. If you’re building tools that need to work across multiple LLMs - or if you just want to avoid vendor lock-in - MCP provides a standardized interface.
With MCP, you write your tool once as a server, and it works identically whether accessed from:
Claude Desktop
ChatGPT (with the upcoming MCP support)
Open source LLMs via frameworks that support MCP
Your custom application using any LLM provider
MCP is useful when you need bidirectional communication
Traditional function calling is a one-way street: the LLM calls a function and gets a response. But real-world systems often need to push updates to the AI, and this is where MCP’s bidirectional communication shines.
MCP servers can send notifications to clients about:
New data becoming available
System state changes
Authentication requirements
Progress updates for long-running operations
We included an example of this in the previous section, where the WhatsApp MCP server notifies the LLM client when a chat analysis is complete. The LLM client can then react to this notification, for example by including a message in the next LLM call.
This bidirectional flow enables truly reactive AI systems that respond to changing conditions, rather than just answering queries.
MCP comes with a pre-defined authorization model
The MCP specification includes OAuth 2.1 support. This allows you to implement secure, standardized authorization flows for your MCP servers.
The tooling is ready to use. The TypeScript MCP SDK provides a dead-simple ProxyOAuthServerProvider class for servers that need to implement OAuth 2.1 authorization. On the MCP client side, the SDK provides a OAuthClientProvider interface that handles the OAuth 2.1 flow for client developers.
If you need authorization for function calls, you’ll need to implement it yourself. This is often straightforward, but as we all know: Never roll your own authentication. Using the standardized OAuth 2.1 flow provided by MCP is a much safer bet.
MCP is useful when you need composable, reusable tools
One underappreciated benefit of MCP is how it encourages building composable, reusable tools. Because MCP servers are independent processes with standardized interfaces, they naturally become building blocks that different teams can share and combine.
We’re seeing this play out in the ecosystem:
Teams share MCP servers internally like libraries
Open source MCP servers are proliferating (we’ve lost count)
This composability means you can mix and match capabilities:
Each server is maintained independently, tested separately, and can be updated without affecting others. This modular approach is much cleaner than cramming all functionality into a monolithic set of function calls.
What MCP is: A brief introduction for the semi-interested developer
OK, so we’ve addressed why we think MCP is useful despite the backlash, but a common reaction is still, “I just don’t get it.”
Let’s make sense of this thing.
MCP stands for Model Context Protocol, but that doesn’t really explain what it does. It gives the impression that it’s a protocol for LLMs to understand context, but that isn’t it.
MCP is not about models or context
MCP has very little (to nothing) to do with LLMs and their context. In fact, a fully functional MCP implementation doesn’t require an LLM at all. To really understand MCP, first forget about AI, LLMs, and context.
So what should we think about instead?
MCP is about servers and clients
MCP is a protocol that connects a tool server and a client. The MCP server provides access to a dynamic list of tools, and the MCP client calls those tools to perform actions. The protocol defines how they communicate, what messages they send, and how they maintain state.
For the sake of simplicity, let’s focus only on tools, which are the most common use case for MCP. We’ll ignore some other parts of the spec, like resources, prompts, sampling, and roots. They’ll follow later.
If we were to rename MCP, we’d call it the Dynamic Tool Protocol. Alas, we’re stuck with MCP, so let’s roll with it.
The MCP server
An MCP server is a program that exposes a set of tools to clients. It can be anything from a simple script that runs on your local machine to a complex cloud service that integrates with multiple APIs. Even if it runs on your local machine, we’ll call it a server because it serves tools.
The MCP client
An MCP client is a program that connects to an MCP server and uses its tools.
The MCP transport
MCP uses a transport layer to send messages between the MCP client and server. The transport can be:
stdio: The MCP client and server communicate over standard input and output streams. This is the simplest transport and works well for local MCP servers.
Streamable HTTP: The MCP client and server communicate over HTTP with a streamable response. This is useful for remote servers, but it works just as well for local servers.
What the code looks like
Here’s a simple TypeScript server that implements a WhatsApp MCP server:
This sets up an MCP server that clients can connect to via stdio. The MCP server has a single tool, getWhatsAppChatById, which retrieves a WhatsApp chat by its ID. The input and output schemas are defined using Zod. This allows the MCP server to validate input and output data, ensuring that clients can only call the tool with the correct parameters and receive the expected response format.
We respond with a JSON object that contains the chat information, which the LLM client can then use. The MCP server can be extended with more tools as needed, for instance, to send messages or to list chats.
Our example excludes error handling, authentication, and other complexities for brevity, but a real-world implementation would include these aspects.
This may look like a lot of code, but it’s actually rather simple. The MCP server defines a set of tools, each with an input schema and an output schema. The LLM client can call these tools by sending messages to the MCP server, and the MCP server responds with the results.
Function calls would look similar and most likely simpler, but this MCP server is reusable across different clients and can be extended with more tools, without changing any client code. Adding new tools is as simple as registering them with the MCP server, and clients can discover available tools dynamically.
How MCP works under the hood
Let’s use our fictional WhatsApp MCP server as an example. We’re going to connect a client to this server and use its tools. Our server only has the tools capability.
When an MCP client connects to a server, it sends an initialize message to start the session.
The MCP server responds with a capabilities and serverInfo message that lists the available tools and their descriptions.
The MCP client and server have now established a session. They can exchange messages.
Usually, the MCP client then sends a tools/list message to get the list of available tools.
The MCP server responds with a tools/list/response message containing the available tools.
The MCP client can now call any of these tools by sending a tools/call message with the tool name and input parameters. (This example assumes the MCP server already has access to an authenticated WhatsApp API, so we’ll skip the authentication step for simplicity.)
The MCP server processes the request and responds with a tools/call/response message containing the result.
The LLM client can then process the response and display the list of chats to the user.
Things you don’t need to know about MCP
As we saw earlier, MCP is complex and has many moving parts. But you don’t need to know everything to build an MCP server. Here are some things that are outside the scope of this article and the Model Context Protocol (MCP) in general:
How the LLM client presents the tools to the LLM: The LLM client will most likely use the tool descriptions to generate a prompt for the LLM, but this is an implementation detail outside the scope of MCP. If you’re building a server, forget about the LLM and focus on the tools. The LLM client will handle the LLM integration.
How the LLM client handles tool calls: The LLM client will call the tools and pass the results to the LLM, but this is also an implementation detail. MCP doesn’t care how the LLM client processes the tool calls or how it integrates with the LLM.
How the LLM calls the tools: The LLM will call the tools using the MCP client, but this is again an implementation detail. Your server receives well-defined JSON-RPC messages, and you respond with well-defined JSON-RPC messages. You don’t need to parse LLM output at all. The MCP client handles that for you.
The exact details of the MCP protocol: The protocol is defined in the MCP specification , but you don’t need to know every detail to build a server. Even the messages above are overkill. The SDKs handle the protocol details for you, so you can focus on implementing your tools.
Showing the complex stuff
If you’re still with us, you might be wondering how to handle more complex scenarios. Let’s use our WhatsApp MCP server as an example and explore some of the more advanced features of MCP.
Dynamic tool discovery in MCP
Say your WhatsApp MCP server’s WhatsApp authentication expires, and you need to re-authenticate. We want to hide the getWhatsAppChatById tool until the user re-authenticates. MCP allows you to dynamically change the list of available tools at runtime.
When the MCP server detects that the authentication has expired, it can send a notifications/tools/list_changed message to notify the MCP client that the list of tools has changed. The MCP client can then call tools/list to get the updated list of tools.
MCP allows you to send a notifications/tools/list_changed message from the MCP server to the MCP client to notify it that the list of available tools has changed. This is useful for dynamic tool discovery, where the MCP server can add or remove tools at runtime.
The MCP client can then call tools/list to get the updated list of tools. This allows the MCP server to dynamically change its capabilities without requiring the MCP client to reconnect or reinitialize.
Here’s what that would look like in our WhatsApp MCP server:
That’s it! The TypeScript SDK handles the notifications/tools/list_changed message for you, so you don’t need to worry about the details. You just call disable() on the tool, and the LLM client will automatically update its list of available tools.
When the user re-authenticates, you can re-enable the tool by calling enable():
MCP resources
MCP resources are used to expose data that can be accessed by the MCP client. They are similar to tools, but they represent data rather than actions. Resources can be used to provide information about the MCP server, such as its capabilities, or to expose data from external APIs.
In our WhatsApp MCP server, we could expose all the image attachments that the MCP server downloads as resources. This way, the MCP client can access the images without having to call a tool every time. The MCP client can simply request the resource by its URL, and the MCP server will return the image data.
Here’s how we could implement a resource in our WhatsApp MCP server. We’d add the following code to our server:
This code defines a resource called media that can be accessed by the MCP client. The resource has a list method that returns a list of all media items in the WhatsApp server. The MCP client can then request a specific media item by its URI, and the MCP server will return the media data.
But how will the MCP client know about this resource? The MCP server sends a notifications/resources/list_changed message to notify the MCP client that the list of resources has changed. The MCP client can then call resources/list to get the updated list of resources.
Here’s what that would look like in our WhatsApp MCP server:
You can also send a notification when a specific resource has changed, like when a media item is updated or deleted:
How the LLM client handles resources is up to the LLM client implementation. We don’t need to worry about that. The MCP server just needs to expose the resources and notify the MCP client when they change.
MCP prompts
This one is sometimes ridiculed as “just a way to pass prompts to LLMs,” but MCP prompts are actually a powerful way to define reusable templates for prompts that the LLM client can then send to the LLM.
Just like with resources and tools, the MCP server exposes a list of prompts that the LLM client can use. Prompts are defined like this:
This defines a prompt called whatsapp_chat_summarizer that the LLM client can use to summarize WhatsApp chats. The prompt takes an argument chatName and generates a prompt text based on the chat data.
The LLM client presents a list of available prompts to the user, who can then select one to use. When the user selects a prompt with arguments, the LLM client should display a modal or form allowing the user to fill in the required arguments. Once the user submits the form, the MCP client sends a prompts/call message to the MCP server with the selected prompt and its arguments.
The MCP server adds the relevant context to the prompt (in our case, the WhatsApp chat data) and returns the prompt to the MCP client. The LLM client can then send the prompt to the LLM for processing.
This is especially useful for repetitive tasks where a user needs to combine tool call results with a complex prompt. If you can anticipate the user’s needs, you can define a prompt that combines the necessary context and tool calls into a single reusable template.
Completables: Autocomplete for MCP prompt and resource arguments
This is a more advanced MCP feature that has not been widely adopted yet. However, the TypeScript SDK supports it, so let’s briefly cover it.
When you register a prompt or resource that requires user input as arguments, you can register the arguments as completables. This allows the LLM client to provide suggestions for the arguments by querying the MCP server for available options. This is useful for prompts that require user input, such as selecting a chat or a file.
Here’s how you can register a completable for the whatsapp_chat_summarizer prompt we defined earlier:
After this change, the LLM client can now provide autocomplete suggestions for the chatName argument when the user selects the whatsapp_chat_summarizer prompt. The MCP client will call the MCP server with the partial input, and the MCP server will return a list of matching chat names.
We’ve only ever seen this work in the @modelcontextprotocol/inspector client, but it is likely that other clients will implement this feature as well.
Logging and debugging MCP servers
MCP servers can be complex, and debugging them can be challenging. The TypeScript SDK provides a built-in logging mechanism that allows you to log messages at different levels.
Call mcpServer.server.sendLoggingMessage to send a log message to the MCP client. The LLM client can then display the log messages in its UI or (most likely) write them to a file.
To log a message using the TypeScript SDK, you can use the following code:
Note that, unlike the earlier mcpServer methods, this method is a direct call to the MCP server, not a method on the mcpServer wrapper instance. This might be confusing - we’ve seen it send Claude 4 into a loop.
The future of MCP
It is still early days for MCP, but the community is rapidly building out the ecosystem. The specification is changing, and new features are being added to the SDKs. Here are some areas where we expect to see significant growth in Q3 of 2025:
Client-server feature parity: As the MCP Specification matures, we expect to see more features implemented in both the MCP server and MCP client SDKs. This will make it easier to build and use MCP servers, improving the overall developer experience.
Tool and resource discovery: The MCP Specification enables much more dynamic tool and resource discovery. We expect to see more servers implementing this feature, allowing clients to use fewer tokens when providing context to LLMs.
Security and access control: As MCP servers become more widely used, we expect to see more focus on security and access control. The specification already includes OAuth 2.1 support, but this addresses only a small part of the security model. Better guardrails against prompt injection and other attacks will be needed as the ecosystem grows. This is, unfortunately, a common theme in the LLM space, and perhaps one that can’t be solved completely. It isn’t unique to MCP, though.
Registries and marketplaces: We expect to see more MCP server registries and marketplaces where developers can share and discover MCP servers. This will make it easier to find and use existing servers, as well as to contribute to the ecosystem. A trusted registry would also help with security, as users could verify the authenticity of the MCP servers they connect to.
Whether MCP will become the de facto standard for LLM tool integration remains to be seen. It is useful now, and it is likely to become more useful in the near future. But who knows what this space will look like in a year or two? Your guess is as good as ours.