diff --git a/docs/guides/mcp/chat_with_mcp.cs b/docs/guides/mcp/chat_with_mcp.cs new file mode 100644 index 000000000..620c8520e --- /dev/null +++ b/docs/guides/mcp/chat_with_mcp.cs @@ -0,0 +1,89 @@ +// SAMPLE: call the official MCP library for .NET + +#:package OpenAI@2.2.*-* +#: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 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>(); + 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 contents) + { + List 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 \ No newline at end of file