This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Voltaic is a JSON-RPC 2.0, Model Context Protocol (MCP), and Agent2Agent (A2A) implementation for .NET 8.0 and .NET 10.0. The library provides TCP JSON-RPC, MCP stdio/Streamable HTTP/TCP/WebSocket transports, and dependency-light A2A JSON-RPC/HTTP+JSON/gRPC/SSE hosting. Voltaic v0.4.0 targets MCP protocol version 2025-11-25, retains MCP compatibility for 2025-03-26, and targets A2A protocol version 1.0.
- Voltaic (src/Voltaic/): Core library containing JSON-RPC 2.0, MCP, and A2A protocol implementation
JsonRpcRequest.cs: Request message modelJsonRpcResponse.cs: Response message modelJsonRpcError.cs: Error object with standard JSON-RPC error codesJsonRpcServer.cs: TCP-based server implementation with JsonRpcServer classJsonRpcClient.cs: TCP-based client implementation with JsonRpcClient classMessageFraming.cs: LSP-style message framing utilities (internal static class)
McpEndpoint.cs: Shared MCP dispatcher for tools, resources, prompts, completions, capabilities, schema validation, and utility methodsMcp*Models.cs: Typed MCP protocol models for capabilities, content, resources, prompts, completions, and utilitiesA2A*: A2A Agent Cards, models, JSON-RPC/HTTP+JSON/gRPC clients and servers, task store/projection, SSE helpers, protobuf wire helpers, and protocol errors- Test.Shared (src/Test.Shared/): Touchstone descriptors and public API coverage matrix
- Test.Automated (src/Test.Automated/): Touchstone console runner
- Test.Xunit / Test.Nunit: Touchstone adapter projects for
dotnet test - Sample.McpServer: Copyable MCP sample with tools, resources, prompts, and completions
- Sample.A2AServer: Copyable A2A sample with Agent Card, JSON-RPC, HTTP+JSON, gRPC, streaming, push config, and extended card support
- Test.JsonRpc*, Test.Mcp*, Test.A2AServer, Test.A2AClient: Manual and transport-specific sample/test applications
Namespace layout:
Voltaic.Core: JSON-RPC, TCP framing, connection, shared auth/error helpersVoltaic.Mcp: MCP clients, servers, protocol models, and endpoint helpersVoltaic.A2A: A2A clients, servers, protocol models, task helpers, and Agent Card discovery
# Build entire solution
dotnet build src/Voltaic.sln
# Build specific project
dotnet build src/Voltaic/Voltaic.csproj
dotnet build src/Sample.McpServer/Sample.McpServer.csproj
dotnet build src/Sample.A2AServer/Sample.A2AServer.csproj
dotnet build src/Test.A2AServer/Test.A2AServer.csproj
# Build for release
dotnet build src/Voltaic.sln -c Release
# Release package
dotnet pack src/Voltaic/Voltaic.csproj -c Release# Run Touchstone console suites
dotnet run --project src/Test.Automated/Test.Automated.csproj -c Release --framework net8.0
dotnet run --project src/Test.Automated/Test.Automated.csproj -c Release --framework net10.0
# Export JSON results
dotnet run --project src/Test.Automated/Test.Automated.csproj -c Release --framework net8.0 -- --results artifacts/test-results/voltaic-touchstone.json
# Run adapter-backed tests
dotnet test src/Test.Xunit/Test.Xunit.csproj -c Release --framework net8.0
dotnet test src/Test.Xunit/Test.Xunit.csproj -c Release --framework net10.0
dotnet test src/Test.Nunit/Test.Nunit.csproj -c Release --framework net8.0
dotnet test src/Test.Nunit/Test.Nunit.csproj -c Release --framework net10.0
# The shared suite currently projects 274 deterministic cases through the console, xUnit, and NUnit runners.# Start JSON-RPC TCP server/client examples
dotnet run --project src/Test.JsonRpcServer/Test.JsonRpcServer.csproj -- 8080
dotnet run --project src/Test.JsonRpcClient/Test.JsonRpcClient.csproj -- 8080
# Start MCP HTTP examples
dotnet run --project src/Test.McpHttpServer/Test.McpHttpServer.csproj -- 8080
dotnet run --project src/Test.McpHttpClient/Test.McpHttpClient.csproj -- 8080
# Start the combined MCP sample server
dotnet run --project src/Sample.McpServer/Sample.McpServer.csproj
# Start the A2A sample server and manual client
# A2A servers use the selected port for JSON-RPC/HTTP+JSON and port + 1 for gRPC.
dotnet run --project src/Sample.A2AServer/Sample.A2AServer.csproj -- 8080
dotnet run --project src/Test.A2AServer/Test.A2AServer.csproj -- 8080
dotnet run --project src/Test.A2AClient/Test.A2AClient.csproj -- http://localhost:8080 "hello from A2A"The implementation follows JSON-RPC 2.0 specification:
- Requests have an optional
idfield - when omitted, they become notifications (no response expected) - Responses must include the matching
idfrom the request - Built-in error codes are provided via static factory methods in
JsonRpcError
- Implements
IDisposablefor proper resource cleanup - Uses
TcpListenerto accept connections on a specified port - Maintains concurrent connections in a
ConcurrentDictionary<string, ClientConnection> - Each client is assigned a unique ID (
client_1,client_2, etc.) - Methods are registered via
RegisterMethod(name, handler)with both sync (Func<JsonElement?, object>) and async (Func<JsonElement?, Task<object>>) overloads. Sync handlers are wrapped internally viaTask.FromResult(). - Built-in methods:
echo,getTime,add,getClients,ping - Supports broadcasting notifications to all connected clients via
BroadcastNotificationAsync() - Responses are only sent for requests with an
id(not for notifications) - Server can be stopped gracefully with
Stop()method which disconnects all clients
- Implements
IDisposablefor proper resource cleanup - Uses
TcpClientto connect to server viaConnectAsync(host, port) - Maintains pending requests in
ConcurrentDictionary<object, TaskCompletionSource<JsonRpcResponse>> - Request IDs are integers generated by
Interlocked.Increment(thread-safe) - Response matching handles
JsonElementwrapping of numeric IDs - Background receive loop processes responses and notifications
- Two call methods:
CallAsync<T>()for typed responses andCallAsync()for object responses NotifyAsync()sends notifications without expecting responses- Notifications from server trigger
NotificationReceivedevent - Connection status available via
IsConnectedproperty - Supports configurable timeouts for requests (default 30 seconds)
- Use
Disconnect()orDispose()to cleanly close connection
-
ID Handling: The client uses integer IDs but must handle them as
JsonElementwhen deserializing responses. TheProcessResponsemethod (JsonRpcClient.cs:89-133) extracts the integer value fromJsonElementfor dictionary lookups. -
Namespace Usage: When working with the library:
- Use
using Voltaic.Core;for JSON-RPC and shared core types - Use
using Voltaic.Mcp;for MCP types - Use
using Voltaic.A2A;for A2A types
- Use
-
Connection Management:
- Server handles disconnections in
HandleClientAsyncfinally block (JsonRpcServer.cs:137-142) - Client maintains
isConnectedflag synchronized with connection state - Pending requests are cancelled on disconnect
- Server handles disconnections in
-
Method Registration: Server methods receive
JsonElement?parameters and must useTryGetProperty()to extract typed values safely. -
Message Framing: The
MessageFramingclass (internal static) provides LSP-style message framing:- Format:
Content-Length: {size}\r\n\r\n{json_body} ReadMessageAsync()handles partial reads, buffering, and multiple messagesWriteMessageAsync()frames messages with Content-Length header- Supports optional Content-Type header
- Note: Currently uses tuples for return values (should be refactored per coding standards)
- Format:
The Voltaic library is configured for NuGet package generation:
- Author: Joel Christner
- Target frameworks: .NET 8.0 and .NET 10.0
GeneratePackageOnBuildis enabled
THESE RULES MUST BE FOLLOWED STRICTLY
- Namespace declaration must always be at the top of the file
- Using statements must be contained INSIDE the namespace block
- All Microsoft and standard system library usings must be first, in alphabetical order
- Other using statements follow, also in alphabetical order
Example:
namespace Voltaic.Core
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ThirdPartyLib;
}- All public members, constructors, and public methods MUST have XML documentation comments
- NO code documentation should be applied to private members or private methods
- Document nullability in XML comments
- Document thread safety guarantees in XML comments
- Document which exceptions public methods can throw using
/// <exception>tags - Document default values, minimum values, and maximum values where appropriate
- Specify what different values mean or what effect they may have
- Private class member variable names MUST start with an underscore and then be Pascal cased
- Correct:
_FooBar,_TcpClient,_IsConnected - Incorrect:
_fooBar,fooBar,tcpClient
- Correct:
- All public members should have explicit getters and setters using backing variables when value requires range or null validation
- Do NOT use
varwhen defining variables - use the actual type explicitly- Correct:
List<string> items = new List<string>(); - Incorrect:
var items = new List<string>();
- Correct:
- Avoid using constant values for things that developers may later want to configure
- Instead use a public member with a backing private member set to a reasonable default
- Do NOT use tuples unless absolutely necessary - they are strongly discouraged
- Use specific exception types rather than generic
Exception - Always include meaningful error messages with context
- Consider using custom exception types for domain-specific errors
- Use exception filters when appropriate:
catch (SqlException ex) when (ex.Number == 2601) - Use nullable reference types (ensure
<Nullable>enable</Nullable>in project files) - Validate input parameters with guard clauses at method start
- Use
ArgumentNullException.ThrowIfNull()for .NET 6+ or manual null checks - Consider using the Result pattern or Option/Maybe types for methods that can fail
- Proactively identify and eliminate any situations in code where null might cause exceptions
- Async calls should use
.ConfigureAwait(false)where appropriate - Every async method should accept a
CancellationTokenas an input parameter, unless:- The class has a
CancellationTokenas a class member, OR - The class has a
CancellationTokenSourceas a class member
- The class has a
- Async calls should check whether cancellation has been requested at appropriate places using
token.ThrowIfCancellationRequested()or checkingtoken.IsCancellationRequested - When implementing a method that returns an
IEnumerable, also create an async variant that includes aCancellationToken
- Implement
IDisposable/IAsyncDisposablewhen holding unmanaged resources or disposable objects - Use
usingstatements orusingdeclarations forIDisposableobjects - Follow the full Dispose pattern with
protected virtual void Dispose(bool disposing) - Always call
base.Dispose()in derived classes
- Use
Interlockedoperations for simple atomic operations - Prefer
ReaderWriterLockSlimoverlockfor read-heavy scenarios
- Prefer LINQ methods over manual loops when readability is not compromised
- Use
.Any()instead of.Count() > 0for existence checks - Be aware of multiple enumeration issues - consider
.ToList()when needed - Use
.FirstOrDefault()with null checks rather than.First()when element might not exist
- Limit each file to containing exactly one class OR exactly one enum
- Do NOT nest multiple classes or multiple enums in a single file
- Regions for Public-Members, Private-Members, Constructors-and-Factories, Public-Methods, and Private-Methods are NOT required for small files under 500 lines
- Ensure NO
Console.WriteLinestatements are added to library code - Use logging abstractions, events, or delegates instead for library diagnostic output
- Sample and test applications under
src/Test.*andsrc/Sample.*may use console output freely
- If manual SQL string construction is present, there is likely a good reason - assume it's intentional
- Do not make assumptions about class members or methods on opaque classes - ask for implementation details
- If a README exists, analyze it and ensure it is accurate
- Compile the code and ensure it is free of errors and warnings to the best of your ability
Note: Some existing code in the repository does not yet fully conform to these standards:
- Private member names in
JsonRpcClient.csandJsonRpcServer.csuse camelCase (e.g.,tcpClient,stream) instead of PascalCase with underscore prefix (e.g.,_TcpClient,_Stream) MessageFraming.csuses tuples extensively for return values, which should be refactored to custom types or classes- Some uses of
varexist in the codebase that should be replaced with explicit types
When modifying existing code, update it to follow these standards. When creating new code, strictly adhere to all standards from the start.