Reorganize folders, samples, minor bug fix (#14)
Reorganize folders Samples Casing fixes
This commit is contained in:
parent
e51c78e5e4
commit
3e35efe79c
6 changed files with 501 additions and 20 deletions
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="mcpdotnet" Version="0.6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI" Version="9.1.0-preview.1.25064.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.1.0-preview.1.25064.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
using McpDotNet.Client;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using Microsoft.Extensions.AI;
|
||||
using SimpleToolsConsole;
|
||||
|
||||
namespace SimpleToolsConsole;
|
||||
|
||||
public class McpAIFunction : AIFunction
|
||||
{
|
||||
private readonly Tool _tool;
|
||||
private readonly IMcpClient _client;
|
||||
private readonly AIFunctionMetadata _metadata;
|
||||
|
||||
public McpAIFunction(Tool tool, IMcpClient client)
|
||||
{
|
||||
_tool = tool ?? throw new ArgumentNullException(nameof(tool));
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
_metadata = new AIFunctionMetadata(tool.Name)
|
||||
{
|
||||
Description = tool.Description,
|
||||
Parameters = MapFunctionParameters(tool.InputSchema),
|
||||
ReturnParameter = new AIFunctionReturnParameterMetadata()
|
||||
{
|
||||
ParameterType = typeof(string),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString() => _tool.Name;
|
||||
|
||||
protected async override Task<object?> InvokeCoreAsync(IEnumerable<KeyValuePair<string, object?>> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
// Convert arguments to dictionary format expected by mcpdotnet
|
||||
Dictionary<string, object> argDict = new();
|
||||
foreach (var arg in arguments)
|
||||
{
|
||||
if (arg.Value is not null)
|
||||
{
|
||||
argDict[arg.Key] = arg.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the tool through mcpdotnet
|
||||
var result = await _client.CallToolAsync(
|
||||
_tool.Name,
|
||||
argDict.Count == 0 ? null: argDict,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
// Extract the text content from the result
|
||||
// For simplicity in this sample, we'll just concatenate all text content
|
||||
return string.Join("\n", result.Content
|
||||
.Where(c => c.Type == "text")
|
||||
.Select(c => c.Text));
|
||||
}
|
||||
|
||||
public override AIFunctionMetadata Metadata => _metadata;
|
||||
|
||||
private static List<AIFunctionParameterMetadata> MapFunctionParameters(JsonSchema? jsonSchema)
|
||||
{
|
||||
var properties = jsonSchema?.Properties;
|
||||
if (properties == null)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
HashSet<string> requiredProperties = new(jsonSchema!.Required ?? []);
|
||||
return properties.Select(kvp =>
|
||||
new AIFunctionParameterMetadata(kvp.Key)
|
||||
{
|
||||
Description = kvp.Value.Description,
|
||||
ParameterType = McpTypeMapper.MapJsonToDotNetType(kvp.Value.Type, requiredProperties.Contains(kvp.Key)),
|
||||
IsRequired = requiredProperties.Contains(kvp.Key)
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using McpDotNet.Client;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace SimpleToolsConsole;
|
||||
|
||||
public static class McpToolExtensions
|
||||
{
|
||||
public static AITool ToAITool(this Tool tool, IMcpClient client)
|
||||
=> new McpAIFunction(tool, client);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Enable nullable reference types, as we need this for dynamic mapping of JSON schema to .NET types
|
||||
#nullable enable
|
||||
|
||||
namespace SimpleToolsConsole;
|
||||
|
||||
public static class McpTypeMapper
|
||||
{
|
||||
public static Type MapJsonToDotNetType(string jsonType, bool required)
|
||||
{
|
||||
// For simplicity, we don't handle complex types in this sample, but use object instead
|
||||
var baseType = jsonType switch
|
||||
{
|
||||
"string" => typeof(string),
|
||||
"integer" => typeof(int),
|
||||
"number" => typeof(double),
|
||||
"boolean" => typeof(bool),
|
||||
"array" => typeof(IEnumerable<object>),
|
||||
"object" => typeof(IDictionary<string, object>),
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
// If it's a value type and not required, make it nullable
|
||||
if (!required && baseType.IsValueType)
|
||||
{
|
||||
return typeof(Nullable<>).MakeGenericType(baseType);
|
||||
}
|
||||
|
||||
// Reference types are already nullable when not required
|
||||
return baseType;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
using McpDotNet.Client;
|
||||
using McpDotNet.Configuration;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using OpenAI;
|
||||
using SimpleToolsConsole;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static async Task<IMcpClient> GetMcpClientAsync()
|
||||
{
|
||||
|
||||
McpClientOptions options = new()
|
||||
{
|
||||
ClientInfo = new() { Name = "SimpleToolsConsole", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything",
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
[config],
|
||||
options,
|
||||
NullLoggerFactory.Instance
|
||||
);
|
||||
|
||||
return await factory.GetClientAsync("everything");
|
||||
}
|
||||
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Initializing MCP 'everything' server");
|
||||
var client = await GetMcpClientAsync();
|
||||
Console.WriteLine("MCP 'everything' server initialized");
|
||||
Console.WriteLine("Listing tools...");
|
||||
var tools = await client.ListToolsAsync();
|
||||
var mappedTools = tools.Tools.Select(t => t.ToAITool(client)).ToList();
|
||||
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 openaiClient = new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
|
||||
.AsChatClient("gpt-4o-mini");
|
||||
|
||||
// Note: To use the ChatClientBuilder you need to install the Microsoft.Extensions.AI package
|
||||
IChatClient chatClient = new ChatClientBuilder(openaiClient)
|
||||
.UseFunctionInvocation()
|
||||
.Build();
|
||||
|
||||
Console.WriteLine("Asking GPT-4o-mini to call the Echo Tool...");
|
||||
var response = chatClient.CompleteStreamingAsync(
|
||||
"Please call the echo tool with the string 'Hello MCP!' and give me the response as-is.",
|
||||
new() { Tools = mappedTools });
|
||||
|
||||
await foreach (var update in response)
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Chat with GPT-4o-mini complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error occurred: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"profiles": {
|
||||
"SimpleToolsConsole": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue