Skip to content

Commit 9f03dcc

Browse files
committed
Add McpServerPrompt / McpClientPrompt and friends
1 parent 62e92fa commit 9f03dcc

File tree

55 files changed

+2067
-634
lines changed

Some content is hidden

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

55 files changed

+2067
-634
lines changed

README.md

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var client = await McpClientFactory.CreateAsync(new()
4444
});
4545

4646
// Print the list of tools available from the server.
47-
await foreach (var tool in client.ListToolsAsync())
47+
foreach (var tool in await client.ListToolsAsync())
4848
{
4949
Console.WriteLine($"{tool.Name} ({tool.Description})");
5050
}
@@ -84,9 +84,9 @@ the employed overload of `WithTools` examines the current assembly for classes w
8484
`McpTool` attribute as tools.)
8585

8686
```csharp
87-
using ModelContextProtocol;
88-
using ModelContextProtocol.Server;
87+
using Microsoft.Extensions.DependencyInjection;
8988
using Microsoft.Extensions.Hosting;
89+
using ModelContextProtocol.Server;
9090
using System.ComponentModel;
9191

9292
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
@@ -96,7 +96,7 @@ builder.Services
9696
.WithToolsFromAssembly();
9797
await builder.Build().RunAsync();
9898

99-
[McpServerToolType]
99+
[McpServerType]
100100
public static class EchoTool
101101
{
102102
[McpServerTool, Description("Echoes the message back to the client.")]
@@ -109,7 +109,7 @@ the connected client. Similarly, arguments may be injected via dependency inject
109109
`IMcpServer` to make sampling requests back to the client in order to summarize content it downloads from the specified url via
110110
an `HttpClient` injected via dependency injection.
111111
```csharp
112-
[McpServerTool("SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")]
112+
[McpServerTool(Name = "SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")]
113113
public static async Task<string> SummarizeDownloadedContent(
114114
IMcpServer thisServer,
115115
HttpClient httpClient,
@@ -122,8 +122,8 @@ public static async Task<string> SummarizeDownloadedContent(
122122
[
123123
new(ChatRole.User, "Briefly summarize the following downloaded content:"),
124124
new(ChatRole.User, content),
125-
]
126-
125+
];
126+
127127
ChatOptions options = new()
128128
{
129129
MaxOutputTokens = 256,
@@ -134,13 +134,24 @@ public static async Task<string> SummarizeDownloadedContent(
134134
}
135135
```
136136

137+
Prompts can be exposed in a similar manner, using `[McpServerPrompt]`, e.g.
138+
```csharp
139+
[McpServerType]
140+
public static class MyPrompts
141+
{
142+
[McpServerPrompt, Description("Creates a prompt to summarize the provided message.")]
143+
public static ChatMessage Summarize([Description("The content to summarize")] string content) =>
144+
new(ChatRole.User, $"Please summarize this content into a single sentence: {content}");
145+
}
146+
```
147+
137148
More control is also available, with fine-grained control over configuring the server and how it should handle client requests. For example:
138149

139150
```csharp
140151
using ModelContextProtocol.Protocol.Transport;
141152
using ModelContextProtocol.Protocol.Types;
142153
using ModelContextProtocol.Server;
143-
using Microsoft.Extensions.Logging.Abstractions;
154+
using System.Text.Json;
144155

145156
McpServerOptions options = new()
146157
{
@@ -149,9 +160,8 @@ McpServerOptions options = new()
149160
{
150161
Tools = new()
151162
{
152-
ListToolsHandler = async (request, cancellationToken) =>
153-
{
154-
return new ListToolsResult()
163+
ListToolsHandler = (request, cancellationToken) =>
164+
Task.FromResult(new ListToolsResult()
155165
{
156166
Tools =
157167
[
@@ -173,10 +183,9 @@ McpServerOptions options = new()
173183
"""),
174184
}
175185
]
176-
};
177-
},
186+
}),
178187

179-
CallToolHandler = async (request, cancellationToken) =>
188+
CallToolHandler = (request, cancellationToken) =>
180189
{
181190
if (request.Params?.Name == "echo")
182191
{
@@ -185,10 +194,10 @@ McpServerOptions options = new()
185194
throw new McpServerException("Missing required argument 'message'");
186195
}
187196

188-
return new CallToolResponse()
197+
return Task.FromResult(new CallToolResponse()
189198
{
190199
Content = [new Content() { Text = $"Echo: {message}", Type = "text" }]
191-
};
200+
});
192201
}
193202

194203
throw new McpServerException($"Unknown tool: '{request.Params?.Name}'");

samples/AspNetCoreSseServer/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using ModelContextProtocol;
21
using AspNetCoreSseServer;
32

43
var builder = WebApplication.CreateBuilder(args);

samples/AspNetCoreSseServer/Tools/EchoTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace TestServerWithHosting.Tools;
55

6-
[McpServerToolType]
6+
[McpServerType]
77
public static class EchoTool
88
{
99
[McpServerTool, Description("Echoes the input back to the client.")]

samples/AspNetCoreSseServer/Tools/SampleLlmTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace TestServerWithHosting.Tools;
77
/// <summary>
88
/// This tool uses dependency injection and async method
99
/// </summary>
10-
[McpServerToolType]
10+
[McpServerType]
1111
public static class SampleLlmTool
1212
{
1313
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
Lines changed: 149 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,163 @@
1-
using Anthropic.SDK;
2-
using Microsoft.Extensions.AI;
3-
using Microsoft.Extensions.Configuration;
4-
using Microsoft.Extensions.Hosting;
5-
using ModelContextProtocol.Client;
6-
using ModelContextProtocol.Protocol.Transport;
1+
//using Anthropic.SDK;
2+
//using Microsoft.Extensions.AI;
3+
//using Microsoft.Extensions.Configuration;
4+
//using Microsoft.Extensions.Hosting;
5+
//using ModelContextProtocol.Client;
6+
//using ModelContextProtocol.Protocol.Transport;
77

8-
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
8+
//var builder = Host.CreateEmptyApplicationBuilder(settings: null);
99

10-
builder.Configuration
11-
.AddEnvironmentVariables()
12-
.AddUserSecrets<Program>();
10+
//builder.Configuration
11+
// .AddEnvironmentVariables()
12+
// .AddUserSecrets<Program>();
1313

14-
var (command, arguments) = GetCommandAndArguments(args);
14+
//var (command, arguments) = GetCommandAndArguments(args);
1515

16-
await using var mcpClient = await McpClientFactory.CreateAsync(new()
17-
{
18-
Id = "demo-server",
19-
Name = "Demo Server",
20-
TransportType = TransportTypes.StdIo,
21-
TransportOptions = new()
22-
{
23-
["command"] = command,
24-
["arguments"] = arguments,
25-
}
26-
});
16+
//await using var mcpClient = await McpClientFactory.CreateAsync(new()
17+
//{
18+
// Id = "demo-server",
19+
// Name = "Demo Server",
20+
// TransportType = TransportTypes.StdIo,
21+
// TransportOptions = new()
22+
// {
23+
// ["command"] = command,
24+
// ["arguments"] = arguments,
25+
// }
26+
//});
2727

28-
var tools = await mcpClient.ListToolsAsync();
29-
foreach (var tool in tools)
30-
{
31-
Console.WriteLine($"Connected to server with tools: {tool.Name}");
32-
}
28+
//var tools = await mcpClient.ListToolsAsync();
29+
//foreach (var tool in tools)
30+
//{
31+
// Console.WriteLine($"Connected to server with tools: {tool.Name}");
32+
//}
3333

34-
using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
35-
.Messages
36-
.AsBuilder()
37-
.UseFunctionInvocation()
38-
.Build();
34+
//using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
35+
// .Messages
36+
// .AsBuilder()
37+
// .UseFunctionInvocation()
38+
// .Build();
3939

40-
var options = new ChatOptions
41-
{
42-
MaxOutputTokens = 1000,
43-
ModelId = "claude-3-5-sonnet-20241022",
44-
Tools = [.. tools]
45-
};
40+
//var options = new ChatOptions
41+
//{
42+
// MaxOutputTokens = 1000,
43+
// ModelId = "claude-3-5-sonnet-20241022",
44+
// Tools = [.. tools]
45+
//};
4646

47-
Console.ForegroundColor = ConsoleColor.Green;
48-
Console.WriteLine("MCP Client Started!");
49-
Console.ResetColor();
47+
//Console.ForegroundColor = ConsoleColor.Green;
48+
//Console.WriteLine("MCP Client Started!");
49+
//Console.ResetColor();
5050

51-
PromptForInput();
52-
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
53-
{
54-
if (string.IsNullOrWhiteSpace(query))
55-
{
56-
PromptForInput();
57-
continue;
58-
}
51+
//PromptForInput();
52+
//while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
53+
//{
54+
// if (string.IsNullOrWhiteSpace(query))
55+
// {
56+
// PromptForInput();
57+
// continue;
58+
// }
5959

60-
await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
61-
{
62-
Console.Write(message);
63-
}
64-
Console.WriteLine();
60+
// await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
61+
// {
62+
// Console.Write(message);
63+
// }
64+
// Console.WriteLine();
6565

66-
PromptForInput();
67-
}
66+
// PromptForInput();
67+
//}
6868

69-
static void PromptForInput()
70-
{
71-
Console.WriteLine("Enter a command (or 'exit' to quit):");
72-
Console.ForegroundColor = ConsoleColor.Cyan;
73-
Console.Write("> ");
74-
Console.ResetColor();
75-
}
76-
77-
/// <summary>
78-
/// Determines the command (executable) to run and the script/path to pass to it. This allows different
79-
/// languages/runtime environments to be used as the MCP server.
80-
/// </summary>
81-
/// <remarks>
82-
/// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python,
83-
/// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet.
84-
///
85-
/// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo.
86-
///
87-
/// This method would only be required if you're creating a generic client, such as we use for the quickstart.
88-
/// </remarks>
89-
static (string command, string arguments) GetCommandAndArguments(string[] args)
69+
//static void PromptForInput()
70+
//{
71+
// Console.WriteLine("Enter a command (or 'exit' to quit):");
72+
// Console.ForegroundColor = ConsoleColor.Cyan;
73+
// Console.Write("> ");
74+
// Console.ResetColor();
75+
//}
76+
77+
///// <summary>
78+
///// Determines the command (executable) to run and the script/path to pass to it. This allows different
79+
///// languages/runtime environments to be used as the MCP server.
80+
///// </summary>
81+
///// <remarks>
82+
///// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python,
83+
///// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet.
84+
/////
85+
///// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo.
86+
/////
87+
///// This method would only be required if you're creating a generic client, such as we use for the quickstart.
88+
///// </remarks>
89+
//static (string command, string arguments) GetCommandAndArguments(string[] args)
90+
//{
91+
// return args switch
92+
// {
93+
// [var script] when script.EndsWith(".py") => ("python", script),
94+
// [var script] when script.EndsWith(".js") => ("node", script),
95+
// [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"),
96+
// _ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build")
97+
// };
98+
//}
99+
100+
using ModelContextProtocol.Protocol.Transport;
101+
using ModelContextProtocol.Protocol.Types;
102+
using ModelContextProtocol.Server;
103+
using System.Text.Json;
104+
105+
McpServerOptions options = new()
90106
{
91-
return args switch
107+
ServerInfo = new() { Name = "MyServer", Version = "1.0.0" },
108+
Capabilities = new()
92109
{
93-
[var script] when script.EndsWith(".py") => ("python", script),
94-
[var script] when script.EndsWith(".js") => ("node", script),
95-
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"),
96-
_ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build")
97-
};
98-
}
110+
Tools = new()
111+
{
112+
ListToolsHandler = (request, cancellationToken) =>
113+
Task.FromResult(new ListToolsResult()
114+
{
115+
Tools =
116+
[
117+
new Tool()
118+
{
119+
Name = "echo",
120+
Description = "Echoes the input back to the client.",
121+
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
122+
{
123+
"type": "object",
124+
"properties": {
125+
"message": {
126+
"type": "string",
127+
"description": "The input to echo back"
128+
}
129+
},
130+
"required": ["message"]
131+
}
132+
"""),
133+
}
134+
]
135+
}),
136+
137+
CallToolHandler = (request, cancellationToken) =>
138+
{
139+
if (request.Params?.Name == "echo")
140+
{
141+
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
142+
{
143+
throw new McpServerException("Missing required argument 'message'");
144+
}
145+
146+
return Task.FromResult(new CallToolResponse()
147+
{
148+
Content = [new Content() { Text = $"Echo: {message}", Type = "text" }]
149+
});
150+
}
151+
152+
throw new McpServerException($"Unknown tool: '{request.Params?.Name}'");
153+
},
154+
}
155+
},
156+
};
157+
158+
await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("MyServer"), options);
159+
160+
await server.StartAsync();
161+
162+
// Run until process is stopped by the client (parent process)
163+
await Task.Delay(Timeout.Infinite);

samples/QuickstartWeatherServer/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Microsoft.Extensions.Hosting;
3-
using ModelContextProtocol;
43
using System.Net.Http.Headers;
54

65
var builder = Host.CreateEmptyApplicationBuilder(settings: null);

0 commit comments

Comments
 (0)