N2.Mcp.Core
1.0.1
dotnet add package N2.Mcp.Core --version 1.0.1
NuGet\Install-Package N2.Mcp.Core -Version 1.0.1
<PackageReference Include="N2.Mcp.Core" Version="1.0.1" />
<PackageVersion Include="N2.Mcp.Core" Version="1.0.1" />
<PackageReference Include="N2.Mcp.Core" />
paket add N2.Mcp.Core --version 1.0.1
#r "nuget: N2.Mcp.Core, 1.0.1"
#:package N2.Mcp.Core@1.0.1
#addin nuget:?package=N2.Mcp.Core&version=1.0.1
#tool nuget:?package=N2.Mcp.Core&version=1.0.1
N2.McpCore - Technical Guide
This library provides the foundational components for building MCP (Model Context Protocol) servers in .NET. It handles the JSON-RPC communication layer and MCP protocol compliance, allowing you to focus on implementing your domain-specific tools.
Installation
Add the package to your project:
dotnet add package N2.Core.Abstractions
Creating a Custom MCP Server
1. Inherit from McpServer
Create a class that inherits from McpServer and provide server information and capabilities:
using McpCore.Server;
using McpCore.Protocol;
public class YourMcpServer : McpServer
{
public YourMcpServer() : base(
new McpServerInfo
{
Name = "your-server-name",
Version = "1.0.0"
},
new McpServerCapabilities
{
Tools = new McpToolsCapability()
})
{
}
}
2. Define Available Tools
Override GetAvailableTools() to return an array of tool definitions. Each tool must include:
- Name: Unique identifier for the tool
- Description: Human-readable description of what the tool does
- InputSchema: JSON Schema defining the tool's parameters
protected override McpTool[] GetAvailableTools()
{
return new[]
{
new McpTool
{
Name = "your_tool_name",
Description = "Description of what this tool does",
InputSchema = new McpInputSchema
{
Type = "object",
Properties = new Dictionary<string, McpPropertyDefinition>
{
["param1"] = new McpPropertyDefinition
{
Type = "string",
Description = "Description of parameter 1"
},
["param2"] = new McpPropertyDefinition
{
Type = "boolean",
Description = "Description of parameter 2",
Default = false
},
["param3"] = new McpPropertyDefinition
{
Type = "integer",
Description = "Description of parameter 3"
}
},
Required = new[] { "param1" } // List required parameters
}
}
};
}
3. Implement Tool Execution
Override CallToolAsync() to handle tool execution. Use a switch statement or pattern matching to route to specific tool handlers:
protected override async Task<McpToolCallResult> CallToolAsync(McpToolCallParams parameters)
{
try
{
var result = parameters.Name switch
{
"your_tool_name" => await HandleYourToolAsync(parameters.Arguments),
_ => throw new ArgumentException($"Unknown tool: {parameters.Name}")
};
return new McpToolCallResult
{
Content = new[] { new McpContent { Type = "text", Text = result } },
IsError = false
};
}
catch (Exception ex)
{
return new McpToolCallResult
{
Content = new[] { new McpContent { Type = "text", Text = $"Error: {ex.Message}" } },
IsError = true
};
}
}
Working with Tool Arguments
Tool arguments arrive as Dictionary<string, object?> where values may be JsonElement or native types. Use the helper methods to safely extract typed values:
private static string? GetStringArgument(Dictionary<string, object?> arguments, string key)
{
if (!arguments.TryGetValue(key, out var value) || value == null)
return null;
if (value is JsonElement element)
return element.GetString();
return value.ToString();
}
private static bool GetBoolArgument(Dictionary<string, object?> arguments, string key)
{
if (!arguments.TryGetValue(key, out var value) || value == null)
return false;
if (value is JsonElement element)
return element.GetBoolean();
if (value is bool boolValue)
return boolValue;
return bool.TryParse(value.ToString(), out var result) && result;
}
private static int? GetIntArgument(Dictionary<string, object?> arguments, string key)
{
if (!arguments.TryGetValue(key, out var value) || value == null)
return null;
if (value is JsonElement element)
return element.GetInt32();
if (value is int intValue)
return intValue;
return int.TryParse(value.ToString(), out var result) ? result : null;
}
Dependency Injection Integration
MCP servers often need access to business logic services. Accept IServiceProvider in your constructor:
public class YourMcpServer : McpServer
{
private readonly IServiceProvider _serviceProvider;
public YourMcpServer(IServiceProvider serviceProvider)
: base(
new McpServerInfo { Name = "your-server", Version = "1.0.0" },
new McpServerCapabilities { Tools = new McpToolsCapability() }
)
{
_serviceProvider = serviceProvider;
}
private async Task<string> HandleYourToolAsync(Dictionary<string, object?> arguments)
{
var service = _serviceProvider.GetRequiredService<IYourService>();
// Use the service to implement tool logic
var result = await service.DoSomethingAsync();
return result.Message;
}
}
Register your server in the DI container:
serviceCollection.AddSingleton<YourMcpServer>();
Setting Up stdio Communication
MCP servers communicate via JSON-RPC 2.0 over standard input/output. Implement a loop that:
- Reads JSON-RPC requests from
Console.In - Processes them through your MCP server
- Writes JSON-RPC responses to
Console.Out - Logs diagnostic information to
Console.Error
private static async Task<int> RunMcp(IServiceProvider serviceProvider)
{
serviceProvider.RunMcpServerAsync<YourMcpServer>(Console.In, Console.Out, Console.Error.Writeline);
}
Utility Classes
Response and Response<T>
The Response class provides a standardized way to return operation results:
// Simple success/failure
var result = Response.Ok();
var failed = Response.Fail("Operation failed");
// With message
var success = Response.Ok("Operation completed successfully");
// With typed value
var data = Response.Ok("Data retrieved", new { Id = 1, Name = "Test" });
var typedData = Response.Ok(new User { Id = 1, Name = "John" });
// Check result
if (result.Success)
{
Console.WriteLine(result.Message);
}
VerifyResult
The VerifyResult class accumulates validation messages:
var verify = VerifyResult.Start("Validating input");
if (string.IsNullOrEmpty(name))
{
verify.Fail("Name is required");
}
if (age < 0)
{
verify.Fail("Age must be positive");
}
verify.Remark("Validation completed");
// Throw exception if any failures occurred
verify.ThrowIfFailed();
// Or check manually
if (verify.Failure)
{
foreach (var message in verify.Messages)
{
Console.WriteLine(message);
}
}
SessionCodeGenerator
Generate and validate 6-character session codes for client connections:
var generator = new SessionCodeGenerator();
// Generate a code like "ABC123"
string code = generator.GenerateCode();
// Validate a code
bool isValid = generator.IsValidCode("ABC123"); // true
bool isInvalid = generator.IsValidCode("invalid"); // false
Register in DI container:
serviceCollection.AddSingleton<ISessionCodeGenerator, SessionCodeGenerator>();
JSON Serialization
The library provides pre-configured JsonSerializerOptions via McpServer.options:
var json = JsonSerializer.Serialize(data, McpServer.options);
var obj = JsonSerializer.Deserialize<MyType>(json, McpServer.options);
Features:
- Case-insensitive property names
- Enum string conversion with integer fallback
- Trailing commas allowed
- Number reading from strings
- Out-of-order metadata properties
- Maximum depth of 5
Best Practices
Input Validation
- Always validate required parameters and throw
ArgumentExceptionfor missing or invalid inputs - Use
VerifyResultto accumulate validation errors before throwing - Use try-catch in
CallToolAsync()to return structured error responses - Provide clear error messages to help users understand what went wrong
Tool Design
- Use clear, descriptive tool names following
category_actionpattern (e.g.,file_read,data_transform) - Write detailed descriptions that explain what the tool does and when to use it
- Define comprehensive input schemas with descriptions for all parameters
- Mark parameters as required or provide sensible defaults
Error Handling
- Catch exceptions at the tool execution level
- Return
McpToolCallResultwithIsError = truefor failures - Include meaningful error messages in the response content
- Log detailed error information to
Console.Errorfor debugging
Performance
- Keep tool execution lightweight and responsive
- For long-running operations, consider returning progress updates
- Dispose of resources properly in async operations
Logging
- Use
Console.Errorfor all diagnostic logging (neverConsole.Out) - Include timestamps in log messages
- Log request/response counts and timing information
- Log errors with full exception details
MCP Protocol Compliance
- Never modify the base
McpServerinitialization flow - Respect the two-phase initialization (initialize → initialized notification)
- Handle notifications (requests without IDs) correctly by not sending responses
- Use standard JSON-RPC error codes from
JsonRpcErrorCodes
Testing Your Server
Create unit tests that verify:
- Tool definitions are correctly formatted
- Tool execution handles valid inputs correctly
- Error cases return appropriate error responses
- Required parameters are validated
[Test]
public async Task CallToolAsync_WithValidInput_ReturnsSuccess()
{
var server = new YourMcpServer(serviceProvider);
var result = await server.CallToolAsync(new McpToolCallParams
{
Name = "your_tool_name",
Arguments = new Dictionary<string, object?>
{
["param1"] = "test value"
}
});
Assert.IsFalse(result.IsError);
Assert.IsNotEmpty(result.Content);
}
Common Patterns
Returning Multiple Content Items
You can return multiple content items in a single response:
return new McpToolCallResult
{
Content = new[]
{
new McpContent { Type = "text", Text = "Summary information" },
new McpContent { Type = "text", Text = "Detailed data", MimeType = "application/json" }
},
IsError = false
};
Handling Enum Parameters
For parameters with limited valid values, use enums and parse them from strings:
var statusStr = GetStringArgument(arguments, "status") ?? "Default";
var status = Enum.TryParse<YourEnum>(statusStr, true, out var parsed)
? parsed
: throw new ArgumentException($"Invalid status: {statusStr}");
Using Response Pattern for Tool Results
Combine Response class with tool execution:
private async Task<string> HandleYourToolAsync(Dictionary<string, object?> arguments)
{
var service = _serviceProvider.GetRequiredService<IYourService>();
var result = await service.DoSomethingAsync();
return result.Success
? result.Message ?? "Operation completed successfully"
: $"Failed: {result.Message}";
}
Support and Resources
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
-
.NETStandard 2.1
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
-
net9.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.