Simplify client samples (#14)

- Remove duplicate projects
- Replace client sample with a simple chat app
This commit is contained in:
Stephen Toub 2025-03-20 23:25:10 -04:00 • committed by GitHub
commit 3e4dd011a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 61 additions and 304 deletions

View file

@ -7,10 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.Tests", "tests\ModelContextProtocol.Tests\ModelContextProtocol.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MEAIToolsConsole", "samples\microsoft.extensions.ai\tools\ToolsConsole\MEAIToolsConsole.csproj", "{76E295FA-9E85-7B99-332A-2CDBFCD5860A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnthropicToolsConsole", "samples\anthropic\tools\ToolsConsole\AnthropicToolsConsole.csproj", "{CA0BB450-1903-2F92-A68D-1285976551D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestServer", "tests\ModelContextProtocol.TestServer\ModelContextProtocol.TestServer.csproj", "{7C229573-A085-4ECC-8131-958CDA2BE731}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerWithHosting", "samples\TestServerWithHosting\TestServerWithHosting.csproj", "{6499876E-2F76-44A8-B6EB-5B889C6E9B7F}"
@ -47,6 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\ci.yml = .github\workflows\ci.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -61,14 +59,6 @@ Global
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.Build.0 = Release|Any CPU
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.Build.0 = Release|Any CPU
{CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.Build.0 = Release|Any CPU
{7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -85,6 +75,10 @@ Global
{B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.Build.0 = Release|Any CPU
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -92,13 +86,12 @@ Global
GlobalSection(NestedProjects) = preSolution
{12260CD2-AFFC-4B2E-8898-F442CAA1FA0F} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD}
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928}
{76E295FA-9E85-7B99-332A-2CDBFCD5860A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{CA0BB450-1903-2F92-A68D-1285976551D6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{7C229573-A085-4ECC-8131-958CDA2BE731} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928}
{6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928}
{B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}
{0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}

View file

@ -8,12 +8,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Anthropic.SDK" />
<PackageReference Include="System.Linq.AsyncEnumerable" VersionOverride="10.0.0-preview.1.25080.5" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,52 @@
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
using Microsoft.Extensions.AI;
using OpenAI;
// Connect to an MCP server
Console.WriteLine("Connecting client to MCP 'everything' server");
var mcpClient = await McpClientFactory.CreateAsync(
new()
{
Id = "everything",
Name = "Everything",
TransportType = TransportTypes.StdIo,
TransportOptions = new()
{
["command"] = "npx", ["arguments"] = "-y @modelcontextprotocol/server-everything",
}
},
new() { ClientInfo = new() { Name = "ChatClient", Version = "1.0.0" } });
// Get all available tools
Console.WriteLine("Tools available:");
var tools = await mcpClient.GetAIFunctionsAsync();
foreach (var tool in tools)
{
Console.WriteLine($" {tool}");
}
Console.WriteLine();
// Create an IChatClient. (This shows using OpenAIClient, but it could be any other IChatClient implementation.)
// Provide your own OPENAI_API_KEY via an environment variable.
using IChatClient chatClient =
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")).AsChatClient("gpt-4o-mini")
.AsBuilder().UseFunctionInvocation().Build();
// Have a conversation, making all tools available to the LLM.
List<ChatMessage> messages = [];
while (true)
{
Console.Write("Q: ");
messages.Add(new(ChatRole.User, Console.ReadLine()));
List<ChatResponseUpdate> updates = [];
await foreach (var update in chatClient.GetStreamingResponseAsync(messages, new() { Tools = [.. tools] }))
{
Console.Write(update);
updates.Add(update);
}
Console.WriteLine();
messages.AddMessages(updates);
}

View file

@ -1,41 +0,0 @@
# Samples
This directory contains example projects demonstrating how to use ModelContextProtocol with different LLM SDKs and platforms.
## Environment Setup
Before running the samples, you'll need to set up the following environment variables:
- `ANTHROPIC_API_KEY` - Required for the Anthropic sample
- `OPENAI_API_KEY` - Required for the MEAI/OpenAI sample
## Sample Projects
### Anthropic Integration
Located in `samples/anthropic`, this sample demonstrates integration with Anthropic's Claude API using ModelContextProtocol. The example shows how to:
- Configure an MCP client for use with Anthropic
- Map between MCP protocol types and Anthropic SDK types
- Handle tool invocations and responses
### Microsoft.Extensions.AI Integration
Located in `samples/microsoft.extensions.ai`, this sample shows how to use ModelContextProtocol with Microsoft's AI SDK. It demonstrates:
- Setting up an MCP client with MEAI
- Mapping MCP tools to MEAI function calls
- Handling streaming responses and tool invocations
## Implementation Notes
Each sample focuses on the mapping between MCP types and SDK-specific types, which is the primary integration point when working with different LLM SDKs. The samples provide reusable mapping code that can serve as a starting point for your own implementations.
The complexity of this mapping varies depending on how closely the SDK's design aligns with typical LLM APIs. The provided samples demonstrate best practices for both straightforward and more complex mapping scenarios.
## Running the Samples
1. Clone the repository
2. Set up the required environment variables
3. Navigate to the desired sample directory
4. Run `dotnet run` to execute the sample
For more detailed examples of ModelContextProtocol usage, you can also refer to the integration tests in the `tests/ModelContextProtocol.IntegrationTests` project.

View file

@ -1,37 +0,0 @@
using Anthropic.SDK.Common;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace ModelContextProtocol;
public static class McpToolExtensions
{
public static IList<Anthropic.SDK.Common.Tool> ToAnthropicTools(this IEnumerable<ModelContextProtocol.Protocol.Types.Tool> tools)
{
if (tools is null)
{
throw new ArgumentNullException(nameof(tools));
}
List<Anthropic.SDK.Common.Tool> result = [];
foreach (var tool in tools)
{
var function = new Function(tool.Name, tool.Description, JsonSerializer.SerializeToNode(tool.InputSchema));
result.Add(function);
}
return result;
}
public static Dictionary<string, object>? ToMCPArguments(this JsonNode jsonNode)
{
if (jsonNode == null)
return null;
// Convert JsonNode to Dictionary<string, object>
return jsonNode.AsObject()
.ToDictionary(
prop => prop.Key,
prop => JsonSerializer.Deserialize<object>(prop.Value)!
);
}
}

View file

@ -1,99 +0,0 @@
using Anthropic.SDK;
using Anthropic.SDK.Constants;
using Anthropic.SDK.Messaging;
using ModelContextProtocol;
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
internal class Program
{
private static async Task<IMcpClient> GetMcpClientAsync()
{
McpClientOptions clientOptions = new()
{
ClientInfo = new() { Name = "SimpleToolsConsole", Version = "1.0.0" }
};
McpServerConfig serverConfig = new()
{
Id = "everything",
Name = "Everything",
TransportType = TransportTypes.StdIo,
TransportOptions = new Dictionary<string, string>
{
["command"] = "npx",
["arguments"] = "-y @modelcontextprotocol/server-everything",
}
};
return await McpClientFactory.CreateAsync(serverConfig, clientOptions);
}
private static async Task Main(string[] args)
{
try
{
Console.WriteLine("Initializing MCP 'everything' server");
await using var client = await GetMcpClientAsync();
Console.WriteLine("MCP 'everything' server initialized");
Console.WriteLine("Listing tools...");
var tools = await client.ListToolsAsync().ToListAsync();
var anthropicTools = tools.ToAnthropicTools();
Console.WriteLine("Tools available:");
foreach (var tool in anthropicTools)
{
Console.WriteLine(" " + tool.Function.Name);
}
Console.WriteLine("Starting chat with Claude Haiku 3.5...");
using var antClient = new AnthropicClient(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!);
Console.WriteLine("Asking Claude to call the Echo Tool...");
List<Message> messages =
[
new Message(RoleType.User, "Please call the echo tool with the string 'Hello MCP!' and show me the echoed response.")
];
var parameters = new MessageParameters()
{
Messages = messages,
MaxTokens = 2048,
Model = AnthropicModels.Claude35Haiku,
Stream = false,
Temperature = 0.5m,
Tools = anthropicTools,
System = [new SystemMessage("You will be helping the user test MCP server tool call functionality. Remember that the user cannot see your tool calls or tool results.")]
};
// If the server provides instructions, add them as the system prompt
if (!string.IsNullOrEmpty(client.ServerInstructions))
{
parameters.System.Add(new SystemMessage(client.ServerInstructions));
}
var res = await antClient.Messages.GetClaudeMessageAsync(parameters);
messages.Add(res.Message);
foreach (var toolCall in res.ToolCalls)
{
var response = await client.CallToolAsync(toolCall.Name, toolCall.Arguments?.ToMCPArguments() ?? []);
messages.Add(new Message(toolCall, response.Content[0].Text));
}
var finalResult = await antClient.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine("Final result: " + finalResult.Message.ToString());
Console.WriteLine();
Console.WriteLine("Chat with Claude Haiku 3.5 complete");
}
catch (Exception ex)
{
Console.WriteLine("Error occurred: " + ex.Message);
}
}
}

View file

@ -1,7 +0,0 @@
{
"profiles": {
"SimpleToolsConsole": {
"commandName": "Project"
}
}
}

View file

@ -1,79 +0,0 @@
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using Microsoft.Extensions.AI;
using OpenAI;
try
{
Console.WriteLine("Initializing MCP 'everything' server");
await using var client = await GetMcpClientAsync();
Console.WriteLine("MCP 'everything' server initialized");
Console.WriteLine("Listing tools...");
var mappedTools = await client.GetAIFunctionsAsync();
Console.WriteLine("Tools available:");
foreach (var tool in mappedTools)
{
Console.WriteLine($" {tool}");
}
Console.WriteLine("Starting chat with GPT-4o-mini...");
// Note: We use then Microsoft.Extensions.AI.OpenAI client here, but it could be any other MEAI client.
// Provide your own OPENAI_API_KEY via an environment variable, secret or file-based appsettings. Do not hardcode it.
IChatClient chatClient = new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
.AsChatClient("gpt-4o-mini")
.AsBuilder()
.UseFunctionInvocation()
.Build();
// Create message list
IList<Microsoft.Extensions.AI.ChatMessage> messages =
[
// Add a system message
new(ChatRole.System, "You are a helpful assistant, helping us test MCP server functionality."),
];
// If MCP server provides instructions, add them as an additional system message (you could also add it as a content part)
if (!string.IsNullOrEmpty(client.ServerInstructions))
{
messages.Add(new(ChatRole.System, client.ServerInstructions));
}
// Add a user message
messages.Add(new(ChatRole.User, "Please call the echo tool with the string 'Hello MCP!' and give me the response as-is."));
// Call the chat client
Console.WriteLine("Asking GPT-4o-mini to call the Echo Tool...");
await foreach (var update in chatClient.GetStreamingResponseAsync(messages, new() { Tools = [.. mappedTools] }))
{
Console.Write(update);
}
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred: {ex.Message}");
}
async Task<IMcpClient> GetMcpClientAsync()
{
McpClientOptions clientOptions = new()
{
ClientInfo = new() { Name = "SimpleToolsConsole", Version = "1.0.0" }
};
McpServerConfig serverConfig = new()
{
Id = "everything",
Name = "Everything",
TransportType = TransportTypes.StdIo,
TransportOptions = new Dictionary<string, string>
{
["command"] = "npx",
["arguments"] = "-y @modelcontextprotocol/server-everything",
}
};
return await McpClientFactory.CreateAsync(serverConfig, clientOptions);
}

View file

@ -1,7 +0,0 @@
{
"profiles": {
"SimpleToolsConsole": {
"commandName": "Project"
}
}
}