Microsoft.Extensions.AI.Abstractions integration (#25)
* MEAI integration * README and xmldocs
This commit is contained in:
parent
502a09d9fc
commit
89c4913ed7
4 changed files with 589 additions and 1 deletions
|
|
@ -0,0 +1,95 @@
|
|||
using McpDotNet.Protocol.Types;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace McpDotNet.Extensions.AI.Tests;
|
||||
|
||||
public class AIFunctionUtilitiesTests
|
||||
{
|
||||
[Fact]
|
||||
public void MapToJsonElement_WithRequiredProperties_CreatesCorrectSchema()
|
||||
{
|
||||
// Arrange
|
||||
var tool = new Tool()
|
||||
{
|
||||
Name = "calculator",
|
||||
Description = "A simple calculator",
|
||||
InputSchema = new JsonSchema
|
||||
{
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>
|
||||
{
|
||||
["a"] = new JsonSchemaProperty { Type = "number" },
|
||||
["b"] = new JsonSchemaProperty { Type = "number" }
|
||||
},
|
||||
Required = ["a", "b"]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = AIFunctionUtilities.MapToJsonElement(tool);
|
||||
|
||||
// Assert
|
||||
var resultObj = JsonSerializer.Deserialize<JsonElement>(result.GetRawText());
|
||||
Assert.Equal("object", resultObj.GetProperty("type").GetString());
|
||||
Assert.Equal("calculator", resultObj.GetProperty("title").GetString());
|
||||
Assert.Equal("A simple calculator", resultObj.GetProperty("description").GetString());
|
||||
// Add more detailed property assertions
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToJsonElement_WithOptionalProperties_CreatesCorrectSchema()
|
||||
{
|
||||
// Arrange
|
||||
var tool = new Tool()
|
||||
{
|
||||
Name = "format",
|
||||
Description = "Text formatter",
|
||||
InputSchema = new JsonSchema
|
||||
{
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>
|
||||
{
|
||||
["text"] = new JsonSchemaProperty { Type = "string" },
|
||||
["style"] = new JsonSchemaProperty { Type = "string" }
|
||||
},
|
||||
Required = ["text"] // only text is required
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = AIFunctionUtilities.MapToJsonElement(tool);
|
||||
|
||||
// Assert
|
||||
var resultObj = JsonSerializer.Deserialize<JsonElement>(result.GetRawText());
|
||||
Assert.Equal("object", resultObj.GetProperty("type").GetString());
|
||||
Assert.Equal("format", resultObj.GetProperty("title").GetString());
|
||||
Assert.Equal("Text formatter", resultObj.GetProperty("description").GetString());
|
||||
Assert.Contains("text", resultObj.GetProperty("required").EnumerateArray().Select(x => x.GetString()));
|
||||
Assert.DoesNotContain("style", resultObj.GetProperty("required").EnumerateArray().Select(x => x.GetString()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToJsonElement_WithNullProperties_HandlesGracefully()
|
||||
{
|
||||
// Arrange
|
||||
var tool = new Tool()
|
||||
{
|
||||
Name = "minimal",
|
||||
Description = "Minimal tool",
|
||||
InputSchema = new JsonSchema
|
||||
{
|
||||
Properties = null,
|
||||
Required = null
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = AIFunctionUtilities.MapToJsonElement(tool);
|
||||
|
||||
// Assert
|
||||
var resultObj = JsonSerializer.Deserialize<JsonElement>(result.GetRawText());
|
||||
Assert.Equal("object", resultObj.GetProperty("type").GetString());
|
||||
Assert.Equal("minimal", resultObj.GetProperty("title").GetString());
|
||||
Assert.Equal("Minimal tool", resultObj.GetProperty("description").GetString());
|
||||
Assert.True(resultObj.GetProperty("properties").GetRawText() == "{}");
|
||||
Assert.Empty(resultObj.GetProperty("required").EnumerateArray());
|
||||
}
|
||||
}
|
||||
1
tests/McpDotNet.Extensions.AI.Tests/GlobalUsings.cs
Normal file
1
tests/McpDotNet.Extensions.AI.Tests/GlobalUsings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using Xunit;
|
||||
119
tests/McpDotNet.Extensions.AI.Tests/IntegrationTests.cs
Normal file
119
tests/McpDotNet.Extensions.AI.Tests/IntegrationTests.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using McpDotNet.Client;
|
||||
using McpDotNet.Configuration;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using OpenAI;
|
||||
|
||||
namespace McpDotNet.Extensions.AI.Tests;
|
||||
|
||||
public class IntegrationTests
|
||||
{
|
||||
private const string OpenAIKey = ""; // Provide your own key when running the test. Do not commit it.
|
||||
|
||||
private static McpServerConfig GetEverythingServerConfig()
|
||||
{
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything",
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static async Task<IMcpClient> GetMcpClientAsync()
|
||||
{
|
||||
McpClientOptions options = new()
|
||||
{
|
||||
ClientInfo = new() { Name = "McpDotNet.Extensions.AI.Tests", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
[GetEverythingServerConfig()],
|
||||
options,
|
||||
NullLoggerFactory.Instance
|
||||
);
|
||||
|
||||
return await factory.GetClientAsync("everything");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IntegrateWithMeai_UsingEverythingServer_ToolsAreProperlyCalled()
|
||||
{
|
||||
var client = await GetMcpClientAsync();
|
||||
var tools = await client.ListToolsAsync();
|
||||
var mappedTools = tools.Tools.Select(t => t.ToAITool(client)).ToList();
|
||||
|
||||
IChatClient openaiClient = new OpenAIClient(OpenAIKey)
|
||||
.AsChatClient("gpt-4o-mini");
|
||||
|
||||
IChatClient chatClient = new ChatClientBuilder(openaiClient)
|
||||
.UseFunctionInvocation()
|
||||
.Build();
|
||||
|
||||
// Create message list
|
||||
IList<ChatMessage> messages = new List<ChatMessage>();
|
||||
// Add a system message
|
||||
messages.Add(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 output the response ad verbatim."));
|
||||
|
||||
// Call the chat client
|
||||
Console.WriteLine("Asking GPT-4o-mini to call the Echo Tool...");
|
||||
var response = await chatClient.GetResponseAsync(
|
||||
messages,
|
||||
new() { Tools = mappedTools, Temperature = 0 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, messages.Count); // 1 system messages, 1 user message, 1 tool message, 1 response message
|
||||
Assert.Equal("Echo: Hello MCP!", response.Message.Contents[0].ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IntegrateWithMeai_UsingEverythingServer_AndSessionScope_ToolsAreProperlyCalled()
|
||||
{
|
||||
await using var sessionScope = await McpSessionScope.CreateAsync(GetEverythingServerConfig());
|
||||
|
||||
IChatClient openaiClient = new OpenAIClient(OpenAIKey)
|
||||
.AsChatClient("gpt-4o-mini");
|
||||
|
||||
IChatClient chatClient = new ChatClientBuilder(openaiClient)
|
||||
.UseFunctionInvocation()
|
||||
.Build();
|
||||
|
||||
// Create message list
|
||||
IList<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)
|
||||
foreach (var serverInstruction in sessionScope.ServerInstructions)
|
||||
{
|
||||
messages.Add(new(ChatRole.System, serverInstruction));
|
||||
}
|
||||
// Add a user message
|
||||
messages.Add(new(ChatRole.User, "Please call the echo tool with the string 'Hello MCP!' and output the response ad verbatim."));
|
||||
|
||||
// Call the chat client
|
||||
Console.WriteLine("Asking GPT-4o-mini to call the Echo Tool...");
|
||||
var response = await chatClient.GetResponseAsync(
|
||||
messages,
|
||||
new() { Tools = sessionScope.Tools, Temperature = 0 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, messages.Count); // 1 system messages, 1 user message, 1 tool message, 1 response message
|
||||
Assert.Equal("Echo: Hello MCP!", response.Message.Contents[0].ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\McpDotNet.Extensions.AI\McpDotNet.Extensions.AI.csproj" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.3.0-preview.1.25114.11" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in a new issue