Skip to content

Commit 3e35efe

Browse files
authored
Reorganize folders, samples, minor bug fix (PederHP#14)
Reorganize folders Samples Casing fixes
1 parent e51c78e commit 3e35efe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+501
-20
lines changed

README.MD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ Note that you should pass CancellationToken objects suitable for your use case,
8080

8181
It is also highly recommended that you pass a proper LoggerFactory instance to the factory constructor, to enable logging of MCP client operations.
8282

83+
You can find samples demonstrating how to use mcpdotnet with an LLM SDK in the [samples](samples) directory, and also refer to the [IntegrationTests](test/McpDotNet.IntegrationTests) project for more examples.
84+
8385
Additional examples and documentation will be added as in the near future.
8486

8587
## Roadmap
@@ -89,7 +91,7 @@ Additional examples and documentation will be added as in the near future.
8991
- Transport configuration
9092
- Error handling and recovery
9193
- Increase test coverage
92-
- Add example applications
94+
- Add additional samples and examples
9395
- Performance optimization and profiling
9496

9597
## License

src/mcpdotnet.sln renamed to mcpdotnet.sln

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.13.35507.96
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet", "mcpdotnet\mcpdotnet.csproj", "{12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}"
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet", "src\mcpdotnet\mcpdotnet.csproj", "{12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet.Tests", "mcpdotnet.Tests\mcpdotnet.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}"
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet.Tests", "tests\mcpdotnet.Tests\mcpdotnet.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MEAIToolsConsole", "samples\microsoft.extensions.ai\tools\ToolsConsole\MEAIToolsConsole.csproj", "{76E295FA-9E85-7B99-332A-2CDBFCD5860A}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnthropicToolsConsole", "samples\anthropic\tools\ToolsConsole\AnthropicToolsConsole.csproj", "{CA0BB450-1903-2F92-A68D-1285976551D6}"
913
EndProject
1014
Global
1115
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,6 +25,14 @@ Global
2125
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.Build.0 = Debug|Any CPU
2226
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.ActiveCfg = Release|Any CPU
2327
{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.Build.0 = Release|Any CPU
28+
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29+
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.Build.0 = Debug|Any CPU
30+
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.ActiveCfg = Release|Any CPU
31+
{76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
35+
{CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.Build.0 = Release|Any CPU
2436
EndGlobalSection
2537
GlobalSection(SolutionProperties) = preSolution
2638
HideSolutionNode = FALSE

samples/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Samples
2+
3+
This directory contains example projects demonstrating how to use mcpdotnet with different LLM SDKs and platforms.
4+
5+
## Environment Setup
6+
7+
Before running the samples, you'll need to set up the following environment variables:
8+
9+
- `ANTHROPIC_API_KEY` - Required for the Anthropic sample
10+
- `OPENAI_API_KEY` - Required for the MEAI/OpenAI sample
11+
12+
## Sample Projects
13+
14+
### Anthropic Integration
15+
16+
Located in `samples/anthropic`, this sample demonstrates integration with Anthropic's Claude API using mcpdotnet. The example shows how to:
17+
- Configure an MCP client for use with Anthropic
18+
- Map between MCP protocol types and Anthropic SDK types
19+
- Handle tool invocations and responses
20+
21+
### Microsoft.Extensions.AI Integration
22+
23+
Located in `samples/microsoft.extensions.ai`, this sample shows how to use mcpdotnet with Microsoft's AI SDK. It demonstrates:
24+
- Setting up an MCP client with MEAI
25+
- Mapping MCP tools to MEAI function calls
26+
- Handling streaming responses and tool invocations
27+
28+
## Implementation Notes
29+
30+
Each sample focuses on the mapping between MCP types and SDK-specific types, which is the primary integration point when working with different LLM SDKs. The samples provide reusable mapping code that can serve as a starting point for your own implementations.
31+
32+
The complexity of this mapping varies depending on how closely the SDK's design aligns with typical LLM APIs. The provided samples demonstrate best practices for both straightforward and more complex mapping scenarios.
33+
34+
## Running the Samples
35+
36+
1. Clone the repository
37+
2. Set up the required environment variables
38+
3. Navigate to the desired sample directory
39+
4. Run `dotnet run` to execute the sample
40+
41+
For more detailed examples of mcpdotnet usage, you can also refer to the integration tests in the `tests/McpDotNet.IntegrationTests` project.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Anthropic.SDK" Version="4.4.3" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\..\..\src\mcpdotnet\mcpdotnet.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Anthropic.SDK.Common;
2+
using System.Text.Json;
3+
using System.Text.Json.Nodes;
4+
5+
public static class McpToolExtensions
6+
{
7+
public static IList<Anthropic.SDK.Common.Tool> ToAnthropicTools(this IEnumerable<McpDotNet.Protocol.Types.Tool> tools)
8+
{
9+
List<Anthropic.SDK.Common.Tool> result = new();
10+
foreach (var tool in tools)
11+
{
12+
var function = tool.InputSchema == null
13+
? new Function(tool.Name, tool.Description)
14+
: new Function(tool.Name, tool.Description, JsonSerializer.Serialize(tool.InputSchema));
15+
result.Add(function);
16+
}
17+
return result;
18+
}
19+
20+
public static Dictionary<string, object>? ToMCPArguments(this JsonNode jsonNode)
21+
{
22+
if (jsonNode == null)
23+
return null;
24+
25+
// Convert JsonNode to Dictionary<string, object>
26+
return jsonNode.AsObject()
27+
.ToDictionary(
28+
prop => prop.Key,
29+
prop => JsonSerializer.Deserialize<object>(prop.Value)!
30+
);
31+
}
32+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using McpDotNet.Client;
2+
using McpDotNet.Configuration;
3+
using Microsoft.Extensions.Logging.Abstractions;
4+
using Anthropic.SDK;
5+
using Anthropic.SDK.Messaging;
6+
using Anthropic.SDK.Constants;
7+
8+
internal class Program
9+
{
10+
private static async Task<IMcpClient> GetMcpClientAsync()
11+
{
12+
13+
McpClientOptions options = new()
14+
{
15+
ClientInfo = new() { Name = "SimpleToolsConsole", Version = "1.0.0" }
16+
};
17+
18+
var config = new McpServerConfig
19+
{
20+
Id = "everything",
21+
Name = "Everything",
22+
TransportType = "stdio",
23+
TransportOptions = new Dictionary<string, string>
24+
{
25+
["command"] = "npx",
26+
["arguments"] = "-y @modelcontextprotocol/server-everything",
27+
}
28+
};
29+
30+
var factory = new McpClientFactory(
31+
[config],
32+
options,
33+
NullLoggerFactory.Instance
34+
);
35+
36+
return await factory.GetClientAsync("everything");
37+
}
38+
39+
private static async Task Main(string[] args)
40+
{
41+
try
42+
{
43+
Console.WriteLine("Initializing MCP 'everything' server");
44+
var client = await GetMcpClientAsync();
45+
Console.WriteLine("MCP 'everything' server initialized");
46+
Console.WriteLine("Listing tools...");
47+
var tools = await client.ListToolsAsync();
48+
var anthropicTools = tools.Tools.ToAnthropicTools();
49+
Console.WriteLine("Tools available:");
50+
foreach (var tool in anthropicTools)
51+
{
52+
Console.WriteLine(" " + tool.Function.Name);
53+
}
54+
55+
Console.WriteLine("Starting chat with Claude Haiku 3.5...");
56+
using var antClient = new AnthropicClient(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!);
57+
58+
Console.WriteLine("Asking Claude to call the Echo Tool...");
59+
60+
var messages = new List<Message>
61+
{
62+
new Message(RoleType.User, "Please call the echo tool with the string 'Hello MCP!' and give me the response as-is.")
63+
};
64+
65+
var parameters = new MessageParameters()
66+
{
67+
Messages = messages,
68+
MaxTokens = 2048,
69+
Model = AnthropicModels.Claude35Haiku,
70+
Stream = false,
71+
Temperature = 1.0m,
72+
Tools = anthropicTools
73+
};
74+
var res = await antClient.Messages.GetClaudeMessageAsync(parameters);
75+
76+
messages.Add(res.Message);
77+
78+
foreach (var toolCall in res.ToolCalls)
79+
{
80+
var response = await client.CallToolAsync(toolCall.Name, toolCall.Arguments?.ToMCPArguments());
81+
82+
messages.Add(new Message(toolCall, response.Content[0].Text));
83+
}
84+
85+
var finalResult = await antClient.Messages.GetClaudeMessageAsync(parameters);
86+
Console.WriteLine("Final result: " + finalResult.Message.ToString());
87+
88+
Console.WriteLine();
89+
90+
Console.WriteLine("Chat with Claude Haiku 3.5 complete");
91+
}
92+
catch (Exception ex)
93+
{
94+
Console.WriteLine("Error occurred: " + ex.Message);
95+
}
96+
}
97+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"profiles": {
3+
"SimpleToolsConsole": {
4+
"commandName": "Project"
5+
}
6+
}
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="mcpdotnet" Version="0.6.0.1" />
12+
<PackageReference Include="Microsoft.Extensions.AI" Version="9.1.0-preview.1.25064.3" />
13+
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.1.0-preview.1.25064.3" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using McpDotNet.Client;
2+
using McpDotNet.Protocol.Types;
3+
using Microsoft.Extensions.AI;
4+
using SimpleToolsConsole;
5+
6+
namespace SimpleToolsConsole;
7+
8+
public class McpAIFunction : AIFunction
9+
{
10+
private readonly Tool _tool;
11+
private readonly IMcpClient _client;
12+
private readonly AIFunctionMetadata _metadata;
13+
14+
public McpAIFunction(Tool tool, IMcpClient client)
15+
{
16+
_tool = tool ?? throw new ArgumentNullException(nameof(tool));
17+
_client = client ?? throw new ArgumentNullException(nameof(client));
18+
_metadata = new AIFunctionMetadata(tool.Name)
19+
{
20+
Description = tool.Description,
21+
Parameters = MapFunctionParameters(tool.InputSchema),
22+
ReturnParameter = new AIFunctionReturnParameterMetadata()
23+
{
24+
ParameterType = typeof(string),
25+
}
26+
};
27+
}
28+
29+
public override string ToString() => _tool.Name;
30+
31+
protected async override Task<object?> InvokeCoreAsync(IEnumerable<KeyValuePair<string, object?>> arguments, CancellationToken cancellationToken)
32+
{
33+
// Convert arguments to dictionary format expected by mcpdotnet
34+
Dictionary<string, object> argDict = new();
35+
foreach (var arg in arguments)
36+
{
37+
if (arg.Value is not null)
38+
{
39+
argDict[arg.Key] = arg.Value;
40+
}
41+
}
42+
43+
// Call the tool through mcpdotnet
44+
var result = await _client.CallToolAsync(
45+
_tool.Name,
46+
argDict.Count == 0 ? null: argDict,
47+
cancellationToken: cancellationToken
48+
);
49+
50+
// Extract the text content from the result
51+
// For simplicity in this sample, we'll just concatenate all text content
52+
return string.Join("\n", result.Content
53+
.Where(c => c.Type == "text")
54+
.Select(c => c.Text));
55+
}
56+
57+
public override AIFunctionMetadata Metadata => _metadata;
58+
59+
private static List<AIFunctionParameterMetadata> MapFunctionParameters(JsonSchema? jsonSchema)
60+
{
61+
var properties = jsonSchema?.Properties;
62+
if (properties == null)
63+
{
64+
return new();
65+
}
66+
HashSet<string> requiredProperties = new(jsonSchema!.Required ?? []);
67+
return properties.Select(kvp =>
68+
new AIFunctionParameterMetadata(kvp.Key)
69+
{
70+
Description = kvp.Value.Description,
71+
ParameterType = McpTypeMapper.MapJsonToDotNetType(kvp.Value.Type, requiredProperties.Contains(kvp.Key)),
72+
IsRequired = requiredProperties.Contains(kvp.Key)
73+
}).ToList();
74+
}
75+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using McpDotNet.Client;
2+
using McpDotNet.Protocol.Types;
3+
using Microsoft.Extensions.AI;
4+
5+
namespace SimpleToolsConsole;
6+
7+
public static class McpToolExtensions
8+
{
9+
public static AITool ToAITool(this Tool tool, IMcpClient client)
10+
=> new McpAIFunction(tool, client);
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Enable nullable reference types, as we need this for dynamic mapping of JSON schema to .NET types
2+
#nullable enable
3+
4+
namespace SimpleToolsConsole;
5+
6+
public static class McpTypeMapper
7+
{
8+
public static Type MapJsonToDotNetType(string jsonType, bool required)
9+
{
10+
// For simplicity, we don't handle complex types in this sample, but use object instead
11+
var baseType = jsonType switch
12+
{
13+
"string" => typeof(string),
14+
"integer" => typeof(int),
15+
"number" => typeof(double),
16+
"boolean" => typeof(bool),
17+
"array" => typeof(IEnumerable<object>),
18+
"object" => typeof(IDictionary<string, object>),
19+
_ => typeof(object)
20+
};
21+
22+
// If it's a value type and not required, make it nullable
23+
if (!required && baseType.IsValueType)
24+
{
25+
return typeof(Nullable<>).MakeGenericType(baseType);
26+
}
27+
28+
// Reference types are already nullable when not required
29+
return baseType;
30+
}
31+
}

0 commit comments

Comments
 (0)