Dev.to · 6 min read

5 Underrated MCP Features That Make AI Clients Smarter

5 Underrated MCP Features That Make AI Clients Smarter

Everyone's building MCP servers. Most conversations focus on transports, authentication, OAuth, and exposing tools. Those topics are important, but while building a few MCP servers recently, I found myself appreciating a different part of the specification that doesn't seem to get nearly as much attention. The interesting thing is that these aren't hidden APIs. They're already part of the Model Context Protocol specification or its official extensions. None of the features below change what your tool does. They change how clearly a client can understand and interact with your server. 1. Tool Annotations This is the feature that inspired this post. MCP lets you describe a tool's behavior through annotations such as readOnlyHint, idempotentHint, destructiveHint, and openWorldHint. { "annotations": { "readOnlyHint": true, "idempotentHint": true, "destructiveHint": false, "openWorldHint": false } } Instead of only telling a client what a tool does, you're also telling it how it behaves. A few examples: readOnlyHint indicates that the tool does not modify its environment. idempotentHint indicates that repeatedly calling a write tool with the same arguments has no additional effect on its environment. destructiveHint distinguishes potentially destructive updates from additive ones. openWorldHint indicates whether the tool may interact with an open world of external entities. For example, a web search tool operates in an open world, while a tool that accesses a fixed local memory store operates in a closed domain. These may look like simple booleans, but they can give clients useful context when planning execution, presenting tools to users, or designing confirmation flows. There are a couple of details worth noting. idempotentHint and destructiveHint are meaningful only for tools that are not read-only. Also, destructiveHint: false means the tool performs only additive updates. Most importantly, these are hints, not security guarantees. Clients should not make security-sensitive decisions based on annotations from untrusted servers. Authentication, authorization, user consent, and deterministic safeguards still matter. 📖 MCP specification: Tools 2. Structured Tool Results Many MCP tools still return only text. Found 12 matching documents. That works perfectly for humans. Clients often benefit from receiving the underlying data in a structured form too. MCP tools can return a JSON value through structuredContent. { "structuredContent": { "documents": [ { "title": "...", "url": "...", "lastModified": "..." } ] } } The value does not have to be an object. It can be any valid JSON value, including an array, string, number, boolean, or null. Structured data can make it easier for clients to validate results, render interfaces, pass data into another workflow, or let models work with individual fields without parsing a natural-language response. For backward compatibility, the specification recommends also returning the serialized JSON in a regular text content block. One terminology distinction is useful here: MCP structuredContent is a structured tool result. It is not the same thing as LLM structured output or schema-constrained model generation. 📖 MCP specification: Structured Content 3. Output Schemas If you're returning structured data, tell clients what it looks like. When a client discovers tools through tools/list, it receives each tool's inputSchema. A tool can also expose an outputSchema. { "name": "search_documents", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" } }, "required": ["query"] }, "outputSchema": { "type": "object", "properties": { "documents": { "type": "array" } }, "required": ["documents"] } } This means the client can inspect the expected shape of structuredContent before invoking the tool. If an output schema is defined, the server must return structured content that conforms to it. Clients are encouraged to validate the result. This can support: Response validation Typed integrations More predictable client-side handling Richer interfaces Clearer tool documentation I like thinking of it this way: Structured content tells clients what you returned. An output schema tells clients what to expect before you return it. The two features complement each other nicely. 📖 MCP specification: Output Schema 4. Progress Reporting Not every operation finishes in a second. Think about indexing a repository, processing thousands of files, or running a large AI workflow. Without progress reporting, users may be left staring at a loading spinner with no indication that work is continuing. MCP supports optional progress notifications for long-running requests. When a client wants progress updates, it includes a unique progressToken in the request metadata. { "_meta": { "progressToken": "index-repository-123" } } The server may then send notifications/progress messages associated with that token. { "method": "notifications/progress", "params": { "progressToken": "index-repository-123", "progress": 42, "total": 100, "message": "Indexing repository" } } The server is not required to send progress notifications, even when the client supplies a token. If it does send them, the progress value must increase with each notification. The total is optional when the amount of work is unknown. Sometimes good UX isn't about making things faster. It's about making the waiting less mysterious. 📖 MCP specification: Progress 5. Tasks Some operations should not keep a request open until the work is finished. MCP Tasks provide asynchronous execution for long-running operations such as CI pipelines, batch processing, external jobs, approval workflows, or model training. Tasks are an official MCP extension rather than part of the core protocol. Both the client and server must declare support for the extension. When a server decides that a supported request will be long-running, it can return a durable task handle instead of the final result. The client can then: Receive a task identifier Poll the task using tasks/get Resume polling after reconnecting Provide requested input through tasks/update Request cancellation through tasks/cancel Retrieve the final result when the task completes Tasks can also expose states such as working, input_required, completed, failed, and cancelled. Polling is the default mechanism. Servers may additionally provide task notifications when supported. This model is particularly useful when the underlying operation may outlive a network connection or pause while waiting for human input. 📖 MCP Tasks extension Final Thoughts It's easy to think of MCP as a protocol for exposing tools. I think it's more than that. It's also a protocol for helping clients understand and operate those tools. A client that knows whether a tool modifies its environment, whether repeated calls have additional effects, what output shape to expect, how to consume structured data, and how to track longer-running work has much more context than one that receives only a name and description. That's the part I find fascinating. The protocol isn't just describing capabilities. It's also describing behavior and execution patterns. Some of these features take only a small amount of work to implement. Others, such as Tasks, require deeper support from both the client and server. Still, they are worth understanding because they can lead to more predictable integrations and a better experience for users. If you're building an MCP server today, it is worth spending a little extra time looking beyond tool names, descriptions, and input schemas. Your clients may understand your server better, and your users may get a more transparent and reliable experience because of it. I'd love to hear what other parts of MCP you think deserve more attention.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News