|
| 1 | +# Copilot Instructions for MCP C# SDK |
| 2 | + |
| 3 | +This repository contains the official C# SDK for the Model Context Protocol (MCP), enabling .NET applications to implement and interact with MCP clients and servers. |
| 4 | + |
| 5 | +## Critical: Always Build and Test |
| 6 | + |
| 7 | +**ALWAYS build and run tests before declaring any task complete or making a pull request.** |
| 8 | + |
| 9 | +When making code changes: |
| 10 | +1. **Build first**: Run `dotnet build` to ensure the code compiles without errors |
| 11 | +2. **Run tests**: Execute `dotnet test` to verify all tests pass |
| 12 | +3. **Fix issues**: Address any build errors or test failures before proceeding |
| 13 | +4. **Verify iteratively**: Build and test frequently during development, not just at the end |
| 14 | +5. **Check warnings**: Treat warnings as errors - the build is configured with `TreatWarningsAsErrors=true` |
| 15 | + |
| 16 | +**Never skip these steps.** Even small changes can have unexpected impacts. A passing build and test suite is the minimum bar for any code change. |
| 17 | + |
| 18 | +## Project Overview |
| 19 | + |
| 20 | +The SDK consists of three main packages: |
| 21 | +- **ModelContextProtocol.Core** - Client and low-level server APIs with minimal dependencies |
| 22 | +- **ModelContextProtocol** - The main package with hosting and dependency injection extensions and which references ModelContextProtocol.Core |
| 23 | +- **ModelContextProtocol.AspNetCore** - HTTP-based MCP server implementations for ASP.NET Core, referencing ModelContextProtocol |
| 24 | + |
| 25 | +## C# Coding Standards |
| 26 | + |
| 27 | +### Language Features |
| 28 | +- Use **file-scoped namespaces** for all C# files |
| 29 | +- Enable **implicit usings** and **nullable reference types** |
| 30 | +- Use **preview language features** (LangVersion: preview) |
| 31 | +- Treat warnings as errors |
| 32 | + |
| 33 | +### Code Style |
| 34 | +- Follow the conventions in `.editorconfig` |
| 35 | +- Use clear, descriptive XML documentation comments for public APIs |
| 36 | +- Follow async/await patterns consistently |
| 37 | +- Use file-scoped namespaces: `namespace ModelContextProtocol.Client;` |
| 38 | + |
| 39 | +### Naming Conventions |
| 40 | +- Use `McpClient`, `McpServer`, `McpSession` for MCP-related classes (capitalize MCP) |
| 41 | +- Prefix MCP-specific types with `Mcp` (e.g., `McpException`, `McpEndpoint`) |
| 42 | +- Use descriptive names for parameters with `[Description("...")]` attributes when exposing to MCP |
| 43 | + |
| 44 | +## Architecture Patterns |
| 45 | + |
| 46 | +### Dependency Injection |
| 47 | +- Use Microsoft.Extensions.DependencyInjection patterns |
| 48 | +- Register services with `.AddMcpServer()` and `.AddMcpClient()` extension methods |
| 49 | +- Support both builder patterns and options configuration |
| 50 | + |
| 51 | +### JSON Serialization |
| 52 | +- Use `System.Text.Json` exclusively for all JSON operations |
| 53 | +- Use `McpJsonUtilities.DefaultOptions` for consistent serialization settings across the SDK |
| 54 | +- Support source generation for Native AOT compatibility via `McpJsonUtilities` source generators |
| 55 | +- Set `JsonIgnoreCondition.WhenWritingNull` for optional properties to minimize payload size |
| 56 | +- Use `JsonSerializerDefaults.Web` for camelCase property naming |
| 57 | +- Protocol types are decorated with `[JsonSerializable]` attributes for AOT support |
| 58 | +- Custom converters: `CustomizableJsonStringEnumConverter` for flexible enum serialization |
| 59 | + |
| 60 | +### Async Patterns |
| 61 | +- All I/O operations should be async |
| 62 | +- Use `ValueTask<T>` for hot paths that may complete synchronously |
| 63 | +- Always accept `CancellationToken` parameters for async operations |
| 64 | +- Name parameters consistently: `cancellationToken` |
| 65 | + |
| 66 | +### MCP Protocol |
| 67 | +- Follow the MCP specification at https://spec.modelcontextprotocol.io/ ([specification docs](https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/docs/specification)) |
| 68 | +- Use JSON-RPC 2.0 for message transport |
| 69 | +- Support all standard MCP capabilities (e.g. tools, prompts, resources, sampling) |
| 70 | +- Implement proper error handling with `McpException` and `McpErrorCode` |
| 71 | + |
| 72 | +### Error Handling |
| 73 | +- Throw `McpException` for MCP protocol-level errors with appropriate `McpErrorCode` |
| 74 | +- Use standard error codes: `InvalidRequest`, `MethodNotFound`, `InvalidParams`, `InternalError` |
| 75 | +- Let domain exceptions bubble up and convert to `InternalError` at transport boundary |
| 76 | +- Include detailed error messages in exception `Message` property for debugging |
| 77 | +- Errors are automatically converted to JSON-RPC error responses by the server infrastructure |
| 78 | + |
| 79 | +## Testing |
| 80 | + |
| 81 | +### Test Organization |
| 82 | +- Unit tests in `tests/ModelContextProtocol.Tests` for core functionality |
| 83 | +- Integration tests in `tests/ModelContextProtocol.AspNetCore.Tests` for HTTP/SSE transports |
| 84 | +- Shared test utilities in `tests/Common/Utils/` |
| 85 | +- Test servers in `tests/ModelContextProtocol.Test*Server/` for integration scenarios |
| 86 | +- Filter manual tests with `[Trait("Execution", "Manual")]` - these require external dependencies |
| 87 | + |
| 88 | +### Test Infrastructure and Helpers |
| 89 | +- **LoggedTest**: Base class for tests that need logging output captured to xUnit test output |
| 90 | + - Provides `ILoggerFactory` and `ITestOutputHelper` for test logging |
| 91 | + - Use when debugging or when tests need to verify log output |
| 92 | +- **TestServerTransport**: In-memory transport for testing client-server interactions without network I/O |
| 93 | +- **MockLoggerProvider**: For capturing and asserting on log messages |
| 94 | +- **XunitLoggerProvider**: Routes `ILogger` output to xUnit's `ITestOutputHelper` |
| 95 | +- **KestrelInMemoryTransport** (AspNetCore.Tests): In-memory Kestrel connection for HTTP transport testing without network stack |
| 96 | + |
| 97 | +### Test Best Practices |
| 98 | +- Inherit from `LoggedTest` for tests needing logging infrastructure |
| 99 | +- Use `TestServerTransport` for in-memory client-server testing |
| 100 | +- Mock external dependencies (filesystem, HTTP clients) rather than calling real services |
| 101 | +- Use `CancellationTokenSource` with timeouts to prevent hanging tests |
| 102 | +- Dispose resources properly (servers, clients, transports) using `IDisposable` or `await using` |
| 103 | +- Run tests with: `dotnet test --filter '(Execution!=Manual)'` |
| 104 | + |
| 105 | +## Build and Development |
| 106 | + |
| 107 | +### Build Commands |
| 108 | +- **Restore**: `dotnet restore` |
| 109 | +- **Build**: `dotnet build` |
| 110 | +- **Test**: `dotnet test` |
| 111 | +- **Clean**: `dotnet clean` |
| 112 | + |
| 113 | +### Development Workflow |
| 114 | +**Critical**: Always follow this workflow when making changes: |
| 115 | +1. Make code changes |
| 116 | +2. Build immediately: `dotnet build` - fix any compilation errors |
| 117 | +3. Run tests: `dotnet test` - fix any test failures |
| 118 | +4. Repeat steps 1-3 iteratively as you develop |
| 119 | +5. Only after successful build and tests should you consider the change complete |
| 120 | + |
| 121 | +Do not skip or defer building and testing. These are mandatory steps for every code change, no matter how small. |
| 122 | + |
| 123 | +### SDK Requirements |
| 124 | +- The repo currently requires the .NET SDK 10.0 to build and run tests. |
| 125 | +- Target frameworks: .NET 10.0, .NET 9.0, .NET 8.0, .NET Standard 2.0 |
| 126 | +- Support Native AOT compilation |
| 127 | + |
| 128 | +### Project Structure |
| 129 | +- Source code: `src/` |
| 130 | +- Tests: `tests/` |
| 131 | +- Samples: `samples/` |
| 132 | +- Documentation: `docs/` |
| 133 | +- Build artifacts: `artifacts/` (not committed) |
| 134 | + |
| 135 | +## Key Types and Architectural Layers |
| 136 | + |
| 137 | +The SDK is organized into distinct architectural layers, each with specific responsibilities: |
| 138 | + |
| 139 | +### Protocol Layer (DTO Types) |
| 140 | +- Located in `ModelContextProtocol.Core/Protocol/` |
| 141 | +- Contains Data Transfer Objects (DTOs) for the MCP specification |
| 142 | +- All protocol types follow JSON-RPC 2.0 conventions |
| 143 | +- Key types: |
| 144 | + - **JsonRpcMessage** (abstract base): Represents any JSON-RPC message (request, response, notification, error) |
| 145 | + - **JsonRpcRequest**, **JsonRpcResponse**, **JsonRpcNotification**: Concrete message types |
| 146 | + - **Tool**, **Prompt**, **Resource**: MCP primitive definitions |
| 147 | + - **CallToolRequestParams**, **GetPromptRequestParams**, **ReadResourceRequestParams**: Request parameter types |
| 148 | + - **ClientCapabilities**, **ServerCapabilities**: Capability negotiation types |
| 149 | + - **Implementation**: Server/client identification metadata |
| 150 | + |
| 151 | +### JSON-RPC Implementation |
| 152 | +- Built-in JSON-RPC 2.0 implementation for MCP communication |
| 153 | +- **JsonRpcMessage.Converter**: Polymorphic converter that deserializes messages into correct types based on structure |
| 154 | +- **JsonRpcMessageContext**: Transport-specific metadata (transport reference, execution context, authenticated user) |
| 155 | +- Message routing handled automatically by session implementations |
| 156 | +- Error responses generated via **McpException** with **McpErrorCode** enumeration |
| 157 | + |
| 158 | +### Transport Abstraction |
| 159 | +- **ITransport**: Core abstraction for bidirectional communication |
| 160 | + - Provides `MessageReader` (ChannelReader) for incoming messages |
| 161 | + - `SendMessageAsync()` for outgoing messages |
| 162 | + - `SessionId` property for multi-session scenarios |
| 163 | +- **IClientTransport**: Client-side abstraction that establishes connections and returns ITransport |
| 164 | +- **TransportBase**: Base class for transport implementations with common functionality |
| 165 | + |
| 166 | +### Transport Implementations |
| 167 | +Two primary transport implementations with different invariants: |
| 168 | + |
| 169 | +1. **Stdio-based transports** (`StdioServerTransport`, `StdioClientTransport`): |
| 170 | + - Single-session, process-bound communication |
| 171 | + - Uses standard input/output streams |
| 172 | + - No session IDs (returns null) |
| 173 | + - Automatic lifecycle tied to process |
| 174 | + |
| 175 | +2. **HTTP-based transports**: |
| 176 | + - **SseResponseStreamTransport**: Server-Sent Events for server-to-client streaming |
| 177 | + - Unidirectional (server → client) event stream |
| 178 | + - Client posts messages to separate endpoint (e.g., `/message`) |
| 179 | + - Supports multiple concurrent sessions via SessionId |
| 180 | + - **StreamableHttpServerTransport**: Bidirectional HTTP with streaming |
| 181 | + - Request/response model with streamed progress updates |
| 182 | + - Session management for concurrent connections |
| 183 | + |
| 184 | +### Session Layer |
| 185 | +- **McpSession** (abstract base): Core bidirectional communication for clients and servers |
| 186 | + - Manages JSON-RPC request/response correlation |
| 187 | + - Handles notification routing |
| 188 | + - Provides `SendRequestAsync<TResult>()`, `SendNotificationAsync()`, `RegisterNotificationHandler()` |
| 189 | + - Properties: `SessionId`, `NegotiatedProtocolVersion` |
| 190 | + |
| 191 | +- **McpClient** (extends McpSession): Client-side MCP implementation |
| 192 | + - Connects to servers via `CreateAsync(IClientTransport)` |
| 193 | + - Exposes `ServerCapabilities`, `ServerInfo`, `ServerInstructions` |
| 194 | + - Methods: `ListToolsAsync()`, `CallToolAsync()`, `ListPromptsAsync()`, `GetPromptAsync()`, etc. |
| 195 | + |
| 196 | +- **McpServer** (extends McpSession): Server-side MCP implementation |
| 197 | + - Configured via `McpServerOptions` and `IMcpServerBuilder` |
| 198 | + - Primitives registered as services: `McpServerTool`, `McpServerPrompt`, `McpServerResource` |
| 199 | + - Handles incoming requests through `McpServer.Methods.cs` |
| 200 | + - Supports filters via `McpRequestFilter` for cross-cutting concerns |
| 201 | + |
| 202 | +### Serialization Architecture |
| 203 | +- **McpJsonUtilities.DefaultOptions**: Singleton JsonSerializerOptions for all MCP types |
| 204 | + - Hardcoded to use source-generated serialization for JSON-RPC messages (Native AOT compatible) |
| 205 | + - Source generation defined in `McpJsonUtilities` via `[JsonSerializable]` attributes |
| 206 | + - Includes Microsoft.Extensions.AI types via chained TypeInfoResolver |
| 207 | + |
| 208 | +- **User-defined types** (tool parameters, return values): |
| 209 | + - Accept custom `JsonSerializerOptions` via `McpServerToolCreateOptions.SerializerOptions` |
| 210 | + - Default to `McpJsonUtilities.DefaultOptions` if not specified |
| 211 | + - Can use reflection-based serialization or custom source generators |
| 212 | + |
| 213 | +- **Enum handling**: `CustomizableJsonStringEnumConverter` for flexible enum serialization |
| 214 | + |
| 215 | +## Architecture and Design Patterns |
| 216 | + |
| 217 | +### Server Implementation Architecture |
| 218 | +- **McpServer** is the core server implementation in `ModelContextProtocol.Core/Server/` |
| 219 | +- **IMcpServerBuilder** pattern provides fluent API for configuring servers via DI |
| 220 | +- Server primitives (tools, prompts, resources) are discovered via reflection using attributes |
| 221 | +- Support both attribute-based registration (`WithTools<T>()`) and instance-based (`WithTools(target)`) |
| 222 | +- Use `McpServerFactory` to create server instances with configured options |
| 223 | + |
| 224 | +### Tool/Prompt/Resource Discovery |
| 225 | +- Tools, prompts, and resources use attribute-based discovery: `[McpServerTool]`, `[McpServerPrompt]`, `[McpServerResource]` |
| 226 | +- Type-level attributes (`[McpServerToolType]`, etc.) mark classes containing server primitives |
| 227 | +- Discovery supports both static and instance methods (public and non-public) |
| 228 | +- For Native AOT compatibility, use generic `WithTools<T>()` methods instead of reflection-based variants |
| 229 | +- `AIFunctionMcpServerTool`, `AIFunctionMcpServerPrompt`, and `AIFunctionMcpServerResource` wrap `AIFunction` for integration with Microsoft.Extensions.AI |
| 230 | + |
| 231 | +### Request Processing Pipeline |
| 232 | +- Requests flow through `McpServer.Methods.cs` which handles JSON-RPC message routing |
| 233 | +- Use `McpRequestFilter` for cross-cutting concerns (logging, auth, validation) |
| 234 | +- `RequestContext` provides access to current request state and services |
| 235 | +- `RequestServiceProvider` enables scoped dependency injection per request |
| 236 | +- Filters can short-circuit request processing or transform requests/responses |
| 237 | + |
| 238 | +### Transport Layer Abstraction |
| 239 | +- Transport implementations handle message serialization and connection management |
| 240 | +- Core transports: `StdioServerTransport`, `StreamServerTransport`, `SseResponseStreamTransport`, `StreamableHttpServerTransport` |
| 241 | +- Transports must implement bidirectional JSON-RPC message exchange |
| 242 | +- SSE (Server-Sent Events) transport for unidirectional server→client streaming |
| 243 | +- Streamable HTTP for request/response with streamed progress updates |
| 244 | + |
| 245 | +## Implementation Patterns |
| 246 | + |
| 247 | +### Tool Implementation |
| 248 | +Tools are methods marked with `[McpServerTool]`: |
| 249 | +```csharp |
| 250 | +[McpServerToolType] |
| 251 | +public class MyTools |
| 252 | +{ |
| 253 | + [McpServerTool, Description("Tool description")] |
| 254 | + public static async Task<string> MyTool( |
| 255 | + [Description("Parameter description")] string param, |
| 256 | + CancellationToken cancellationToken) |
| 257 | + { |
| 258 | + // Implementation - use Description attributes for parameter documentation |
| 259 | + // Return string, TextContent, ImageContent, EmbeddedResource, or arrays of these |
| 260 | + } |
| 261 | +} |
| 262 | +``` |
| 263 | +- Tools support dependency injection in constructors for instance methods |
| 264 | +- Parameters are automatically deserialized from JSON using `System.Text.Json` |
| 265 | +- Use `[Description]` attributes on parameters to generate tool schemas |
| 266 | +- Return types: `string`, `TextContent`, `ImageContent`, `EmbeddedResource`, or collections of content types |
| 267 | + |
| 268 | +### Prompt Implementation |
| 269 | +Prompts return `ChatMessage` or arrays thereof: |
| 270 | +```csharp |
| 271 | +[McpServerPromptType] |
| 272 | +public static class MyPrompts |
| 273 | +{ |
| 274 | + [McpServerPrompt, Description("Prompt description")] |
| 275 | + public static ChatMessage MyPrompt([Description("Parameter description")] string content) => |
| 276 | + new(ChatRole.User, $"Prompt template: {content}"); |
| 277 | +} |
| 278 | +``` |
| 279 | +- Prompts can accept arguments to customize generated messages |
| 280 | +- Return single `ChatMessage` or `ChatMessage[]` for multi-turn prompts |
| 281 | +- Use `ChatRole.User`, `ChatRole.Assistant`, or `ChatRole.System` appropriately |
| 282 | + |
| 283 | +### Resource Implementation |
| 284 | +Resources provide access to data with URI templates: |
| 285 | +```csharp |
| 286 | +[McpServerResourceType] |
| 287 | +public class MyResources |
| 288 | +{ |
| 289 | + [McpServerResource("file:///{path}"), Description("Reads file content")] |
| 290 | + public static async Task<string> ReadFile(string path, CancellationToken cancellationToken) |
| 291 | + { |
| 292 | + // Resource URI matching uses UriTemplate syntax |
| 293 | + // Extract parameters from URI and return content |
| 294 | + } |
| 295 | +} |
| 296 | +``` |
| 297 | +- Use URI templates to define resource paths with parameters |
| 298 | +- Resources support subscription for dynamic content updates |
| 299 | +- Return content types similar to tools |
| 300 | + |
| 301 | +### Filters and Middleware |
| 302 | +Implement `McpRequestFilter` for request/response interception: |
| 303 | +```csharp |
| 304 | +public class LoggingFilter : McpRequestFilter |
| 305 | +{ |
| 306 | + public override async ValueTask InvokeAsync(RequestContext context, Func<ValueTask> next) |
| 307 | + { |
| 308 | + // Pre-processing |
| 309 | + await next(); // Call next filter or handler |
| 310 | + // Post-processing |
| 311 | + } |
| 312 | +} |
| 313 | +``` |
| 314 | +- Filters execute in registration order |
| 315 | +- Can short-circuit by not calling `next()` |
| 316 | +- Access request context, services, and can modify responses |
| 317 | +- Use for cross-cutting concerns: logging, auth, validation, caching |
| 318 | + |
| 319 | +## OpenTelemetry Integration |
| 320 | + |
| 321 | +- The SDK includes built-in observability support |
| 322 | +- Use ActivitySource name: `"Experimental.ModelContextProtocol"` |
| 323 | +- Use Meter name: `"Experimental.ModelContextProtocol"` |
| 324 | +- Export traces and metrics using OTLP when appropriate |
| 325 | + |
| 326 | +## Documentation |
| 327 | + |
| 328 | +- API documentation is generated using DocFX |
| 329 | +- Conceptual documentation is in `docs/concepts/` |
| 330 | +- Keep README files up to date in package directories |
| 331 | +- Use `///` XML comments for all public APIs |
| 332 | +- Include `<remarks>` sections for detailed explanations |
| 333 | + |
| 334 | +## Security |
| 335 | + |
| 336 | +- Never commit secrets or API keys |
| 337 | +- Use environment variables for sensitive configuration |
| 338 | +- Support authentication mechanisms (OAuth, API keys) |
| 339 | +- Validate all user inputs |
| 340 | +- Follow secure coding practices per SECURITY.md |
| 341 | + |
| 342 | +## Additional Notes |
| 343 | + |
| 344 | +- This is a preview SDK; breaking changes may occur |
| 345 | +- Follow the Model Context Protocol specification |
| 346 | +- Integrate with Microsoft.Extensions.AI patterns where applicable |
| 347 | +- Support both stdio and HTTP transports |
| 348 | +- Maintain compatibility with the broader MCP ecosystem |
0 commit comments