Skip to content

Commit 5a05a2b

Browse files
committed
Make Handlers get-only and initialized by default
1 parent 82b62cf commit 5a05a2b

File tree

20 files changed

+274
-346
lines changed

20 files changed

+274
-346
lines changed

README.md

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -176,52 +176,51 @@ McpServerOptions options = new()
176176
ServerInfo = new Implementation { Name = "MyServer", Version = "1.0.0" },
177177
Capabilities = new ServerCapabilities
178178
{
179-
Tools = new ToolsCapability
180-
{
181-
ListToolsHandler = (request, cancellationToken) =>
182-
ValueTask.FromResult(new ListToolsResult
183-
{
184-
Tools =
185-
[
186-
new Tool
187-
{
188-
Name = "echo",
189-
Description = "Echoes the input back to the client.",
190-
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
191-
{
192-
"type": "object",
193-
"properties": {
194-
"message": {
195-
"type": "string",
196-
"description": "The input to echo back"
197-
}
198-
},
199-
"required": ["message"]
200-
}
201-
"""),
202-
}
203-
]
204-
}),
205-
206-
CallToolHandler = (request, cancellationToken) =>
179+
Tools = new ToolsCapability(),
180+
},
181+
};
182+
183+
options.Handlers.ListToolsHandler = (request, cancellationToken) =>
184+
ValueTask.FromResult(new ListToolsResult
185+
{
186+
Tools =
187+
[
188+
new Tool
207189
{
208-
if (request.Params?.Name == "echo")
209-
{
210-
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
190+
Name = "echo",
191+
Description = "Echoes the input back to the client.",
192+
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
211193
{
212-
throw new McpException("Missing required argument 'message'");
194+
"type": "object",
195+
"properties": {
196+
"message": {
197+
"type": "string",
198+
"description": "The input to echo back"
199+
}
200+
},
201+
"required": ["message"]
213202
}
203+
"""),
204+
}
205+
]
206+
});
214207

215-
return ValueTask.FromResult(new CallToolResult
216-
{
217-
Content = [new TextContentBlock { Text = $"Echo: {message}", Type = "text" }]
218-
});
219-
}
220-
221-
throw new McpException($"Unknown tool: '{request.Params?.Name}'");
222-
},
208+
options.Handlers.CallToolHandler = (request, cancellationToken) =>
209+
{
210+
if (request.Params?.Name == "echo")
211+
{
212+
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
213+
{
214+
throw new McpException("Missing required argument 'message'");
223215
}
224-
},
216+
217+
return ValueTask.FromResult(new CallToolResult
218+
{
219+
Content = [new TextContentBlock { Text = $"Echo: {message}", Type = "text" }]
220+
});
221+
}
222+
223+
throw new McpException($"Unknown tool: '{request.Params?.Name}'");
225224
};
226225

227226
await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("MyServer"), options);

docs/concepts/elicitation/samples/client/Program.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@
1717
{
1818
Name = "ElicitationClient",
1919
Version = "1.0.0"
20-
},
21-
Handlers = new()
22-
{
23-
ElicitationHandler = HandleElicitationAsync
2420
}
2521
};
22+
options.Handlers.ElicitationHandler = HandleElicitationAsync;
2623

2724
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport, options);
2825
// </snippet_McpInitialize>

samples/ChatWithTools/Program.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,17 @@
3232
.UseOpenTelemetry(loggerFactory: loggerFactory, configure: o => o.EnableSensitiveData = true)
3333
.Build();
3434

35+
var clientOptions = new McpClientOptions();
36+
clientOptions.Handlers.SamplingHandler = samplingClient.CreateSamplingHandler();
37+
3538
var mcpClient = await McpClientFactory.CreateAsync(
3639
new StdioClientTransport(new()
3740
{
3841
Command = "npx",
3942
Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-everything"],
4043
Name = "Everything",
4144
}),
42-
clientOptions: new()
43-
{
44-
Handlers = new()
45-
{
46-
SamplingHandler = samplingClient.CreateSamplingHandler()
47-
}
48-
},
45+
clientOptions: clientOptions,
4946
loggerFactory: loggerFactory);
5047

5148
// Get all available tools

src/ModelContextProtocol.Core/Client/McpClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public McpClient(IClientTransport clientTransport, McpClientOptions? options, IL
3939

4040
EndpointName = clientTransport.Name;
4141

42-
if (options.Handlers?.NotificationHandlers is { } notificationHandlers)
42+
if (options.Handlers.NotificationHandlers is { } notificationHandlers)
4343
{
4444
NotificationHandlers.RegisterRange(notificationHandlers);
4545
}
4646

47-
if (options.Handlers?.SamplingHandler is { } samplingHandler)
47+
if (options.Handlers.SamplingHandler is { } samplingHandler)
4848
{
4949
RequestHandlers.Set(
5050
RequestMethods.SamplingCreateMessage,
@@ -59,7 +59,7 @@ public McpClient(IClientTransport clientTransport, McpClientOptions? options, IL
5959
_options.Capabilities.Sampling ??= new();
6060
}
6161

62-
if (options.Handlers?.RootsHandler is { } rootsHandler)
62+
if (options.Handlers.RootsHandler is { } rootsHandler)
6363
{
6464
RequestHandlers.Set(
6565
RequestMethods.RootsList,
@@ -71,7 +71,7 @@ public McpClient(IClientTransport clientTransport, McpClientOptions? options, IL
7171
_options.Capabilities.Roots ??= new();
7272
}
7373

74-
if (options.Handlers?.ElicitationHandler is { } elicitationHandler)
74+
if (options.Handlers.ElicitationHandler is { } elicitationHandler)
7575
{
7676
RequestHandlers.Set(
7777
RequestMethods.ElicitationCreate,

src/ModelContextProtocol.Core/Client/McpClientOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,5 @@ public sealed class McpClientOptions
6767
/// <summary>
6868
/// Gets or sets the container of handlers used by the client for processing protocol messages.
6969
/// </summary>
70-
public McpClientHandlers? Handlers { get; set; }
70+
public McpClientHandlers Handlers { get; } = new();
7171
}

src/ModelContextProtocol.Core/Server/McpServer.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory?
6868
ConfigurePing();
6969

7070
// Register any notification handlers that were provided.
71-
if (options.Handlers?.NotificationHandlers is { } notificationHandlers)
71+
if (options.Handlers.NotificationHandlers is { } notificationHandlers)
7272
{
7373
NotificationHandlers.RegisterRange(notificationHandlers);
7474
}
@@ -190,7 +190,7 @@ private void ConfigureInitialize(McpServerOptions options)
190190

191191
private void ConfigureCompletion(McpServerOptions options)
192192
{
193-
var completeHandler = options.Handlers?.CompleteHandler;
193+
var completeHandler = options.Handlers.CompleteHandler;
194194

195195
if (completeHandler is null && options.Capabilities?.Completions is null)
196196
{
@@ -216,11 +216,11 @@ private void ConfigureExperimental(McpServerOptions options)
216216

217217
private void ConfigureResources(McpServerOptions options)
218218
{
219-
var listResourcesHandler = options.Handlers?.ListResourcesHandler;
220-
var listResourceTemplatesHandler = options.Handlers?.ListResourceTemplatesHandler;
221-
var readResourceHandler = options.Handlers?.ReadResourceHandler;
222-
var subscribeHandler = options.Handlers?.SubscribeToResourcesHandler;
223-
var unsubscribeHandler = options.Handlers?.UnsubscribeFromResourcesHandler;
219+
var listResourcesHandler = options.Handlers.ListResourcesHandler;
220+
var listResourceTemplatesHandler = options.Handlers.ListResourceTemplatesHandler;
221+
var readResourceHandler = options.Handlers.ReadResourceHandler;
222+
var subscribeHandler = options.Handlers.SubscribeToResourcesHandler;
223+
var unsubscribeHandler = options.Handlers.UnsubscribeFromResourcesHandler;
224224
var resources = options.ResourceCollection;
225225
var resourcesCapability = options.Capabilities?.Resources;
226226

@@ -380,8 +380,8 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure
380380

381381
private void ConfigurePrompts(McpServerOptions options)
382382
{
383-
var listPromptsHandler = options.Handlers?.ListPromptsHandler;
384-
var getPromptHandler = options.Handlers?.GetPromptHandler;
383+
var listPromptsHandler = options.Handlers.ListPromptsHandler;
384+
var getPromptHandler = options.Handlers.GetPromptHandler;
385385
var prompts = options.PromptCollection;
386386
var promptsCapability = options.Capabilities?.Prompts;
387387

@@ -463,8 +463,8 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals
463463

464464
private void ConfigureTools(McpServerOptions options)
465465
{
466-
var listToolsHandler = options.Handlers?.ListToolsHandler;
467-
var callToolHandler = options.Handlers?.CallToolHandler;
466+
var listToolsHandler = options.Handlers.ListToolsHandler;
467+
var callToolHandler = options.Handlers.CallToolHandler;
468468
var tools = options.ToolCollection;
469469
var toolsCapability = options.Capabilities?.Tools;
470470

@@ -578,7 +578,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
578578
private void ConfigureLogging(McpServerOptions options)
579579
{
580580
// We don't require that the handler be provided, as we always store the provided log level to the server.
581-
var setLoggingLevelHandler = options.Handlers?.SetLoggingLevelHandler;
581+
var setLoggingLevelHandler = options.Handlers.SetLoggingLevelHandler;
582582

583583
// Apply filters to the handler
584584
if (setLoggingLevelHandler is not null)

src/ModelContextProtocol.Core/Server/McpServerOptions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public sealed class McpServerOptions
9090
/// </remarks>
9191
public McpServerFilters Filters { get; } = new();
9292

93+
/// <summary>
94+
/// Gets or sets the container of handlers used by the server for processing protocol messages.
95+
/// </summary>
96+
public McpServerHandlers Handlers { get; } = new();
97+
9398
/// <summary>
9499
/// Gets or sets a collection of tools served by the server.
95100
/// </summary>
@@ -139,9 +144,4 @@ public sealed class McpServerOptions
139144
/// </para>
140145
/// </remarks>
141146
public McpServerPrimitiveCollection<McpServerPrompt>? PromptCollection { get; set; }
142-
143-
/// <summary>
144-
/// Gets or sets the container of handlers used by the server for processing protocol messages.
145-
/// </summary>
146-
public McpServerHandlers? Handlers { get; set; }
147147
}

src/ModelContextProtocol/McpServerOptionsSetup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void Configure(McpServerOptions options)
8080
/// </summary>
8181
private static void OverwriteWithSetHandlers(McpServerHandlers handlers, McpServerOptions options)
8282
{
83-
McpServerHandlers optionsHandlers = options.Handlers ??= new();
83+
McpServerHandlers optionsHandlers = options.Handlers;
8484

8585
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
8686
if (handlers.ListPromptsHandler is not null || handlers.GetPromptHandler is not null)

tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -249,21 +249,16 @@ public async Task Sampling_Sse_TestServer()
249249
// Set up the sampling handler
250250
int samplingHandlerCalls = 0;
251251
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
252-
McpClientOptions options = new()
252+
McpClientOptions options = new();
253+
options.Handlers.SamplingHandler = async (_, _, _) =>
253254
{
254-
Handlers = new()
255+
samplingHandlerCalls++;
256+
return new CreateMessageResult
255257
{
256-
SamplingHandler = async (_, _, _) =>
257-
{
258-
samplingHandlerCalls++;
259-
return new CreateMessageResult
260-
{
261-
Model = "test-model",
262-
Role = Role.Assistant,
263-
Content = new TextContentBlock { Text = "Test response" },
264-
};
265-
}
266-
}
258+
Model = "test-model",
259+
Role = Role.Assistant,
260+
Content = new TextContentBlock { Text = "Test response" },
261+
};
267262
};
268263
await using var client = await GetClientAsync(options);
269264
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously

tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,26 +161,21 @@ public async Task Sampling_DoesNotCloseStream_Prematurely()
161161
await app.StartAsync(TestContext.Current.CancellationToken);
162162

163163
var sampleCount = 0;
164-
var clientOptions = new McpClientOptions
164+
var clientOptions = new McpClientOptions();
165+
clientOptions.Handlers.SamplingHandler = async (parameters, _, _) =>
165166
{
166-
Handlers = new()
167+
Assert.NotNull(parameters?.Messages);
168+
var message = Assert.Single(parameters.Messages);
169+
Assert.Equal(Role.User, message.Role);
170+
Assert.Equal("Test prompt for sampling", Assert.IsType<TextContentBlock>(message.Content).Text);
171+
172+
sampleCount++;
173+
return new CreateMessageResult
167174
{
168-
SamplingHandler = async (parameters, _, _) =>
169-
{
170-
Assert.NotNull(parameters?.Messages);
171-
var message = Assert.Single(parameters.Messages);
172-
Assert.Equal(Role.User, message.Role);
173-
Assert.Equal("Test prompt for sampling", Assert.IsType<TextContentBlock>(message.Content).Text);
174-
175-
sampleCount++;
176-
return new CreateMessageResult
177-
{
178-
Model = "test-model",
179-
Role = Role.Assistant,
180-
Content = new TextContentBlock { Text = "Sampling response from client" },
181-
};
182-
}
183-
}
175+
Model = "test-model",
176+
Role = Role.Assistant,
177+
Content = new TextContentBlock { Text = "Sampling response from client" },
178+
};
184179
};
185180

186181
await using var mcpClient = await ConnectAsync(clientOptions: clientOptions);

0 commit comments

Comments
 (0)