Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/guides/mcp/chat_with_mcp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SAMPLE: call the official MCP library for .NET
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably leave some kind of comment detailing what is needed to execute this, since C#-as-a-script files are not widely used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a readme in the parent folder describing it


#:package [email protected].*-*
#:package ModelContextProtocol.Core@*-*
#:property PublishAot=false

using ModelContextProtocol;
using ModelContextProtocol.Client;
using OpenAI.Chat;

string mcpCommand = Environment.GetEnvironmentVariable("MCP_SERVER_COMMAND")!; // path to a stdio mcp server
string key = Environment.GetEnvironmentVariable("OPENAI_KEY")!;

ChatClient chatClient = new("gpt-4.1", key);

// Create MCP client to connect to the server
IMcpClient mcpClient = await McpClientFactory.CreateAsync(new StdioClientTransport(new StdioClientTransportOptions
{
Name = "MyServer",
Command = mcpCommand,
Arguments = [],
}));

// get avaliable tools and add them to completion options
ChatCompletionOptions options = new();
await foreach (McpClientTool chatTool in mcpClient.EnumerateToolsAsync())
{
options.Tools.Add(chatTool.AsOpenAIChatTool());
}

// conversation thread
List<ChatMessage> conversation = new()
{
ChatMessage.CreateUserMessage("Use an MCP tool")
};

start:
ChatCompletion chatCompletion = chatClient.CompleteChat(conversation, options);
conversation.Add(ChatMessage.CreateAssistantMessage(chatCompletion));
switch (chatCompletion.FinishReason)
{
case ChatFinishReason.ToolCalls:
foreach(var call in chatCompletion.ToolCalls)
{
var mcpArguments = call.FunctionArguments.ToObjectFromJson<Dictionary<string, object>>();
Console.WriteLine("Tool call detected, calling MCP server...");
ModelContextProtocol.Protocol.CallToolResult result = await mcpClient.CallToolAsync(call.FunctionName, mcpArguments!);
Console.WriteLine($"tool call result {result.Content[0]}");
ChatMessage message = call.ToMessage(result.Content.ToAIContents());
conversation.Add(message);
}
goto start;
case ChatFinishReason.Stop:
Console.WriteLine(chatCompletion.Content[0].Text);
break;
default:
throw new NotImplementedException();
}

#region TEMPORARY
// this is temporary. all these APIs will endup being in one of the packages used here.
public static class TemporaryExtensions
{
// this needs to be in the adapter package
public static ChatMessage ToMessage(this ChatToolCall openaiCall, IEnumerable<Microsoft.Extensions.AI.AIContent> contents)
{
List<ChatMessageContentPart> parts = new();
foreach (Microsoft.Extensions.AI.AIContent content in contents)
{
string serialized = System.Text.Json.JsonSerializer.Serialize(content.RawRepresentation);
using System.Text.Json.JsonDocument json = System.Text.Json.JsonDocument.Parse(serialized);
System.Text.Json.JsonElement text = json.RootElement.GetProperty("text");
string textValue = text.GetString() ?? string.Empty;
parts.Add(ChatMessageContentPart.CreateTextPart(textValue));
}
ToolChatMessage message = ChatMessage.CreateToolMessage(openaiCall.Id, parts);
return message;
}

// this is in the adapter package
public static ChatTool AsOpenAIChatTool(this Microsoft.Extensions.AI.AIFunction mcpTool)
{
return ChatTool.CreateFunctionTool(
mcpTool.Name,
mcpTool.Description,
BinaryData.FromString(mcpTool.JsonSchema.GetRawText()));
}
}
#endregion