Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Everything server #151

Merged
merged 51 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
618e1df
wip of server-everything
aaronpowell Mar 24, 2025
ef9ca63
Sharing the tiny image
aaronpowell Mar 23, 2025
fb83e20
Cleanup from bad merge
aaronpowell Mar 24, 2025
1862fe4
Adding a second call tool handler
aaronpowell Mar 24, 2025
393c671
Fixing broken props file
aaronpowell Mar 24, 2025
42dae94
Merge branch 'main' into dotnet-everything
aaronpowell Mar 25, 2025
c393bdd
Merge branch 'main' into dotnet-everything
aaronpowell Mar 25, 2025
1ef2688
Updating to latest API design
aaronpowell Mar 25, 2025
646d441
Merge branch 'main' into dotnet-everything
aaronpowell Mar 27, 2025
4893d70
WIP
aaronpowell Mar 27, 2025
5bbf1c2
Adding support for returning collections from tools
aaronpowell Mar 27, 2025
933f9f6
Merge branch 'more-tool-return-support' into dotnet-everything
aaronpowell Mar 27, 2025
b75b6bc
Updating
aaronpowell Mar 27, 2025
3203b80
Merge branch 'main' into dotnet-everything
aaronpowell Mar 27, 2025
b9dfc51
Completing the tools as best as possible
aaronpowell Mar 27, 2025
e3f5fa6
Working on improvements to resources
aaronpowell Mar 28, 2025
e5227b2
WIP
aaronpowell Mar 28, 2025
ca59b30
Supporting resource templates with read resource callback
aaronpowell Mar 28, 2025
c53abef
Aligning the resource data structures with the 2024-11-05 schema
aaronpowell Mar 28, 2025
d08062c
Apply suggestions from code review
aaronpowell Mar 28, 2025
3ed3b99
Putting files back where they belong
aaronpowell Mar 28, 2025
7c9bcc3
Merge branch 'main' into improving-resources
aaronpowell Mar 30, 2025
a49c560
Adding an internal constructor to prevent overloads
aaronpowell Mar 30, 2025
25d54a6
Addressing feedback
aaronpowell Mar 30, 2025
54c7829
Apply suggestions from code review
aaronpowell Mar 31, 2025
f24dd18
Update src/ModelContextProtocol/Protocol/Types/ResourceContents.cs
stephentoub Mar 31, 2025
d753183
Addressing feedback
aaronpowell Mar 31, 2025
f055547
Merge branch 'improving-resources' of https://github.com/aaronpowell/…
aaronpowell Mar 31, 2025
59dadaa
Merge branch 'main' into improving-resources
aaronpowell Mar 31, 2025
07dcebb
Merge branch 'improving-resources' into dotnet-everything
aaronpowell Mar 31, 2025
0358a34
Updating from merge
aaronpowell Mar 31, 2025
061316b
Handling complex prompt properly
aaronpowell Mar 31, 2025
6889563
Adding subscriptions
aaronpowell Mar 31, 2025
ddf39ee
Adding completion handler
aaronpowell Mar 31, 2025
bcb195e
Merge branch 'main' into dotnet-everything
aaronpowell Mar 31, 2025
cab54dd
Moving to using the PromptType attribute
aaronpowell Apr 1, 2025
8f318f5
Implementing annotated tool call
aaronpowell Apr 1, 2025
20ab476
Improving how to setup logging and implementing logging handler
aaronpowell Apr 1, 2025
e841b95
Merge branch 'main' into dotnet-everything
aaronpowell Apr 1, 2025
aadf1c7
Apply suggestions from code review
aaronpowell Apr 2, 2025
aa27fba
Merge branch 'main' into dotnet-everything
aaronpowell Apr 2, 2025
6ba94be
Fixing build errors
aaronpowell Apr 2, 2025
dd1ffe5
Converting to proper background services
aaronpowell Apr 2, 2025
7519f90
Adding tests for setlogginghandler
aaronpowell Apr 2, 2025
1f25fe3
Fixing primary constructor usgae
aaronpowell Apr 3, 2025
387e8fe
Apply suggestions from code review
aaronpowell Apr 3, 2025
6428259
Apply suggestions from code review
aaronpowell Apr 3, 2025
f61784a
Fixing compiler error
aaronpowell Apr 3, 2025
3be1849
Merge branch 'main' into dotnet-everything
aaronpowell Apr 4, 2025
7352efb
Merge branch 'main' into dotnet-everything
stephentoub Apr 4, 2025
7fece16
Fix McpServerException rename
stephentoub Apr 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ModelContextProtocol.sln
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartWeatherServer", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartClient", "samples\QuickstartClient\QuickstartClient.csproj", "{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EverythingServer", "samples\EverythingServer\EverythingServer.csproj", "{17B8453F-AB72-99C5-E5EA-D0B065A6AE65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.AspNetCore", "src\ModelContextProtocol.AspNetCore\ModelContextProtocol.AspNetCore.csproj", "{37B6A5E0-9995-497D-8B43-3BC6870CC716}"
EndProject
Global
Expand Down Expand Up @@ -94,6 +96,10 @@ Global
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.Build.0 = Release|Any CPU
{17B8453F-AB72-99C5-E5EA-D0B065A6AE65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17B8453F-AB72-99C5-E5EA-D0B065A6AE65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17B8453F-AB72-99C5-E5EA-D0B065A6AE65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17B8453F-AB72-99C5-E5EA-D0B065A6AE65}.Release|Any CPU.Build.0 = Release|Any CPU
{37B6A5E0-9995-497D-8B43-3BC6870CC716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37B6A5E0-9995-497D-8B43-3BC6870CC716}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37B6A5E0-9995-497D-8B43-3BC6870CC716}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -113,6 +119,7 @@ Global
{0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{17B8453F-AB72-99C5-E5EA-D0B065A6AE65} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{37B6A5E0-9995-497D-8B43-3BC6870CC716} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
18 changes: 18 additions & 0 deletions samples/EverythingServer/EverythingServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
</ItemGroup>

</Project>
43 changes: 43 additions & 0 deletions samples/EverythingServer/LoggingUpdateMessageSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;

namespace EverythingServer;

public class LoggingUpdateMessageSender(IMcpServer server, Func<LoggingLevel> getMinLevel) : BackgroundService
{
readonly Dictionary<LoggingLevel, string> _loggingLevelMap = new()
{
{ LoggingLevel.Debug, "Debug-level message" },
{ LoggingLevel.Info, "Info-level message" },
{ LoggingLevel.Notice, "Notice-level message" },
{ LoggingLevel.Warning, "Warning-level message" },
{ LoggingLevel.Error, "Error-level message" },
{ LoggingLevel.Critical, "Critical-level message" },
{ LoggingLevel.Alert, "Alert-level message" },
{ LoggingLevel.Emergency, "Emergency-level message" }
};

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count);

var message = new
{
Level = newLevel.ToString().ToLower(),
Data = _loggingLevelMap[newLevel],
};

if (newLevel > getMinLevel())
{
await server.SendNotificationAsync("notifications/message", message, cancellationToken: stoppingToken);
}

await Task.Delay(15000, stoppingToken);
}
}
}
194 changes: 194 additions & 0 deletions samples/EverythingServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using EverythingServer;
using EverythingServer.Prompts;
using EverythingServer.Tools;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

HashSet<string> subscriptions = [];
var _minimumLoggingLevel = LoggingLevel.Debug;

builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<AddTool>()
.WithTools<AnnotatedMessageTool>()
.WithTools<EchoTool>()
.WithTools<LongRunningTool>()
.WithTools<PrintEnvTool>()
.WithTools<SampleLlmTool>()
.WithTools<TinyImageTool>()
.WithPrompts<ComplexPromptType>()
.WithPrompts<SimplePromptType>()
.WithListResourceTemplatesHandler((ctx, ct) =>
{
return Task.FromResult(new ListResourceTemplatesResult
{
ResourceTemplates =
[
new ResourceTemplate { Name = "Static Resource", Description = "A static resource with a numeric ID", UriTemplate = "test://static/resource/{id}" }
]
});
})
.WithReadResourceHandler((ctx, ct) =>
{
var uri = ctx.Params?.Uri;

if (uri is null || !uri.StartsWith("test://static/resource/"))
{
throw new NotSupportedException($"Unknown resource: {uri}");
}

int index = int.Parse(uri["test://static/resource/".Length..]) - 1;

if (index < 0 || index >= ResourceGenerator.Resources.Count)
{
throw new NotSupportedException($"Unknown resource: {uri}");
}

var resource = ResourceGenerator.Resources[index];

if (resource.MimeType == "text/plain")
{
return Task.FromResult(new ReadResourceResult
{
Contents = [new TextResourceContents
{
Text = resource.Description!,
MimeType = resource.MimeType,
Uri = resource.Uri,
}]
});
}
else
{
return Task.FromResult(new ReadResourceResult
{
Contents = [new BlobResourceContents
{
Blob = resource.Description!,
MimeType = resource.MimeType,
Uri = resource.Uri,
}]
});
}
})
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
{
var uri = ctx.Params?.Uri;

if (uri is not null)
{
subscriptions.Add(uri);

await ctx.Server.RequestSamplingAsync([
new ChatMessage(ChatRole.System, "You are a helpful test server"),
new ChatMessage(ChatRole.User, $"Resource {uri}, context: A new subscription was started"),
],
options: new ChatOptions
{
MaxOutputTokens = 100,
Temperature = 0.7f,
},
cancellationToken: ct);
}

return new EmptyResult();
})
.WithUnsubscribeFromResourcesHandler((ctx, ct) =>
{
var uri = ctx.Params?.Uri;
if (uri is not null)
{
subscriptions.Remove(uri);
}
return Task.FromResult(new EmptyResult());
})
.WithGetCompletionHandler((ctx, ct) =>
{
var exampleCompletions = new Dictionary<string, IEnumerable<string>>
{
{ "style", ["casual", "formal", "technical", "friendly"] },
{ "temperature", ["0", "0.5", "0.7", "1.0"] },
{ "resourceId", ["1", "2", "3", "4", "5"] }
};

if (ctx.Params is not { } @params)
{
throw new NotSupportedException($"Params are required.");
}

var @ref = @params.Ref;
var argument = @params.Argument;

if (@ref.Type == "ref/resource")
{
var resourceId = @ref.Uri?.Split("/").Last();

if (resourceId is null)
{
return Task.FromResult(new CompleteResult());
}

var values = exampleCompletions["resourceId"].Where(id => id.StartsWith(argument.Value));

return Task.FromResult(new CompleteResult
{
Completion = new Completion { Values = [..values], HasMore = false, Total = values.Count() }
});
}

if (@ref.Type == "ref/prompt")
{
if (!exampleCompletions.TryGetValue(argument.Name, out IEnumerable<string>? value))
{
throw new NotSupportedException($"Unknown argument name: {argument.Name}");
}

var values = value.Where(value => value.StartsWith(argument.Value));
return Task.FromResult(new CompleteResult
{
Completion = new Completion { Values = [..values], HasMore = false, Total = values.Count() }
});
}

throw new NotSupportedException($"Unknown reference type: {@ref.Type}");
})
.WithSetLoggingLevelHandler(async (ctx, ct) =>
{
if (ctx.Params?.Level is null)
{
throw new McpException("Missing required argument 'level'");
}

_minimumLoggingLevel = ctx.Params.Level;

await ctx.Server.SendNotificationAsync("notifications/message", new
{
Level = "debug",
Logger = "test-server",
Data = $"Logging level set to {_minimumLoggingLevel}",
}, cancellationToken: ct);

return new EmptyResult();
})
;

builder.Services.AddSingleton(subscriptions);
builder.Services.AddHostedService<SubscriptionMessageSender>();
builder.Services.AddHostedService<LoggingUpdateMessageSender>();

builder.Services.AddSingleton<Func<LoggingLevel>>(_ => () => _minimumLoggingLevel);

await builder.Build().RunAsync();
22 changes: 22 additions & 0 deletions samples/EverythingServer/Prompts/ComplexPromptType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using EverythingServer.Tools;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Prompts;

[McpServerPromptType]
public class ComplexPromptType
{
[McpServerPrompt(Name = "complex_prompt"), Description("A prompt with arguments")]
public static IEnumerable<ChatMessage> ComplexPrompt(
[Description("Temperature setting")] int temperature,
[Description("Output style")] string? style = null)
{
return [
new ChatMessage(ChatRole.User,$"This is a complex prompt with arguments: temperature={temperature}, style={style}"),
new ChatMessage(ChatRole.Assistant, "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?"),
new ChatMessage(ChatRole.User, [new DataContent(TinyImageTool.MCP_TINY_IMAGE)])
];
}
}
11 changes: 11 additions & 0 deletions samples/EverythingServer/Prompts/SimplePromptType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Prompts;

[McpServerPromptType]
public class SimplePromptType
{
[McpServerPrompt(Name = "simple_prompt"), Description("A prompt without arguments")]
public static string SimplePrompt() => "This is a simple prompt without arguments";
}
37 changes: 37 additions & 0 deletions samples/EverythingServer/ResourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using ModelContextProtocol.Protocol.Types;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EverythingServer;

static class ResourceGenerator
{
private static readonly List<Resource> _resources = Enumerable.Range(1, 100).Select(i =>
{
var uri = $"test://static/resource/{i}";
if (i % 2 != 0)
{
return new Resource
{
Uri = uri,
Name = $"Resource {i}",
MimeType = "text/plain",
Description = $"Resource {i}: This is a plaintext resource"
};
}
else
{
var buffer = System.Text.Encoding.UTF8.GetBytes($"Resource {i}: This is a base64 blob");
return new Resource
{
Uri = uri,
Name = $"Resource {i}",
MimeType = "application/octet-stream",
Description = Convert.ToBase64String(buffer)
};
}
}).ToList();

public static IReadOnlyList<Resource> Resources => _resources;
}
23 changes: 23 additions & 0 deletions samples/EverythingServer/SubscriptionMessageSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;
using ModelContextProtocol.Server;

internal class SubscriptionMessageSender(IMcpServer server, HashSet<string> subscriptions) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foreach (var uri in subscriptions)
{
await server.SendNotificationAsync("notifications/resource/updated",
new
{
Uri = uri,
}, cancellationToken: stoppingToken);
}

await Task.Delay(5000, stoppingToken);
}
}
}
11 changes: 11 additions & 0 deletions samples/EverythingServer/Tools/AddTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace EverythingServer.Tools;

[McpServerToolType]
public class AddTool
{
[McpServerTool(Name = "add"), Description("Adds two numbers.")]
public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}";
}
Loading