-
Notifications
You must be signed in to change notification settings - Fork 754
How to return csv format for some tools #971
Replies: 1 comment · 10 replies
|
I would suggest returning as string and doing the conversion in your tool. With the current spec, clients have poor to no support for non-json structured output. It's perfectly valid to use the "old" string-based content mechanism. It is not deprecated or legacy tool format. It gives you full control of what is sent to the model. For a csv returning tool I think it's the right choice. |
All reactions
Your response just shows that you are in fact the one who didn't understand the problem at all and completely lack the broader view. You look at this backwards, from the agent point of view. I am looking at it from the MCP server point of view. If you make a public high-traffic MCP (server), memory usage, allocations and garbace collection is a problem as it uses more resources and more hardware to achive the same performance and throughput due to the way GC works in these scenarios.
MCP already uses Want Markdown response? Just could just add or the current Since markdown is first the Accept header MCP content engotation coulds/should prefer to try to return in as that (in case of success) or as json (that also be signaled as accepted) in case of failure. MCP clients could then take the prefered format, while the server can serve these in the most efficient way. What works in ones private toy MCPs with 100 requests a days, do not work at high-traffic public MCPs w/ paying the price of bad scaling. But for this to work there needs to be a place/hook where to put custom formaters/serializers in that take the Tool/Resource and formats it in a way that the client requested it. There is absolutely no need for changes in the protocol. Everything already there. Only missing thing is support for ASP.NET Core styled media formatters. With it, additional formats can be added easily and determined via content negotation and also support for IAsyncEnumerable streaming, Json, Markdown, TOON, whatever you can imagine with it. For existing MCP clients, nothing changes. They send |
All reactions
|
The tool result is in the payload not the entire message envelope. You are conflating the MCP JSON-RPC response and the tool call result. They are not the same. The content-type is a transport-level thing - it is related to how MCP over HTTP happens. It does not relate to MCP as a protocol. There is a very big difference between adding content-type negotiation to tool results and what you're outlining which is using something other than JSON-RPC for the actual transport. And I am well-aware of the challenges in writing highly scalable and performant web services. But the tool result is content for the model. It is not a protocol or transport level machinery. This is a tool response: The result goes into content. The result is the JSON-RPC result not the tool result. Here is an example of structured data: If you want to send a 100 rows back in a wire-efficient manner then just put them in text content as CSV and tell the model in the description that this is a CSV. Or use structured content with arrays per column. Models are perfectly capable of transforming that from file. I recommend you take this discussion and ask an AI to explain why You can already send whatever you want in the tool result. And if you send a structured result, then the client host can decide the format it wants. It is not up to the server to decide how client hosts pass on structured data to the model. I have not heard any client host developers who need the ability to request other formats - likely because it serves no purpose the way model APIs look to day and if it did it is just a simple transformation. There is a case to be made for a content-negotiation / mime-type field for tool result. If you're sending large amounts of data, json is not the most efficient format. I would however note that models don't actually necessarily receive structured content as-is. If they're using code-mode they have the option to work with the return data in the code execution environment before it enters context - and often do so. An alternative technique you can use right now is this: But not all clients support this. Either way - you can create perfectly scalable and performant MCP servers with the current tool result shape. If a client wants to convert a response to TOON then this is trivial. The server shouldn't need to care. |
All reactions
There's nothing in the SSE specs that dictates it to be
You're speaking of current implementation. If the discussion would have been about the current implentation, the question wouldn't arise in the first place.
You still do not seem to understand the actual problem. Its not about whether its possible to return a plain text, markdown or TOON formated string, its about doing it efficiently (and on the prefererd way of the agent, Markdown/TOON vs json). Right now, you have to convert your output to a string before returning it. You can't return a
If you'd have a media formatter support similar to ASP.NET Core, you could just return
Assume you have these 100 rows, with 1 kb per row. That'd be maybe 200-300 kb in memory (don't forget, strings take 2 bytes per character plus referneces to the objects on the heap (strings, arrays, other complex types, method table pointers etc.). Since you can't stream it, you gotta fetch all 100 of them as objects. Next you need to convert them to a string. Lets assume serialized as CSV string, you need to conver it to continues string, so everything turns into string. That gives you maybe 30% overhead over the raw data, so 1.3 KB per row or 2.6 KB per row in utf16. for 100 rows, thats 260kb. Now you already had over 500-550 kb allocations only for the for that data + serialized string. Not counting what all the other allcoations in the request were (EF Core DbContext, the db connection itself etc.). And take that 2000 requests per second and you're at 1 GB allocations per second, the GC gonna run amok trying to clean up. And its the expensive kind of GC, since 200k string (>85kb) will directly land in Gen 3, the largest and slowest one to clean up. On other side, when you can use Huge difference.
I recommend you do that too and learn something about memory allcoations and how garbage collection in .NET works and how it effects performance.
Technically yes. But I never said you can't do that. I said its hacky and unefficient and has pad performance when you scale it.
Cause most don't care about performance and token costs, they don't have to pay for it, the one who runs the final product is the one paying. Plus there are not many options in that sight yet, hence why its important to add that functionality then people will also use it. Unnecessary tokens cost money. An MCP Server needs to be flexible in the way it returns the data. Some clients may want json, because its the only one they need or understand. Others may want TOON, CSV or Markdown. Especially Markdown is important here, since a lot of data comes from one form of database or another. Its simliar to how Cloudflare offers Markdown results for agents asking for it (see Introducing Markdown for Agents). Before they added this, there weren't many ways for arbitrary web retrieval in Markdown. You had to retieve Html and strip it yourself, for the better or worse. Now, you just need to set the markdown Accept header and it will retrieve you a markdown friendly version of it. Bonus of it: You get less traffic, faster response (due to caching if someone else retrieved this page before) and less resource usage (parcing the html, processing its dom tree and producing markdown). Plus, Cloudflare also returns the tokens count that provides you with information how many tokes are there so you can directly use it instead of tokenizing the input yourself. A win for everyone. CloudFlare becomes more relevant and future proof for agentic era, less traffic for the webhost behind it, less work for the agent and agent friendly return. You have to think broader.
Not everything is code. My use case is document and data retrieval, so the outputs tend to be larger and allocations matter a lot. But allocations and GC is just one side of the story. Like I said, the other one is token usage. Both affect pricing, one for the MCP server hoster, the other for the consumer.
My understanding is that resources are either for static content (such as .editorconfig tempaltes an agent can use instead of generating new one each time requested to make a new project or for storing "Skills" under a unique uri) or dynamic content with static uri (i.e. return a database/tables current schema). It do not seem suitable to return larger dynamic per user/request queries (results from search machines, document databases or knowledge sources) results at scale. Imho using that would be even "hackier" approach than deserializing to string and returning this, as it would not only require memory but also infrastructure complexity: |
All reactions
|
I don't have time right to answer your full post, but I will do one quick note which is that the embedded resources do not link to a resource on the server (even though it passes a URI which is a little odd, but can be thought of as an identifier rather than a stable URI than be re-read) - and the actual contents are embedded. So you can have a tool return a csv resource embedded in the response. I will respond to the rest tomorrow - because I do agree that returning very large datasets with tools can be clunky and awkward, but I do not agree with your positions on server vs. client host vs protocol vs transport responsibilities. I will try to lay it out more cleanly because you are arguing in good faith, but I still see some misconceptions about how the protocol works, and I believe that is where our disagreements are rooted. And I will still claim that you can achieve your goal in a performant manner with the current C# MCP SDK in a way that is performant both on the server side and in terms of token usage on the host side. But I'll make it up with some substance tomorrow. Thank you for not focusing on the substance of the discussion, even if we disagree. Let us try to not escalate this into something unnecessarily heated, and I think that goes for both of us. |
All reactions
|
First off - if you have tool results that are large enough that memory allocation becomes an issue - then the harness will very likely persist the tool result to a file and alias it. The model will then read the file using appropriate tools. If it is running in code mode it doesn't need to do this, but then it is imperative that the host knows the format of data, and that requires JSON. Is JSON a great wire format for large amounts of data? Not really. So that is where I see your point. But the solution to this cannot be where you point at. The JSON envelope for an MCP request or response is not the same as the JSON envelope for the tool result. The accept header is a transport-artifact, and it applies to a different layer than the one where you want to choose the format. Changing that is fundamentally incompatible with how MCP is designed at the protocol level. Now - if you did want to allow serialization to an arbitrary format it would require several things:
So it is not as simple as just adding an accept content header at the transport level. It won't work. And it is very important to distinguish between the protocol format (and remember MCP is a protocol transported over HTTP - it is not a standard for how to do tool calls over HTTP) and the result format. Results can currently be text or json. In regards to IAsyncEnumerable - that won't work because MCP tool calls are synchronous and so are model tool calls. You can use the Task feature to have long-running calls that produce data over time, but both at the protocol and model API level there are some pretty hefty obstacles to streaming large amounts of data. It is also the case that tools are just not very suitable to transmitting large amounts of data. If an agent needs a lot of data it is arguably better to use tools to transmit the data over other channels and stream it to disk. So there are many reasons why it isn't possible to just add a media encoder and wire up accept content flags. It's not possible to do so in an MCP spec compliant way. You can't just change at an SDK level what isn't possible at a protocol level. But again sending large amounts of data over MCP is possible right now - just put it into the text field. And yes that does cause an allocation. But if you need to send enormous amount of data in tool results, then I'd argue something else is wrong - it's just not a great way to design tools. You can use pagination, queries, and many other techniques. |
Uh oh!
There was an error while loading. Please reload this page.
Pre-submission Checklist
Question Category
Your Question
When the tool return complex type, by default it return in json format, how to return the data in csv format? Before i make each converter on each tool or is there centralised place to it like CallToolHandler?
All reactions