Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b8535f33-08ac-44ef-84d1-aab96770ae3a
384 lines
19 KiB
Markdown
384 lines
19 KiB
Markdown
# Copilot Instructions for MCP C# SDK
|
|
|
|
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.
|
|
|
|
## GitHub Interactions
|
|
|
|
### Commits and Pushes
|
|
- Never push to an active pull request without being explicitly asked. Always wait for explicit instructions to push.
|
|
- Never chain commit and push in the same command. Commit first, report what was committed, then wait for explicit push instructions. This creates a mandatory decision point.
|
|
|
|
### AI-Generated Content Disclosure
|
|
When posting any content to GitHub under a user's credentials — opening pull requests, creating issues, commenting on pull requests or issues, posting review comments, or taking any other public-facing action — include a concise, visible note (e.g. a `> [!NOTE]` alert) at the bottom indicating that the content was AI/Copilot-generated.
|
|
|
|
This disclosure is not required when:
|
|
- The account is a recognized bot or Copilot app account (for example, `github-actions[bot]` or `copilot`), where the AI origin is already apparent from the account identity.
|
|
- The user explicitly asks to omit the disclosure.
|
|
|
|
**Draft release notes** are a special case. Include the disclosure while the release is a draft, but
|
|
remind the user at the publishing handoff that they may remove it once they have thoroughly reviewed
|
|
and signed off on the notes — the published notes then stand as their own reviewed work. Removing it
|
|
is always the user's decision; never remove it on your own initiative, and never reintroduce it once
|
|
the user has removed it.
|
|
|
|
## Critical: Always Build and Test
|
|
|
|
**ALWAYS build and run tests before declaring any task complete or making a pull request.**
|
|
|
|
When making code changes:
|
|
1. **Build first**: Run `dotnet build` to ensure the code compiles without errors
|
|
2. **Run tests**: Execute `dotnet test` to verify all tests pass
|
|
3. **Fix issues**: Address any build errors or test failures before proceeding
|
|
4. **Verify iteratively**: Build and test frequently during development, not just at the end
|
|
5. **Check warnings**: Treat warnings as errors - the build is configured with `TreatWarningsAsErrors=true`
|
|
|
|
**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.
|
|
|
|
## Project Overview
|
|
|
|
The SDK consists of three main packages:
|
|
- **ModelContextProtocol.Core** - Client and low-level server APIs with minimal dependencies
|
|
- **ModelContextProtocol** - The main package with hosting and dependency injection extensions and which references ModelContextProtocol.Core
|
|
- **ModelContextProtocol.AspNetCore** - HTTP-based MCP server implementations for ASP.NET Core, referencing ModelContextProtocol
|
|
|
|
## C# Coding Standards
|
|
|
|
### Language Features
|
|
- Use **file-scoped namespaces** for all C# files
|
|
- Enable **implicit usings** and **nullable reference types**
|
|
- Use **preview language features** (LangVersion: preview)
|
|
- Treat warnings as errors
|
|
|
|
### Code Style
|
|
- Follow the conventions in `.editorconfig`
|
|
- Use clear, descriptive XML documentation comments for public APIs
|
|
- Follow async/await patterns consistently
|
|
- Use file-scoped namespaces: `namespace ModelContextProtocol.Client;`
|
|
|
|
### Naming Conventions
|
|
- Use `McpClient`, `McpServer`, `McpSession` for MCP-related classes (capitalize MCP)
|
|
- Prefix MCP-specific types with `Mcp` (e.g., `McpException`, `McpEndpoint`)
|
|
- Use descriptive names for parameters with `[Description("...")]` attributes when exposing to MCP
|
|
|
|
## Architecture Patterns
|
|
|
|
### Dependency Injection
|
|
- Use Microsoft.Extensions.DependencyInjection patterns
|
|
- Register services with `.AddMcpServer()` and `.AddMcpClient()` extension methods
|
|
- Support both builder patterns and options configuration
|
|
|
|
### JSON Serialization
|
|
- Use `System.Text.Json` exclusively for all JSON operations
|
|
- Use `McpJsonUtilities.DefaultOptions` for consistent serialization settings across the SDK
|
|
- Support source generation for Native AOT compatibility via `McpJsonUtilities` source generators
|
|
- Set `JsonIgnoreCondition.WhenWritingNull` for optional properties to minimize payload size
|
|
- Use `JsonSerializerDefaults.Web` for camelCase property naming
|
|
- Protocol types are decorated with `[JsonSerializable]` attributes for AOT support
|
|
- Custom converters: `CustomizableJsonStringEnumConverter` for flexible enum serialization
|
|
|
|
### Async Patterns
|
|
- All I/O operations should be async
|
|
- Use `ValueTask<T>` for hot paths that may complete synchronously
|
|
- Always accept `CancellationToken` parameters for async operations
|
|
- Name parameters consistently: `cancellationToken`
|
|
|
|
### MCP Protocol
|
|
- Follow the MCP specification at https://spec.modelcontextprotocol.io/ ([specification docs](https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/docs/specification))
|
|
- Use JSON-RPC 2.0 for message transport
|
|
- Support all standard MCP capabilities (e.g. tools, prompts, resources, sampling)
|
|
- Implement proper error handling with `McpException` and `McpErrorCode`
|
|
|
|
### Error Handling
|
|
- Throw `McpException` for MCP protocol-level errors with appropriate `McpErrorCode`
|
|
- Use standard error codes: `InvalidRequest`, `MethodNotFound`, `InvalidParams`, `InternalError`
|
|
- Let domain exceptions bubble up and convert to `InternalError` at transport boundary
|
|
- Include detailed error messages in exception `Message` property for debugging
|
|
- Errors are automatically converted to JSON-RPC error responses by the server infrastructure
|
|
|
|
## Testing
|
|
|
|
### Test Organization
|
|
- Unit tests in `tests/ModelContextProtocol.Tests` for core functionality
|
|
- Integration tests in `tests/ModelContextProtocol.AspNetCore.Tests` for HTTP/SSE transports
|
|
- Shared test utilities in `tests/Common/Utils/`
|
|
- Test servers in `tests/ModelContextProtocol.Test*Server/` for integration scenarios
|
|
- Filter manual tests with `[Trait("Execution", "Manual")]` - these require external dependencies
|
|
|
|
### Test Base Classes
|
|
- **`LoggedTest`**: Base class that wires up `ILoggerFactory` with `XunitLoggerProvider` (test output) and `MockLoggerProvider` (log assertions). Inherit from this for any test needing logging.
|
|
- **`ClientServerTestBase`**: Sets up in-memory client/server pair via `Pipe`. Override `ConfigureServices` to register tools/prompts/resources, then call `CreateMcpClientForServer()`. Handles async disposal automatically.
|
|
- **`KestrelInMemoryTest`** (AspNetCore tests): Hosts ASP.NET Core with in-memory transport — no ports needed.
|
|
- **`TestServerTransport`**: In-memory mock transport for testing client logic without a real server.
|
|
|
|
### Transport Selection in Tests
|
|
- **Never use `WithStdioServerTransport()` in unit tests.** It reads from the test host's stdin, which cannot be closed, permanently leaking a thread pool thread per test.
|
|
- For DI-only tests: `WithStreamServerTransport(Stream.Null, Stream.Null)`
|
|
- For client/server interaction: inherit `ClientServerTestBase`
|
|
- For client-only logic: use `TestServerTransport`
|
|
- For HTTP/SSE: inherit `KestrelInMemoryTest`
|
|
- For process lifecycle tests: `StdioClientTransport` (only when testing actual process behavior)
|
|
|
|
### Resource Management
|
|
- **Always `await using` the `ServiceProvider`** when MCP server services are registered — `McpServerImpl` only implements `IAsyncDisposable`. Synchronous `using` throws at runtime.
|
|
- **Always dispose clients and servers** — use `await using var client = ...`
|
|
- **Use `TestContext.Current.CancellationToken`** for async MCP calls so xUnit can cancel on timeout.
|
|
|
|
### Timeouts
|
|
- **Always use `TestConstants.DefaultTimeout`** (60s) instead of hardcoded values. CI machines are slower than dev workstations.
|
|
- For HTTP polling operations use `TestConstants.HttpClientPollingTimeout` (2s).
|
|
|
|
### Synchronization
|
|
- **Never use `Task.Delay` for synchronization.** Use `TaskCompletionSource`, `SemaphoreSlim`, or `Channel` so tests don't depend on timing.
|
|
|
|
### Background Logging
|
|
- `ITestOutputHelper.WriteLine` throws after the test method returns. Background threads (process event handlers, async continuations) can outlive the test, causing unhandled exceptions that crash the test host.
|
|
- Route logging through `LoggedTest.LoggerFactory` — `XunitLoggerProvider` already catches post-test exceptions.
|
|
- If calling `ITestOutputHelper` directly from an event handler, wrap in try/catch for `InvalidOperationException`.
|
|
|
|
### Parallelism
|
|
- Tests run in parallel by default. Apply `[Collection(nameof(DisableParallelization))]` to test classes that touch global state (e.g., `ActivitySource` listeners).
|
|
|
|
## Build and Development
|
|
|
|
### Build Commands
|
|
- **Restore**: `dotnet restore`
|
|
- **Build**: `dotnet build`
|
|
- **Test**: `dotnet test`
|
|
- **Clean**: `dotnet clean`
|
|
|
|
### Development Workflow
|
|
**Critical**: Always follow this workflow when making changes:
|
|
1. Make code changes
|
|
2. Build immediately: `dotnet build` - fix any compilation errors
|
|
3. Run tests: `dotnet test` - fix any test failures
|
|
4. Repeat steps 1-3 iteratively as you develop
|
|
5. Only after successful build and tests should you consider the change complete
|
|
|
|
Do not skip or defer building and testing. These are mandatory steps for every code change, no matter how small.
|
|
|
|
### SDK Requirements
|
|
- The repo currently requires the .NET SDK 10.0 to build and run tests.
|
|
- Target frameworks: .NET 10.0, .NET 9.0, .NET 8.0, .NET Standard 2.0
|
|
- Support Native AOT compilation
|
|
|
|
### Project Structure
|
|
- Source code: `src/`
|
|
- Tests: `tests/`
|
|
- Samples: `samples/`
|
|
- Documentation: `docs/`
|
|
- Build artifacts: `artifacts/` (not committed)
|
|
|
|
## Key Types and Architectural Layers
|
|
|
|
The SDK is organized into distinct architectural layers, each with specific responsibilities:
|
|
|
|
### Protocol Layer (DTO Types)
|
|
- Located in `ModelContextProtocol.Core/Protocol/`
|
|
- Contains Data Transfer Objects (DTOs) for the MCP specification
|
|
- All protocol types follow JSON-RPC 2.0 conventions
|
|
- Key types:
|
|
- **JsonRpcMessage** (abstract base): Represents any JSON-RPC message (request, response, notification, error)
|
|
- **JsonRpcRequest**, **JsonRpcResponse**, **JsonRpcNotification**: Concrete message types
|
|
- **Tool**, **Prompt**, **Resource**: MCP primitive definitions
|
|
- **CallToolRequestParams**, **GetPromptRequestParams**, **ReadResourceRequestParams**: Request parameter types
|
|
- **ClientCapabilities**, **ServerCapabilities**: Capability negotiation types
|
|
- **Implementation**: Server/client identification metadata
|
|
|
|
### JSON-RPC Implementation
|
|
- Built-in JSON-RPC 2.0 implementation for MCP communication
|
|
- **JsonRpcMessage.Converter**: Polymorphic converter that deserializes messages into correct types based on structure
|
|
- **JsonRpcMessageContext**: Transport-specific metadata (transport reference, execution context, authenticated user)
|
|
- Message routing handled automatically by session implementations
|
|
- Error responses generated via **McpException** with **McpErrorCode** enumeration
|
|
|
|
### Transport Abstraction
|
|
- **ITransport**: Core abstraction for bidirectional communication
|
|
- Provides `MessageReader` (ChannelReader) for incoming messages
|
|
- `SendMessageAsync()` for outgoing messages
|
|
- `SessionId` property for multi-session scenarios
|
|
- **IClientTransport**: Client-side abstraction that establishes connections and returns ITransport
|
|
- **TransportBase**: Base class for transport implementations with common functionality
|
|
|
|
### Transport Implementations
|
|
Two primary transport implementations with different invariants:
|
|
|
|
1. **Stdio-based transports** (`StdioServerTransport`, `StdioClientTransport`):
|
|
- Single-session, process-bound communication
|
|
- Uses standard input/output streams
|
|
- No session IDs (returns null)
|
|
- Automatic lifecycle tied to process
|
|
|
|
2. **HTTP-based transports**:
|
|
- **SseResponseStreamTransport**: Server-Sent Events for server-to-client streaming
|
|
- Unidirectional (server → client) event stream
|
|
- Client posts messages to separate endpoint (e.g., `/message`)
|
|
- Supports multiple concurrent sessions via SessionId
|
|
- **StreamableHttpServerTransport**: Bidirectional HTTP with streaming
|
|
- Request/response model with streamed progress updates
|
|
- Session management for concurrent connections
|
|
|
|
### Session Layer
|
|
- **McpSession** (abstract base): Core bidirectional communication for clients and servers
|
|
- Manages JSON-RPC request/response correlation
|
|
- Handles notification routing
|
|
- Provides `SendRequestAsync<TResult>()`, `SendNotificationAsync()`, `RegisterNotificationHandler()`
|
|
- Properties: `SessionId`, `NegotiatedProtocolVersion`
|
|
|
|
- **McpClient** (extends McpSession): Client-side MCP implementation
|
|
- Connects to servers via `CreateAsync(IClientTransport)`
|
|
- Exposes `ServerCapabilities`, `ServerInfo`, `ServerInstructions`
|
|
- Methods: `ListToolsAsync()`, `CallToolAsync()`, `ListPromptsAsync()`, `GetPromptAsync()`, etc.
|
|
|
|
- **McpServer** (extends McpSession): Server-side MCP implementation
|
|
- Configured via `McpServerOptions` and `IMcpServerBuilder`
|
|
- Primitives registered as services: `McpServerTool`, `McpServerPrompt`, `McpServerResource`
|
|
- Handles incoming requests through `McpServer.Methods.cs`
|
|
- Supports filters via `McpRequestFilter` for cross-cutting concerns
|
|
|
|
### Serialization Architecture
|
|
- **McpJsonUtilities.DefaultOptions**: Singleton JsonSerializerOptions for all MCP types
|
|
- Hardcoded to use source-generated serialization for JSON-RPC messages (Native AOT compatible)
|
|
- Source generation defined in `McpJsonUtilities` via `[JsonSerializable]` attributes
|
|
- Includes Microsoft.Extensions.AI types via chained TypeInfoResolver
|
|
|
|
- **User-defined types** (tool parameters, return values):
|
|
- Accept custom `JsonSerializerOptions` via `McpServerToolCreateOptions.SerializerOptions`
|
|
- Default to `McpJsonUtilities.DefaultOptions` if not specified
|
|
- Can use reflection-based serialization or custom source generators
|
|
|
|
- **Enum handling**: `CustomizableJsonStringEnumConverter` for flexible enum serialization
|
|
|
|
## Architecture and Design Patterns
|
|
|
|
### Server Implementation Architecture
|
|
- **McpServer** is the core server implementation in `ModelContextProtocol.Core/Server/`
|
|
- **IMcpServerBuilder** pattern provides fluent API for configuring servers via DI
|
|
- Server primitives (tools, prompts, resources) are discovered via reflection using attributes
|
|
- Support both attribute-based registration (`WithTools<T>()`) and instance-based (`WithTools(target)`)
|
|
- Use `McpServerFactory` to create server instances with configured options
|
|
|
|
### Tool/Prompt/Resource Discovery
|
|
- Tools, prompts, and resources use attribute-based discovery: `[McpServerTool]`, `[McpServerPrompt]`, `[McpServerResource]`
|
|
- Type-level attributes (`[McpServerToolType]`, etc.) mark classes containing server primitives
|
|
- Discovery supports both static and instance methods (public and non-public)
|
|
- For Native AOT compatibility, use generic `WithTools<T>()` methods instead of reflection-based variants
|
|
- `AIFunctionMcpServerTool`, `AIFunctionMcpServerPrompt`, and `AIFunctionMcpServerResource` wrap `AIFunction` for integration with Microsoft.Extensions.AI
|
|
|
|
### Request Processing Pipeline
|
|
- Requests flow through `McpServer.Methods.cs` which handles JSON-RPC message routing
|
|
- Use `McpRequestFilter` for cross-cutting concerns (logging, auth, validation)
|
|
- `RequestContext` provides access to current request state and services
|
|
- `RequestServiceProvider` enables scoped dependency injection per request
|
|
- Filters can short-circuit request processing or transform requests/responses
|
|
|
|
### Transport Layer Abstraction
|
|
- Transport implementations handle message serialization and connection management
|
|
- Core transports: `StdioServerTransport`, `StreamServerTransport`, `SseResponseStreamTransport`, `StreamableHttpServerTransport`
|
|
- Transports must implement bidirectional JSON-RPC message exchange
|
|
- SSE (Server-Sent Events) transport for unidirectional server→client streaming
|
|
- Streamable HTTP for request/response with streamed progress updates
|
|
|
|
## Implementation Patterns
|
|
|
|
### Tool Implementation
|
|
Tools are methods marked with `[McpServerTool]`:
|
|
```csharp
|
|
[McpServerToolType]
|
|
public class MyTools
|
|
{
|
|
[McpServerTool, Description("Tool description")]
|
|
public static async Task<string> MyTool(
|
|
[Description("Parameter description")] string param,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// Implementation - use Description attributes for parameter documentation
|
|
// Return string, TextContent, ImageContent, EmbeddedResource, or arrays of these
|
|
}
|
|
}
|
|
```
|
|
- Tools support dependency injection in constructors for instance methods
|
|
- Parameters are automatically deserialized from JSON using `System.Text.Json`
|
|
- Use `[Description]` attributes on parameters to generate tool schemas
|
|
- Return types: `string`, `TextContent`, `ImageContent`, `EmbeddedResource`, or collections of content types
|
|
|
|
### Prompt Implementation
|
|
Prompts return `ChatMessage` or arrays thereof:
|
|
```csharp
|
|
[McpServerPromptType]
|
|
public static class MyPrompts
|
|
{
|
|
[McpServerPrompt, Description("Prompt description")]
|
|
public static ChatMessage MyPrompt([Description("Parameter description")] string content) =>
|
|
new(ChatRole.User, $"Prompt template: {content}");
|
|
}
|
|
```
|
|
- Prompts can accept arguments to customize generated messages
|
|
- Return single `ChatMessage` or `ChatMessage[]` for multi-turn prompts
|
|
- Use `ChatRole.User`, `ChatRole.Assistant`, or `ChatRole.System` appropriately
|
|
|
|
### Resource Implementation
|
|
Resources provide access to data with URI templates:
|
|
```csharp
|
|
[McpServerResourceType]
|
|
public class MyResources
|
|
{
|
|
[McpServerResource("file:///{path}"), Description("Reads file content")]
|
|
public static async Task<string> ReadFile(string path, CancellationToken cancellationToken)
|
|
{
|
|
// Resource URI matching uses UriTemplate syntax
|
|
// Extract parameters from URI and return content
|
|
}
|
|
}
|
|
```
|
|
- Use URI templates to define resource paths with parameters
|
|
- Resources support subscription for dynamic content updates
|
|
- Return content types similar to tools
|
|
|
|
### Filters and Middleware
|
|
Implement `McpRequestFilter` for request/response interception:
|
|
```csharp
|
|
public class LoggingFilter : McpRequestFilter
|
|
{
|
|
public override async ValueTask InvokeAsync(RequestContext context, Func<ValueTask> next)
|
|
{
|
|
// Pre-processing
|
|
await next(); // Call next filter or handler
|
|
// Post-processing
|
|
}
|
|
}
|
|
```
|
|
- Filters execute in registration order
|
|
- Can short-circuit by not calling `next()`
|
|
- Access request context, services, and can modify responses
|
|
- Use for cross-cutting concerns: logging, auth, validation, caching
|
|
|
|
## OpenTelemetry Integration
|
|
|
|
- The SDK includes built-in observability support
|
|
- Use ActivitySource name: `"Experimental.ModelContextProtocol"`
|
|
- Use Meter name: `"Experimental.ModelContextProtocol"`
|
|
- Export traces and metrics using OTLP when appropriate
|
|
|
|
## Documentation
|
|
|
|
- API documentation is generated using DocFX
|
|
- Conceptual documentation is in `docs/concepts/`
|
|
- Keep README files up to date in package directories
|
|
- Use `///` XML comments for all public APIs
|
|
- Include `<remarks>` sections for detailed explanations
|
|
|
|
## Security
|
|
|
|
- Never commit secrets or API keys
|
|
- Use environment variables for sensitive configuration
|
|
- Support authentication mechanisms (OAuth, API keys)
|
|
- Validate all user inputs
|
|
- Follow secure coding practices per SECURITY.md
|
|
|
|
## Additional Notes
|
|
|
|
- This is a preview SDK; breaking changes may occur
|
|
- Follow the Model Context Protocol specification
|
|
- Integrate with Microsoft.Extensions.AI patterns where applicable
|
|
- Support both stdio and HTTP transports
|
|
- Maintain compatibility with the broader MCP ecosystem
|