{
    "componentChunkName": "component---src-templates-blog-tsx",
    "path": "/blog/dalsoft-restclient-5-1-mcp-and-typed-clients/",
    "result": {"data":{"mdx":{"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"DalSoft.RestClient 5.1 - Call MCP Servers Like Any Other API, and Proper Typed Clients\",\n  \"date\": \"2026-08-21T00:00:00.000Z\",\n  \"image\": \"./image.jpg\",\n  \"banner\": \"./banner.jpg\",\n  \"description\": \"RestClient 5.1 adds an MCP (Model Context Protocol) handler so you can call MCP servers with the same fluent syntax you use for REST API's. Version 5.1 also adds AddRestClient<TClient>() for typed clients.\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient\"\n  }, \"DalSoft.RestClient\"), \" 5.1 is a feature release with two headlines: a handler that lets you talk to \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://modelcontextprotocol.io/\"\n  }, \"MCP (Model Context Protocol)\"), \" servers as if they were REST APIs, and first class typed clients via \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddRestClient<TClient>()\"), \".\"), mdx(\"h3\", null, \"MCP in one line\"), mdx(\"p\", null, \"If you've been following the AI tooling space you'll know MCP is the protocol LLM agents use to call tools. More and more services are exposing an MCP endpoint alongside (or instead of) a REST API - and from a .NET app there hasn't been a lightweight way to just \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"call an endpoint\"), \".\"), mdx(\"p\", null, \"Now there is. All you need to do is point a DalSoft.RestClient at the MCP endpoint, add the handler, and call tools:\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"IRestClient mcp = new RestClient(\\\"https://gateway.mcpservers.org/yahoo-finance/mcp\\\", new Config().UseMcpHandler());\\n\\nvar tools = await mcp.ListTools();\\nConsole.WriteLine(tools.tools[0].name); // get_quote\\n\\nvar quotes = await mcp.CallToolJson(\\\"get_quote\\\", new { symbols = new[] { \\\"MSFT\\\", \\\"AAPL\\\" } });\\nConsole.WriteLine($\\\"{quotes[0].shortName} {quotes[0].regularMarketPrice}\\\"); // Microsoft Corporation 481.5\"), \"\\n        \"), mdx(\"p\", null, \"That's a real, public MCP server - it's what our integration tests run against.\"), mdx(\"p\", null, \"Underneath, MCP is JSON-RPC over the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://modelcontextprotocol.io/specification/2025-06-18/basic/transports\"\n  }, \"Streamable HTTP transport\"), \", and there is a surprising amount of ceremony involved: an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"initialize\"), \" handshake, a session id header, a protocol version header, responses that might arrive as plain JSON \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"or\"), \" as a Server-Sent Events stream, and a JSON-RPC envelope around everything. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"McpHandler\"), \" does all of it:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Wraps whatever you post in the JSON-RPC envelope (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"jsonrpc\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"id\"), \").\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Initializes the session lazily on your first call, then tracks \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Mcp-Session-Id\"), \" and the negotiated \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"MCP-Protocol-Version\"), \" on every request - and transparently re-initializes if the server expires the session.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Reads SSE response streams until your response arrives, surfacing any \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"notifications/progress\"), \" the server sends along the way via \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"OnNotification\"), \".\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Unwraps the envelope so the response \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"is\"), \" the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"result\"), \", and throws \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"McpException\"), \" for JSON-RPC errors.\")), mdx(\"p\", null, \"It's a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DelegatingHandler\"), \" like every other handler in the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://restclient.dalsoft.io/docs/about-the-handler-pipeline/\"\n  }, \"pipeline\"), \", so authentication, retries, logging, IHttpClientFactory and unit testing with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UseUnitTestHandler()\"), \" all work exactly as they do for everything else in DalSoft.RestClient.\"), mdx(\"h3\", null, \"Tool results aren't JSON (but they usually are)\"), mdx(\"p\", null, \"One thing that surprised us building this: MCP tool results aren't data. They're \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"content blocks\"), \" for an LLM - \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"image\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"audio\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"resource\"), \" - because an agent could be handed any media. So a finance tool that returns a quote may give you a JSON \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"string\"), \" inside a text block, and you'd be writing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"result.content[0].text\"), \" and parsing it yourself.\"), mdx(\"p\", null, \"If the MCP endpoint's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"result.content[0].text\"), \" is returning JSON, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CallToolJson()\"), \" has got your back - dynamically typed like every other DalSoft.RestClient response, or strongly typed with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CallToolJson<T>()\"), \" - and if the server supports the spec's newer \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"structuredContent\"), \" it uses that instead. Crucially it throws \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"McpException\"), \" when the tool returns \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isError\"), \", because a tool execution error is a \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"successful\"), \" JSON-RPC response in MCP and it's far too easy to mistake one for data.\"), mdx(\"p\", null, \"The plain \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CallTool()\"), \" still gives you the raw content blocks when that's what you want.\"), mdx(\"h3\", null, \"Why not the official SDK?\"), mdx(\"p\", null, \"Fair question - there's an official \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.nuget.org/packages/ModelContextProtocol\"\n  }, \"ModelContextProtocol\"), \" package from Microsoft and Anthropic, plus DotnetFastMCP and MCPSharp. If you're building an agent - wiring tools into an LLM loop, implementing a server, handling sampling or elicitation - use the official SDK. It's the reference implementation and it has full protocol coverage.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"McpHandler\"), \" is for a different job: you just need to call an MCP server like any other API. Nothing new to learn, no new dependencies, automatically handled types or dynamic (if that's your thing), plain HttpClient underneath, works on .NET Standard 2.0 and .NET Framework - and testable the way you already test. There's a fuller comparison in the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://restclient.dalsoft.io/docs/mcphandler/\"\n  }, \"docs\"), \".\"), mdx(\"h3\", null, \"Typed clients done properly\"), mdx(\"p\", null, \"The second headline fixes something that has bugged us for a while. The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://restclient.dalsoft.io/docs/static-typing/\"\n  }, \"static typing\"), \" docs used to recommend this for a typed client:\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"public GitHubClient(HttpClient httpClient)\\n{\\n    _restClient = new RestClient(\\n        new HttpClientWrapper(httpClient, new Headers(new { UserAgent = \\\"MyClient\\\" })),\\n        \\\"https://api.github.com\\\");\\n}\"), \"\\n        \"), mdx(\"p\", null, \"Ugly, and not obvious. 5.1 adds \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddRestClient<TClient>()\"), \", the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddHttpClient<TClient>()\"), \" pattern you already know, and your typed client just takes an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"IRestClient\"), \":\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"builder.Services\\n    .AddRestClient<GitHubClient>(\\\"https://api.github.com\\\", new Headers(new { UserAgent = \\\"MyClient\\\" }))\\n    .UseRetryHandler(); // Any Use*Handler applies to this client only\\n\\npublic class GitHubClient\\n{\\n    private readonly IRestClient _restClient;\\n\\n    public GitHubClient(IRestClient restClient) => _restClient = restClient;\\n\\n    public Task<List<Repository>> GetRepositories(string user) =>\\n        _restClient.Resource($\\\"users/{user}/repos\\\").Get<List<Repository>>();\\n}\"), \"\\n        \"), mdx(\"p\", null, \"Typed clients are transient (like \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddHttpClient<TClient>()\"), \"), the underlying HttpClient is pooled by IHttpClientFactory, and your constructor can take other dependencies alongside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"IRestClient\"), \". \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddRestClient<TClient, TImplementation>()\"), \" registers an interface with its implementation.\"), mdx(\"p\", null, \"Taking \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"IRestClient\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RestClient\"), \" keeps your client testable, and because \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Resource()\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Get<T>()\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Headers()\"), \" and friends are all on the interface you lose nothing. And yes, the two features combine - register a typed client for an MCP server with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AddRestClient<WeatherMcpClient>(url).UseMcpHandler()\"), \" and you've got an SDK for it.\"), mdx(\"h3\", null, \"Documentation\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://restclient.dalsoft.io/docs/mcphandler/\"\n  }, \"https://restclient.dalsoft.io/docs/mcphandler/\"), \" and \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://restclient.dalsoft.io/docs/static-typing/\"\n  }, \"https://restclient.dalsoft.io/docs/static-typing/\")), mdx(\"h3\", null, \"Get it\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"bash\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"> dotnet add package DalSoft.RestClient\"), \"\\n        \"), mdx(\"p\", null, \"The full notes are in the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient\"\n  }, \"readme\"), \". If you hit anything we missed, \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient/issues\"\n  }, \"raise an issue\"), \" - and if RestClient is useful to you or your company, please consider \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/sponsors/dalsoft\"\n  }, \"becoming a sponsor\"), \" \\u2764\\uFE0F\"));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"DalSoft.RestClient 5.1 - Call MCP Servers Like Any Other API, and Proper Typed Clients","date":"21 August 2026","description":"RestClient 5.1 adds an MCP (Model Context Protocol) handler so you can call MCP servers with the same fluent syntax you use for REST API's. Version 5.1 also adds AddRestClient<TClient>() for typed clients.","banner":{"publicURL":"/static/b1341182365830f0d708af622f4346e5/banner.jpg","childImageSharp":{"fluid":{"srcSet":"/static/b1341182365830f0d708af622f4346e5/d34c9/banner.jpg 480w,\n/static/b1341182365830f0d708af622f4346e5/f7c79/banner.jpg 960w,\n/static/b1341182365830f0d708af622f4346e5/07f12/banner.jpg 1536w","base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAEDBf/EABYBAQEBAAAAAAAAAAAAAAAAAAIBA//aAAwDAQACEAMQAAABx5NRSDf/xAAXEAADAQAAAAAAAAAAAAAAAAAAAREQ/9oACAEBAAEFAk4XP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/AUn/xAAXEAADAQAAAAAAAAAAAAAAAAAAQZEQ/9oACAEBAAY/AlBTP//EABkQAQEAAwEAAAAAAAAAAAAAAAEAESGR8f/aAAgBAQABPyFBg6i/CTd//9oADAMBAAIAAwAAABAEH//EABYRAAMAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxClf//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAECAQE/EAn/xAAaEAEAAwADAAAAAAAAAAAAAAABABExIWHB/9oACAEBAAE/EBKYMLuKEpnQeTmT/9k=","aspectRatio":2.4,"src":"/static/b1341182365830f0d708af622f4346e5/07f12/banner.jpg","sizes":"(max-width: 1536px) 100vw, 1536px"},"id":"3315091b-e6a3-5fe9-9af9-1b50298288af"}}}}},"pageContext":{"slug":"/blog/dalsoft-restclient-5-1-mcp-and-typed-clients/"}},
    "staticQueryHashes": ["1139857438","1946588481","2083862410","2213455283","2418326273","2840686334","3067102388"]}