-
Notifications
You must be signed in to change notification settings - Fork 505
Everything server #151
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
Changes from 46 commits
618e1df
ef9ca63
fb83e20
1862fe4
393c671
42dae94
c393bdd
1ef2688
646d441
4893d70
5bbf1c2
933f9f6
b75b6bc
3203b80
b9dfc51
e3f5fa6
e5227b2
ca59b30
c53abef
d08062c
3ed3b99
7c9bcc3
a49c560
25d54a6
54c7829
f24dd18
d753183
f055547
59dadaa
07dcebb
0358a34
061316b
6889563
ddf39ee
bcb195e
cab54dd
8f318f5
20ab476
e841b95
aadf1c7
aa27fba
6ba94be
dd1ffe5
7519f90
1f25fe3
387e8fe
6428259
f61784a
3be1849
7352efb
7fece16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> |
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> currentLevel) : 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 > currentLevel()) | ||
aaronpowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
await server.SendNotificationAsync("notifications/message", message, cancellationToken: stoppingToken); | ||
} | ||
|
||
await Task.Delay(15000, stoppingToken); | ||
} | ||
} | ||
} |
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) | ||
Check failure on line 127 in samples/EverythingServer/Program.cs
|
||
{ | ||
throw new NotSupportedException($"Params are required."); | ||
} | ||
|
||
var @ref = params.Ref; | ||
Check failure on line 132 in samples/EverythingServer/Program.cs
|
||
var argument = params.Argument; | ||
Check failure on line 133 in samples/EverythingServer/Program.cs
|
||
|
||
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 McpServerException("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(); |
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)]) | ||
]; | ||
} | ||
} |
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"; | ||
} |
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; | ||
} |
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); | ||
} | ||
} | ||
} |
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}"; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.