124 lines
No EOL
3.9 KiB
C#
124 lines
No EOL
3.9 KiB
C#
using Anthropic.SDK;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Hosting;
|
|
using ModelContextProtocol.Client;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
var builder = Host.CreateApplicationBuilder(args);
|
|
|
|
builder.Configuration
|
|
.AddEnvironmentVariables()
|
|
.AddUserSecrets<Program>();
|
|
|
|
IClientTransport clientTransport;
|
|
var (command, arguments) = GetCommandAndArguments(args);
|
|
|
|
if (command == "http")
|
|
{
|
|
// make sure AspNetCoreMcpServer is running
|
|
clientTransport = new SseClientTransport(new()
|
|
{
|
|
Endpoint = new Uri("http://localhost:3001")
|
|
});
|
|
}
|
|
else
|
|
{
|
|
clientTransport = new StdioClientTransport(new()
|
|
{
|
|
Name = "Demo Server",
|
|
Command = command,
|
|
Arguments = arguments,
|
|
});
|
|
}
|
|
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport!);
|
|
|
|
var tools = await mcpClient.ListToolsAsync();
|
|
foreach (var tool in tools)
|
|
{
|
|
Console.WriteLine($"Connected to server with tools: {tool.Name}");
|
|
}
|
|
|
|
using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
|
|
.Messages
|
|
.AsBuilder()
|
|
.UseFunctionInvocation()
|
|
.Build();
|
|
|
|
var options = new ChatOptions
|
|
{
|
|
MaxOutputTokens = 1000,
|
|
ModelId = "claude-3-5-sonnet-20241022",
|
|
Tools = [.. tools]
|
|
};
|
|
|
|
Console.ForegroundColor = ConsoleColor.Green;
|
|
Console.WriteLine("MCP Client Started!");
|
|
Console.ResetColor();
|
|
|
|
var messages = new List<ChatMessage>();
|
|
var sb = new StringBuilder();
|
|
|
|
PromptForInput();
|
|
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
{
|
|
PromptForInput();
|
|
continue;
|
|
}
|
|
|
|
messages.Add(new ChatMessage(ChatRole.User, query));
|
|
await foreach (var message in anthropicClient.GetStreamingResponseAsync(messages, options))
|
|
{
|
|
Console.Write(message);
|
|
sb.Append(message.ToString());
|
|
}
|
|
|
|
Console.WriteLine();
|
|
sb.AppendLine();
|
|
messages.Add(new ChatMessage(ChatRole.Assistant, sb.ToString()));
|
|
sb.Clear();
|
|
|
|
PromptForInput();
|
|
}
|
|
|
|
static void PromptForInput()
|
|
{
|
|
Console.WriteLine("Enter a command (or 'exit' to quit):");
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("> ");
|
|
Console.ResetColor();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the command (executable) to run and the script/path to pass to it. This allows different
|
|
/// languages/runtime environments to be used as the MCP server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python,
|
|
/// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet.
|
|
///
|
|
/// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo.
|
|
///
|
|
/// This method would only be required if you're creating a generic client, such as we use for the quickstart.
|
|
/// </remarks>
|
|
static (string command, string[] arguments) GetCommandAndArguments(string[] args)
|
|
{
|
|
return args switch
|
|
{
|
|
[var mode] when mode.Equals("http", StringComparison.OrdinalIgnoreCase) => ("http", args),
|
|
[var script] when script.EndsWith(".py") => ("python", args),
|
|
[var script] when script.EndsWith(".js") => ("node", args),
|
|
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script]),
|
|
_ => ("dotnet", ["run", "--project", Path.Combine(GetCurrentSourceDirectory(), "../QuickstartWeatherServer")])
|
|
};
|
|
}
|
|
|
|
static string GetCurrentSourceDirectory([CallerFilePath] string? currentFile = null)
|
|
{
|
|
Debug.Assert(!string.IsNullOrWhiteSpace(currentFile));
|
|
return Path.GetDirectoryName(currentFile) ?? throw new InvalidOperationException("Unable to determine source directory.");
|
|
} |