Skip to content

Commit dba2129

Browse files
committed
Merge remote-tracking branch 'origin/main' into mbuck/api-experiments
2 parents cb0066d + 0cf449e commit dba2129

File tree

70 files changed

+2076
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2076
-347
lines changed

.github/workflows/ci-build-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ jobs:
5050
if: runner.os == 'Linux'
5151
run: sudo apt-get install -y mono-devel
5252

53+
- name: Setup Mono on macOS
54+
if: runner.os == 'macOS'
55+
run: brew install mono
56+
5357
- name: Set up Node.js
5458
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
5559
with:

.github/workflows/markdown-link-check.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ jobs:
2121
- name: Markup Link Checker (mlc)
2222
uses: becheran/mlc@c925f90a9a25e16e4c4bfa29058f6f9ffa9f0d8c # v0.21.0
2323
with:
24-
# Ignore external links that result in 403 errors during CI. Do not warn for redirects where we want to keep the vanity URL in the markdown or for GitHub links that redirect to the login.
25-
args: --ignore-links "https://www.anthropic.com/*,https://hackerone.com/anthropic-vdp/*" --do-not-warn-for-redirect-to "https://modelcontextprotocol.io/*,https://github.com/login?*" ./
24+
# Ignore external links that result in 403 errors during CI. Do not warn for redirects where we want to keep the vanity URL in the markdown or for GitHub links that redirect to the login, and DocFX snippet links.
25+
args: --ignore-links "https://www.anthropic.com/*,https://hackerone.com/anthropic-vdp/*" --do-not-warn-for-redirect-to "https://modelcontextprotocol.io/*,https://github.com/login?*" --ignore-links "*samples/*?name=snippet_*" ./docs

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@
3434
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
3535
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
3636
</PropertyGroup>
37+
38+
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
39+
<!-- Don't use x64 when running netfx test procs on Windows ARM -->
40+
<PreferNativeArm64>true</PreferNativeArm64>
41+
</PropertyGroup>
3742
</Project>

Directory.Packages.props

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<System9Version>9.0.5</System9Version>
55
<System10Version>10.0.0-preview.4.25258.110</System10Version>
6-
<MicrosoftExtensionsAIVersion>9.7.1</MicrosoftExtensionsAIVersion>
6+
<MicrosoftExtensionsAIVersion>9.8.0</MicrosoftExtensionsAIVersion>
77
</PropertyGroup>
88

99
<!-- Product dependencies netstandard -->
@@ -47,13 +47,13 @@
4747
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
4848

4949
<!-- Testing dependencies -->
50-
<PackageVersion Include="Anthropic.SDK" Version="5.4.1" />
50+
<PackageVersion Include="Anthropic.SDK" Version="5.5.1" />
5151
<PackageVersion Include="coverlet.collector" Version="6.0.4">
5252
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
5353
<PrivateAssets>all</PrivateAssets>
5454
</PackageVersion>
5555
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
56-
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.7.0-preview.1.25356.2" />
56+
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.8.0-preview.1.25412.6" />
5757
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System9Version)" />
5858
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System9Version)" />
5959
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System9Version)" />
@@ -66,18 +66,18 @@
6666
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
6767
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
6868
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
69-
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
70-
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
69+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
70+
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
7171
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
7272
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.1" />
7373
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
7474
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
7575
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
7676
<PackageVersion Include="Serilog" Version="4.3.0" />
7777
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="$(System10Version)" />
78-
<PackageVersion Include="xunit.v3" Version="2.0.3" />
79-
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
78+
<PackageVersion Include="xunit.v3" Version="3.0.1" />
79+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
8080
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
81-
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
81+
<PackageVersion Include="JsonSchema.Net" Version="7.4.0" />
8282
</ItemGroup>
8383
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Elicitation
3+
author: mikekistler
4+
description: Enable interactive AI experiences by requesting user input during tool execution.
5+
uid: elicitation
6+
---
7+
8+
## Elicitation
9+
10+
The **elicitation** feature allows servers to request additional information from users during interactions. This enables more dynamic and interactive AI experiences, making it easier to gather necessary context before executing tasks.
11+
12+
### Server Support for Elicitation
13+
14+
Servers request structured data from users with the [ElicitAsync] extension method on [IMcpServer].
15+
The C# SDK registers an instance of [IMcpServer] with the dependency injection container,
16+
so tools can simply add a parameter of type [IMcpServer] to their method signature to access it.
17+
18+
[ElicitAsync]: https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.Server.McpServerExtensions.html#ModelContextProtocol_Server_McpServerExtensions_ElicitAsync_ModelContextProtocol_Server_IMcpServer_ModelContextProtocol_Protocol_ElicitRequestParams_System_Threading_CancellationToken_
19+
[IMcpServer]: https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.Server.IMcpServer.html
20+
21+
The MCP Server must specify the schema of each input value it is requesting from the user.
22+
Only primitive types (string, number, boolean) are supported for elicitation requests.
23+
The schema may include a description to help the user understand what is being requested.
24+
25+
The server can request a single input or multiple inputs at once.
26+
To help distinguish multiple inputs, each input has a unique name.
27+
28+
The following example demonstrates how a server could request a boolean response from the user.
29+
30+
[!code-csharp[](samples/server/Tools/InteractiveTools.cs?name=snippet_GuessTheNumber)]
31+
32+
### Client Support for Elicitation
33+
34+
Elicitation is an optional feature so clients declare their support for it in their capabilities as part of the `initialize` request. In the MCP C# SDK, this is done by configuring an [ElicitationHandler] in the [McpClientOptions]:
35+
36+
[ElicitationHandler]: https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.Protocol.ElicitationCapability.html#ModelContextProtocol_Protocol_ElicitationCapability_ElicitationHandler
37+
[McpClientOptions]: https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.Client.McpClientOptions.html
38+
39+
[!code-csharp[](samples/client/Program.cs?name=snippet_McpInitialize)]
40+
41+
The ElicitationHandler is an asynchronous method that will be called when the server requests additional information.
42+
The ElicitationHandler must request input from the user and return the data in a format that matches the requested schema.
43+
This will be highly dependent on the client application and how it interacts with the user.
44+
45+
If the user provides the requested information, the ElicitationHandler should return an [ElicitResult] with the action set to "accept" and the content containing the user's input.
46+
If the user does not provide the requested information, the ElicitationHandler should return an [ElicitResult] with the action set to "reject" and no content.
47+
48+
[ElicitResult]: https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.Protocol.ElicitResult.html
49+
50+
Below is an example of how a console application might handle elicitation requests.
51+
Here's an example implementation:
52+
53+
[!code-csharp[](samples/client/Program.cs?name=snippet_ElicitationHandler)]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="ModelContextProtocol.Core" Version="0.3.0-preview.3" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System.Text.Json;
2+
using ModelContextProtocol.Client;
3+
using ModelContextProtocol.Protocol;
4+
5+
var endpoint = Environment.GetEnvironmentVariable("ENDPOINT") ?? "http://localhost:3001";
6+
7+
var clientTransport = new SseClientTransport(new()
8+
{
9+
Endpoint = new Uri(endpoint),
10+
TransportMode = HttpTransportMode.StreamableHttp,
11+
});
12+
13+
// <snippet_McpInitialize>
14+
McpClientOptions options = new()
15+
{
16+
ClientInfo = new()
17+
{
18+
Name = "ElicitationClient",
19+
Version = "1.0.0"
20+
},
21+
Capabilities = new()
22+
{
23+
Elicitation = new()
24+
{
25+
ElicitationHandler = HandleElicitationAsync
26+
}
27+
}
28+
};
29+
30+
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport, options);
31+
// </snippet_McpInitialize>
32+
33+
var tools = await mcpClient.ListToolsAsync();
34+
foreach (var tool in tools)
35+
{
36+
Console.WriteLine($"Connected to server with tools: {tool.Name}");
37+
}
38+
39+
Console.WriteLine($"Calling tool: {tools.First().Name}");
40+
41+
var result = await mcpClient.CallToolAsync(toolName: tools.First().Name);
42+
43+
foreach (var block in result.Content)
44+
{
45+
if (block is TextContentBlock textBlock)
46+
{
47+
Console.WriteLine(textBlock.Text);
48+
}
49+
else
50+
{
51+
Console.WriteLine($"Received unexpected result content of type {block.GetType()}");
52+
}
53+
}
54+
55+
// <snippet_ElicitationHandler>
56+
async ValueTask<ElicitResult> HandleElicitationAsync(ElicitRequestParams? requestParams, CancellationToken token)
57+
{
58+
// Bail out if the requestParams is null or if the requested schema has no properties
59+
if (requestParams?.RequestedSchema?.Properties == null)
60+
{
61+
return new ElicitResult();
62+
}
63+
64+
// Process the elicitation request
65+
if (requestParams?.Message is not null)
66+
{
67+
Console.WriteLine(requestParams.Message);
68+
}
69+
70+
var content = new Dictionary<string, JsonElement>();
71+
72+
// Loop through requestParams.requestSchema.Properties dictionary requesting values for each property
73+
foreach (var property in requestParams.RequestedSchema.Properties)
74+
{
75+
if (property.Value is ElicitRequestParams.BooleanSchema booleanSchema)
76+
{
77+
Console.Write($"{booleanSchema.Description}: ");
78+
var clientInput = Console.ReadLine();
79+
bool parsedBool;
80+
81+
// Try standard boolean parsing first
82+
if (bool.TryParse(clientInput, out parsedBool))
83+
{
84+
content[property.Key] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(parsedBool));
85+
}
86+
// Also accept "yes"/"no" as valid boolean inputs
87+
else if (string.Equals(clientInput?.Trim(), "yes", StringComparison.OrdinalIgnoreCase))
88+
{
89+
content[property.Key] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(true));
90+
}
91+
else if (string.Equals(clientInput?.Trim(), "no", StringComparison.OrdinalIgnoreCase))
92+
{
93+
content[property.Key] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(false));
94+
}
95+
}
96+
else if (property.Value is ElicitRequestParams.NumberSchema numberSchema)
97+
{
98+
Console.Write($"{numberSchema.Description}: ");
99+
var clientInput = Console.ReadLine();
100+
double parsedNumber;
101+
if (double.TryParse(clientInput, out parsedNumber))
102+
{
103+
content[property.Key] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(parsedNumber));
104+
}
105+
}
106+
else if (property.Value is ElicitRequestParams.StringSchema stringSchema)
107+
{
108+
Console.Write($"{stringSchema.Description}: ");
109+
var clientInput = Console.ReadLine();
110+
content[property.Key] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(clientInput));
111+
}
112+
}
113+
114+
// Return the user's input
115+
return new ElicitResult
116+
{
117+
Action = "accept",
118+
Content = content
119+
};
120+
}
121+
// </snippet_ElicitationHandler>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.3" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
@HostAddress = http://localhost:3001
2+
3+
# No session ID, so elicitation capabilities not declared.
4+
5+
POST {{HostAddress}}/
6+
Accept: application/json, text/event-stream
7+
Content-Type: application/json
8+
MCP-Protocol-Version: 2025-06-18
9+
10+
{
11+
"jsonrpc": "2.0",
12+
"id": 2,
13+
"method": "tools/call",
14+
"params": {
15+
"name": "guess_the_number"
16+
}
17+
}
18+
19+
###
20+
21+
POST {{HostAddress}}/
22+
Accept: application/json, text/event-stream
23+
Content-Type: application/json
24+
25+
{
26+
"jsonrpc": "2.0",
27+
"id": 1,
28+
"method": "initialize",
29+
"params": {
30+
"clientInfo": {
31+
"name": "RestClient",
32+
"version": "0.1.0"
33+
},
34+
"capabilities": {
35+
"elicitation": {}
36+
},
37+
"protocolVersion": "2025-06-18"
38+
}
39+
}
40+
41+
###
42+
43+
@SessionId = lgEu87uKTy8kLffZayO5rQ
44+
45+
POST {{HostAddress}}/
46+
Accept: application/json, text/event-stream
47+
Content-Type: application/json
48+
Mcp-Session-Id: {{SessionId}}
49+
MCP-Protocol-Version: 2025-06-18
50+
51+
{
52+
"jsonrpc": "2.0",
53+
"id": 2,
54+
"method": "tools/call",
55+
"params": {
56+
"name": "guess_the_number"
57+
}
58+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Elicitation.Tools;
2+
3+
var builder = WebApplication.CreateBuilder(args);
4+
5+
// Add services to the container.
6+
7+
builder.Services.AddMcpServer()
8+
.WithHttpTransport(options =>
9+
options.IdleTimeout = Timeout.InfiniteTimeSpan // Never timeout
10+
)
11+
.WithTools<InteractiveTools>();
12+
13+
builder.Logging.AddConsole(options =>
14+
{
15+
options.LogToStandardErrorThreshold = LogLevel.Information;
16+
});
17+
18+
var app = builder.Build();
19+
20+
app.UseHttpsRedirection();
21+
22+
app.MapMcp();
23+
24+
app.Run();

0 commit comments

Comments
 (0)