{"id":30114272,"url":"https://github.com/cicatriiz/csharp-utcp","last_synced_at":"2026-03-13T04:31:29.540Z","repository":{"id":306144033,"uuid":"1023775564","full_name":"Cicatriiz/csharp-utcp","owner":"Cicatriiz","description":"A high-performance, native C# implementation of the Universal Tool Calling Protocol - enabling AI agents to discover and communicate directly with tools without wrappers or middleware.","archived":false,"fork":false,"pushed_at":"2025-09-15T19:53:43.000Z","size":323,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-15T21:30:29.425Z","etag":null,"topics":["ai","ai-agent","ai-agent-tools","artificial-intelligence","claude","csharp","developer-tools","implementation","llm","mcp","model-context-protocol","open-source","universal-tooling","utcp"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cicatriiz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-21T17:13:18.000Z","updated_at":"2025-09-01T11:40:53.000Z","dependencies_parsed_at":"2025-07-23T23:14:35.065Z","dependency_job_id":"e6965af3-0a47-4423-a4d5-f53316a85b72","html_url":"https://github.com/Cicatriiz/csharp-utcp","commit_stats":null,"previous_names":["cicatriiz/csharp-utcp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Cicatriiz/csharp-utcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cicatriiz%2Fcsharp-utcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cicatriiz%2Fcsharp-utcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cicatriiz%2Fcsharp-utcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cicatriiz%2Fcsharp-utcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cicatriiz","download_url":"https://codeload.github.com/Cicatriiz/csharp-utcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cicatriiz%2Fcsharp-utcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30457998,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T03:55:51.346Z","status":"ssl_error","status_checked_at":"2026-03-13T03:55:33.055Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agent","ai-agent-tools","artificial-intelligence","claude","csharp","developer-tools","implementation","llm","mcp","model-context-protocol","open-source","universal-tooling","utcp"],"created_at":"2025-08-10T07:39:32.152Z","updated_at":"2026-03-13T04:31:29.532Z","avatar_url":"https://github.com/Cicatriiz.png","language":"C#","readme":"## Universal Tool Calling Protocol (UTCP) 1.0.1 for .NET\n\nIdiomatic .NET SDK for the Universal Tool Calling Protocol (UTCP), mirroring the Python reference implementation.\n\n### What is UTCP?\nUTCP (Universal Tool Calling Protocol) is an open, language-agnostic specification for discovering and invoking tools (functions, APIs, and services) in a consistent way across transports such as HTTP, MCP, CLI, text files, and sockets. \n\nIn short, UTCP provides a standardized way for AI systems and other clients to discover and call tools from different providers, regardless of the underlying protocol used (HTTP, WebSocket, CLI, etc.). This specification defines:\n\n- Tool discovery mechanisms\n- Tool call formats\n- Provider configuration\n- Authentication methods\n- Response handling\n\nIn practice, UTCP lets you register tools from many sources, search them, call them synchronously or as streams, and compose them reliably from any runtime that implements the spec.\n\nSee the official [UTCP specification](https://github.com/universal-tool-calling-protocol/utcp-specification) for full details. \n\n### Features\n- Core models and client with async APIs and CancellationToken\n- Pluggable communication protocols: HTTP (JSON + SSE), CLI, Text, MCP; Socket and GraphQL scaffolds\n- OpenAPI converter to UTCP tools\n- In-memory concurrent tool repository and search strategy\n- Variable substitution with namespacing and env support\n- Post-processing (FilterDict)\n- System.Text.Json with source-gen and polymorphism\n- Logging via Microsoft.Extensions.Logging\n\n### Repository layout\n- `src/Utcp.Core` – core types, client, search, substitution, post-processing, registry\n- `src/Utcp.Http` – HTTP protocol (including SSE) and OpenAPI converter\n- `src/Utcp.Cli` – CLI protocol (process runner)\n- `src/Utcp.Text` – Text protocol (file read and streaming)\n- `src/Utcp.Mcp` – MCP protocol (HTTP/stdio, OAuth2, SSE, result shaping)\n- `src/Utcp.Socket` – TCP/UDP scaffolds\n- `src/Utcp.Gql` – GraphQL scaffold\n- `tests/*` – xUnit tests with FluentAssertions\n- `samples/*` – client and server samples\n\n### Install / Build\n```bash\ndotnet build\ndotnet test\n```\n\n### Quick start\nRegister a manual and call a tool:\n```csharp\nusing Utcp.Core;\nusing Utcp.Core.Models;\nusing Utcp.Http;\n\nvar client = await UtcpClientImplementation.CreateAsync(config: new UtcpClientConfig\n{\n    ToolRepository = new Utcp.Core.Repositories.InMemToolRepository(),\n    ToolSearchStrategy = new Utcp.Core.Search.TagAndDescriptionWordMatchStrategy(),\n    ManualCallTemplates = new []\n    {\n        (CallTemplate)new HttpCallTemplate\n        {\n            CallTemplateType = \"http\",\n            Name = \"openlibrary\",\n            Method = \"GET\",\n            Url = new Uri(\"https://openlibrary.org/works/OL45883W.json\"),\n        }\n    }\n});\n\nvar result = await client.CallToolAsync(\"openlibrary\", new Dictionary\u003cstring, object?\u003e());\nConsole.WriteLine(result);\n```\n\n### MCP (Model Context Protocol)\nMulti-server configuration with OAuth2 and HTTP SSE streaming:\n```csharp\nusing Utcp.Mcp;\nusing Utcp.Core.Models;\n\nvar mcpTemplate = new McpCallTemplate\n{\n    CallTemplateType = \"mcp\",\n    Name = \"mcp\",\n    Config = new McpConfig\n    {\n        McpServers = new Dictionary\u003cstring, McpServerConfig\u003e\n        {\n            [\"stdioServer\"] = new() { Command = \"my-mcp-server\" },\n            [\"httpServer\"] = new() { Url = \"http://localhost:7400/mcp\", Headers = new Dictionary\u003cstring,string\u003e{{\"X-Client\",\"Utcp\"}} },\n        }\n    },\n    Auth = new OAuth2Auth { AuthType = \"oauth2\", TokenUrl = \"http://localhost:7400/token\", ClientId = \"id\", ClientSecret = \"secret\" }\n};\n\nvar register = await client.RegisterManualAsync(mcpTemplate);\nvar mcpResult = await client.CallToolAsync(\"echo\", new Dictionary\u003cstring, object?\u003e{ [\"message\"] = \"hi\" });\nawait foreach (var chunk in client.CallToolStreamingAsync(\"echo\", new Dictionary\u003cstring, object?\u003e{ [\"message\"] = \"hi\" }))\n{\n    Console.WriteLine(chunk);\n}\n```\n\n### Variable substitution\nNamespaced variables are computed by:\n- Take the namespace and replace every `_` with `__` (double underscores)\n- Append `_` and the variable key\n\nIn other words: `{namespace with underscores doubled}_{KEY}`.\n\nExamples:\n- `variableNamespace = \"manual_openlibrary\"`, `key = \"API_KEY\"` → `manual__openlibrary_API_KEY`\n- `variableNamespace = \"ns_with_many_parts\"`, `key = \"TOKEN\"` → `ns__with__many__parts_TOKEN`\n\nVariables are resolved first from `UtcpClientConfig.Variables` using the namespaced key, then from environment variables using the same namespaced key. Both `$VAR` and `${VAR}` forms in strings are supported.\n```csharp\nusing Utcp.Core.Substitution;\n\nvar substitutor = new DefaultVariableSubstitutor();\nvar cfg = new UtcpClientConfig { ToolRepository = repo, ToolSearchStrategy = search, Variables = new(){ [\"manual__openlibrary_API_KEY\"] = \"123\" } };\nvar substituted = substitutor.Substitute(\n    new Dictionary\u003cstring, object?\u003e { [\"auth\"] = new Dictionary\u003cstring, object?\u003e{ [\"token\"] = \"${API_KEY}\" } },\n    cfg,\n    \"manual_openlibrary\"\n);\n```\n\n### OpenAPI conversion\nYou can register an OpenAPI spec by passing its URL as an `HttpCallTemplate` to `RegisterManualAsync`. The HTTP protocol automatically fetches and converts JSON OpenAPI documents into a UTCP manual at registration time.\n\nExample (automatic conversion on register):\n```csharp\nusing Utcp.Http;\n\nvar client = await UtcpClientImplementation.CreateAsync(config: new UtcpClientConfig\n{\n    ToolRepository = new Utcp.Core.Repositories.InMemToolRepository(),\n    ToolSearchStrategy = new Utcp.Core.Search.TagAndDescriptionWordMatchStrategy(),\n});\n\nawait client.RegisterManualAsync(new HttpCallTemplate\n{\n    CallTemplateType = \"http\",\n    Name = \"petstore\",\n    Url = new Uri(\"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json\"),\n});\n\nvar tools = await client.SearchToolsAsync(\"pet store\", 5);\n```\n\nEnd-to-end conversion using `OpenApiToUtcpConverter` (manual control):\n```csharp\nusing Utcp.Http.OpenApi;\nusing System.Net.Http;\n\n// Load an OpenAPI document\nvar http = new HttpClient();\nvar specUrl = \"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml\";\nvar yaml = await http.GetStringAsync(specUrl);\n\n// Convert (reader supports JSON/YAML via Microsoft.OpenApi.Readers)\nvar converter = new OpenApiToUtcpConverter();\nvar manual = converter.ConvertFromString(yaml, specUrl);\n\n// Register the converted tools\nvar cfg = new UtcpClientConfig\n{\n    ToolRepository = new Utcp.Core.Repositories.InMemToolRepository(),\n    ToolSearchStrategy = new Utcp.Core.Search.TagAndDescriptionWordMatchStrategy(),\n    ManualCallTemplates = Array.Empty\u003cCallTemplate\u003e()\n};\nvar client = await UtcpClientImplementation.CreateAsync(config: cfg);\nawait client.RegisterManualAsync(new HttpCallTemplate { CallTemplateType = \"http\", Name = manual.Tools.First().ToolCallTemplate.Name, Url = new Uri(\"http://example\") });\n\n// Call one of the converted tools (operationId)\nvar tools = await client.SearchToolsAsync(\"pet store\", 5);\nvar anyTool = tools.First();\nvar output = await client.CallToolAsync(anyTool.Name, new Dictionary\u003cstring, object?\u003e());\nConsole.WriteLine(output);\n```\nSee also `tests/Utcp.Http.Tests/OpenApiConverterTests.cs` for focused examples.\n\n### Samples\n- `samples/ClientSample` – demonstrates setup, HTTP + MCP configuration\n- `samples/ServerSample` – minimal ASP.NET API with UTCP discovery endpoint\n\n### Full examples with expected outputs\n\n#### CLI\nRun a local command and capture stdout/stderr:\n```csharp\nusing Utcp.Cli;\n\nvar client = await UtcpClientImplementation.CreateAsync(config: new UtcpClientConfig\n{\n    ToolRepository = new Utcp.Core.Repositories.InMemToolRepository(),\n    ToolSearchStrategy = new Utcp.Core.Search.TagAndDescriptionWordMatchStrategy(),\n});\n\n// Register a CLI tool (e.g., echo)\nvar template = new CliCallTemplate { CallTemplateType = \"cli\", Name = \"local\", Command = \"/bin/echo\" };\nawait client.RegisterManualAsync(template);\n\n// Call it\nvar resp = await client.CallToolAsync(\"local\", new Dictionary\u003cstring, object?\u003e { [\"args\"] = new [] { \"hello\" } });\nConsole.WriteLine(resp); // expected: \"hello\\n\"\n```\n\n#### Text\nRead a file fully and as a stream of chunks:\n```csharp\nusing Utcp.Text;\n\nvar text = new TextCallTemplate { CallTemplateType = \"text\", Name = \"docs\", FilePath = \"README.md\" };\nawait client.RegisterManualAsync(text);\n\nvar all = await client.CallToolAsync(\"docs\", new Dictionary\u003cstring, object?\u003e());\nConsole.WriteLine(((string)all!).Length \u003e 0); // expected: True\n\nvar streamed = client.CallToolStreamingAsync(\"docs\", new Dictionary\u003cstring, object?\u003e());\nawait foreach (var chunk in streamed)\n{\n    Console.WriteLine(chunk); // expected: lines (or chunks if ChunkSizeBytes set)\n}\n```\n\n#### MCP\nEcho via MCP HTTP and stdio with result shaping:\n```csharp\nusing Utcp.Mcp;\n\nvar mcp = new McpCallTemplate\n{\n    CallTemplateType = \"mcp\",\n    Name = \"mcp\",\n    Config = new McpConfig\n    {\n        McpServers = new Dictionary\u003cstring, McpServerConfig\u003e\n        {\n            [\"http\"] = new() { Url = \"http://localhost:7400/mcp\" },\n            [\"stdio\"] = new() { Command = \"my-mcp-server\" },\n        }\n    }\n};\nawait client.RegisterManualAsync(mcp);\n\nvar r = await client.CallToolAsync(\"echo\", new Dictionary\u003cstring, object?\u003e { [\"message\"] = \"test\" });\nConsole.WriteLine(r); // expected: { reply = \"you said: test\" } or similar shaped object/string\n\nawait foreach (var s in client.CallToolStreamingAsync(\"echo\", new Dictionary\u003cstring, object?\u003e { [\"message\"] = \"test\" }))\n{\n    Console.WriteLine(s); // expected: streamed chunks/events if server uses SSE\n}\n```\n\n### Protocol registration\nProtocols are registered in `Utcp.Core.Protocols.ProtocolRegistry`. Custom protocols can be added at runtime:\n```csharp\nusing Utcp.Core.Protocols;\nProtocolRegistry.Register(\"my-protocol\", new MyProtocol());\n```\n\n### Post-processing\nUse post-processors to shape results. A simple filter example:\n```csharp\nusing Utcp.Core.PostProcessing;\nvar cfg = new UtcpClientConfig { ToolRepository = repo, ToolSearchStrategy = search, PostProcessing = new [] { new FilterDictPostProcessor(new []{\"allowed\"}) } };\n```\n\n### Tool search\nDefault search `TagAndDescriptionWordMatchStrategy` ranks tools by tag hits and description term overlap:\n```csharp\nvar results = await client.SearchToolsAsync(\"weather forecast\", limit: 5);\n```\n\n### Concurrency and thread safety\n`InMemToolRepository` uses async synchronization to support concurrent reads/writes safely. All client operations are async and accept `CancellationToken`.\n\n### Error handling\n- Rich argument and state validation with specific exceptions\n- HTTP paths use `EnsureSuccessStatusCode` and propagate details\n- MCP stdio paths surface process start and JSON parsing errors\n\n### Parity with Python\n- Data models map 1:1 (Auth, CallTemplate, Tool, UtcpManual, UtcpClientConfig, RegisterManualResult)\n- MCP mirrors registration, calling, streaming, OAuth2 token flows, and result shaping\n- OpenAPI converter maps security schemes and schemas similarly; see tests for parity\n- Variable substitution supports namespacing and env resolution\n\n### CI \u0026 Packaging\n- GitHub Actions builds/tests/packs on Windows, Linux, macOS (`.github/workflows/build.yml`)\n- NuGet metadata via `Directory.Build.props` (packages per project)\n\n### License\nThis project is licensed under the Mozilla Public License 2.0 (MPL-2.0). See `LICENSE` for details.\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcicatriiz%2Fcsharp-utcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcicatriiz%2Fcsharp-utcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcicatriiz%2Fcsharp-utcp/lists"}