From 618e1df923ea64988eaecd9cbfd47267c545f85c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 24 Mar 2025 13:14:43 +1100 Subject: [PATCH 01/36] wip of server-everything --- Directory.Packages.props | 7 +- ModelContextProtocol.sln | 212 ++++++++++-------- .../EverythingServer/EverythingServer.csproj | 18 ++ samples/EverythingServer/Program.cs | 47 ++++ samples/EverythingServer/Tools/AddTool.cs | 11 + .../Tools/AnnotatedMessageTool.cs | 21 ++ samples/EverythingServer/Tools/EchoTool.cs | 11 + .../EverythingServer/Tools/LongRunningTool.cs | 28 +++ .../EverythingServer/Tools/PrintEnvTool.cs | 23 ++ .../EverythingServer/Tools/SampleLlmTool.cs | 43 ++++ .../EverythingServer/Tools/TinyImageTool.cs | 19 ++ 11 files changed, 337 insertions(+), 103 deletions(-) create mode 100644 samples/EverythingServer/EverythingServer.csproj create mode 100644 samples/EverythingServer/Program.cs create mode 100644 samples/EverythingServer/Tools/AddTool.cs create mode 100644 samples/EverythingServer/Tools/AnnotatedMessageTool.cs create mode 100644 samples/EverythingServer/Tools/EchoTool.cs create mode 100644 samples/EverythingServer/Tools/LongRunningTool.cs create mode 100644 samples/EverythingServer/Tools/PrintEnvTool.cs create mode 100644 samples/EverythingServer/Tools/SampleLlmTool.cs create mode 100644 samples/EverythingServer/Tools/TinyImageTool.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 852f63a2..ffd7cd0d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,17 +13,16 @@ - + - - + - + diff --git a/ModelContextProtocol.sln b/ModelContextProtocol.sln index c0d470ef..e5781b3f 100644 --- a/ModelContextProtocol.sln +++ b/ModelContextProtocol.sln @@ -1,99 +1,113 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35507.96 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol", "src\ModelContextProtocol\ModelContextProtocol.csproj", "{12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.Tests", "tests\ModelContextProtocol.Tests\ModelContextProtocol.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestServer", "tests\ModelContextProtocol.TestServer\ModelContextProtocol.TestServer.csproj", "{7C229573-A085-4ECC-8131-958CDA2BE731}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerWithHosting", "samples\TestServerWithHosting\TestServerWithHosting.csproj", "{6499876E-2F76-44A8-B6EB-5B889C6E9B7F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2A77AF5C-138A-4EBB-9A13-9205DCD67928}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestSseServer", "tests\ModelContextProtocol.TestSseServer\ModelContextProtocol.TestSseServer.csproj", "{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreSseServer", "samples\AspNetCoreSseServer\AspNetCoreSseServer.csproj", "{B6F42305-423F-56FF-090F-B7263547F924}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD}" - ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B6FB2B28-D5DE-4654-BE9A-45E305DE4852}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - global.json = global.json - LICENSE = LICENSE - logo.png = logo.png - nuget.config = nuget.config - README.MD = README.MD - version.json = version.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{20AACB9B-307D-419C-BCC6-1C639C402295}" - ProjectSection(SolutionItems) = preProject - .github\workflows\ci.yml = .github\workflows\ci.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Release|Any CPU.Build.0 = Release|Any CPU - {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.Build.0 = Release|Any CPU - {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.Build.0 = Release|Any CPU - {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.Build.0 = Release|Any CPU - {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.Build.0 = Release|Any CPU - {B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.Build.0 = Release|Any CPU - {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD} - {FF41F619-833D-4FA2-8C53-04D0A1D5AA61} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} - {7C229573-A085-4ECC-8131-958CDA2BE731} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} - {6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} - {B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40} - {0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35507.96 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol", "src\ModelContextProtocol\ModelContextProtocol.csproj", "{12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.Tests", "tests\ModelContextProtocol.Tests\ModelContextProtocol.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MEAIToolsConsole", "samples\microsoft.extensions.ai\tools\ToolsConsole\MEAIToolsConsole.csproj", "{76E295FA-9E85-7B99-332A-2CDBFCD5860A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnthropicToolsConsole", "samples\anthropic\tools\ToolsConsole\AnthropicToolsConsole.csproj", "{CA0BB450-1903-2F92-A68D-1285976551D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestServer", "tests\ModelContextProtocol.TestServer\ModelContextProtocol.TestServer.csproj", "{7C229573-A085-4ECC-8131-958CDA2BE731}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerWithHosting", "samples\TestServerWithHosting\TestServerWithHosting.csproj", "{6499876E-2F76-44A8-B6EB-5B889C6E9B7F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2A77AF5C-138A-4EBB-9A13-9205DCD67928}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestSseServer", "tests\ModelContextProtocol.TestSseServer\ModelContextProtocol.TestSseServer.csproj", "{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreSseServer", "samples\AspNetCoreSseServer\AspNetCoreSseServer.csproj", "{B6F42305-423F-56FF-090F-B7263547F924}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B6FB2B28-D5DE-4654-BE9A-45E305DE4852}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + global.json = global.json + LICENSE = LICENSE + logo.png = logo.png + nuget.config = nuget.config + README.MD = README.MD + version.json = version.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{20AACB9B-307D-419C-BCC6-1C639C402295}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci.yml = .github\workflows\ci.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EverythingServer", "samples\EverythingServer\EverythingServer.csproj", "{2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F}.Release|Any CPU.Build.0 = Release|Any CPU + {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.Build.0 = Release|Any CPU + {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.Build.0 = Release|Any CPU + {CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.Build.0 = Release|Any CPU + {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.Build.0 = Release|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.Build.0 = Release|Any CPU + {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.Build.0 = Release|Any CPU + {B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.Build.0 = Release|Any CPU + {2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD} + {FF41F619-833D-4FA2-8C53-04D0A1D5AA61} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} + {76E295FA-9E85-7B99-332A-2CDBFCD5860A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {CA0BB450-1903-2F92-A68D-1285976551D6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {7C229573-A085-4ECC-8131-958CDA2BE731} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} + {B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40} + {2EEA6B5B-2AD3-4C7D-A2FF-138A864FA755} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89} + EndGlobalSection +EndGlobal diff --git a/samples/EverythingServer/EverythingServer.csproj b/samples/EverythingServer/EverythingServer.csproj new file mode 100644 index 00000000..556d896e --- /dev/null +++ b/samples/EverythingServer/EverythingServer.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + Exe + + + + + + + + + + + diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs new file mode 100644 index 00000000..74046795 --- /dev/null +++ b/samples/EverythingServer/Program.cs @@ -0,0 +1,47 @@ +using ModelContextProtocol; +using Microsoft.Extensions.Hosting; +using ModelContextProtocol.Protocol.Types; +using EverythingServer.Tools; + +var builder = Host.CreateEmptyApplicationBuilder(settings: null); + +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithTools() + .WithListPromptsHandler((ctx, ct) => + { + return Task.FromResult(new ListPromptsResult + { + Prompts = + [ + new Prompt { Name= "simple_prompt", Description = "A prompt without arguments" }, + new Prompt { Name= "complex_prompt", Description = "A prompt with arguments", Arguments = [ + new PromptArgument { Name = "temperature", Description = "Temperature setting", Required = true }, + new PromptArgument { Name = "style", Description = "Output style", Required = false} + ] + } + ] + }); + }) + .WithGetPromptHandler((args, ct) => + { + List messages = args.Params?.Name switch + { + "simple_prompt" => [new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = "This is a simple prompt without arguments" } }], + "complex_prompt" => [ + new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = $"This is a complex prompt with arguments: temperature={args.Params?.Arguments?["temperature"]}, style={args.Params?.Arguments?["style"]}" } }, + new PromptMessage { Role = Role.Assistant, Content = new Content { Type = "text", Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" } }, + new PromptMessage { Role = Role.User, Content = new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" } } + ] + , + _ => throw new NotSupportedException($"Unknown prompt name: {args.Params?.Name}") + }; + + return Task.FromResult(new GetPromptResult + { + Messages = messages + }); + }); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs new file mode 100644 index 00000000..4de48874 --- /dev/null +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -0,0 +1,11 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpToolType] +public static class AddTool +{ + [McpTool, Description("Adds two numbers.")] + public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; +} diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs new file mode 100644 index 00000000..3c4b8325 --- /dev/null +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -0,0 +1,21 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpToolType] +public static class AnnotatedMessageTool +{ + public enum MessageType + { + Error, + Success, + Debug, + } + + [McpTool, Description("Generates an annotated message")] + public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) + { + return ["incomplete"]; + } +} diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs new file mode 100644 index 00000000..94460ff1 --- /dev/null +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -0,0 +1,11 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpToolType] +public static class EchoTool +{ + [McpTool, Description("Echoes the message back to the client.")] + public static string Echo(string message) => $"Echo: {message}"; +} diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs new file mode 100644 index 00000000..c638e595 --- /dev/null +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -0,0 +1,28 @@ +//using ModelContextProtocol.Server; +//using System; +//using System.Collections.Generic; +//using System.ComponentModel; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace EverythingServer.Tools; + +//[McpToolType] +//public static class LongRunningTool +//{ +// [McpTool, Description("Demonstrates a long running operation with progress updates")] +// public static async Task LongRunningOperation(int duration, int steps) +// { +// var stepDuration = duration / steps; + +// for (int i = 1; i <= steps + 1; i++) +// { +// // Simulate a long-running operation +// await Task.Delay(stepDuration); +// // Report progress +// var progress = (i * 100) / steps; +// Console.WriteLine($"Progress: {progress}%"); +// } +// } +//} diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs new file mode 100644 index 00000000..d795610b --- /dev/null +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -0,0 +1,23 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Diagnostics; +using System.Text.Json; + +namespace EverythingServer.Tools; + +[McpToolType] +public static class PrintEnvTool +{ + private static readonly JsonSerializerOptions options = new() + { + WriteIndented = true + }; + + [McpTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] + public static string PrintEnv() + { + Debugger.Launch(); + var envVars = Environment.GetEnvironmentVariables(); + return JsonSerializer.Serialize(envVars, options); + } +} diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs new file mode 100644 index 00000000..4947890a --- /dev/null +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -0,0 +1,43 @@ +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpToolType] +public class SampleLlmTool(IMcpServer server) +{ + private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); + + [McpTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] + public async Task SampleLLM( + [Description("The prompt to send to the LLM")] string prompt, + [Description("Maximum number of tokens to generate")] int maxTokens, + CancellationToken cancellationToken) + { + var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); + var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken); + + return $"LLM sampling result: {sampleResult.Content.Text}"; + } + + private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100) + { + return new CreateMessageRequestParams() + { + Messages = [new SamplingMessage() + { + Role = Role.User, + Content = new Content() + { + Type = "text", + Text = $"Resource {uri} context: {context}" + } + }], + SystemPrompt = "You are a helpful test server.", + MaxTokens = maxTokens, + Temperature = 0.7f, + IncludeContext = ContextInclusion.ThisServer + }; + } +} diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs new file mode 100644 index 00000000..e840bf04 --- /dev/null +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -0,0 +1,19 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Diagnostics; + +namespace EverythingServer.Tools; + +[McpToolType] +public static class TinyImageTool +{ + [McpTool("getTinyImage"), Description("Get a tiny image from the server")] + public static IEnumerable GetTinyImage() + { + Debugger.Launch(); + return ["test"]; + } + + const string MCP_TINY_IMAGE = + "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"; +} From ef9ca63f8c4fd13dfd07e92f93e2a10cbac0cce9 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 24 Mar 2025 09:59:21 +1100 Subject: [PATCH 02/36] Sharing the tiny image --- samples/EverythingServer/Tools/TinyImageTool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index e840bf04..5afa4eda 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -14,6 +14,6 @@ public static IEnumerable GetTinyImage() return ["test"]; } - const string MCP_TINY_IMAGE = + internal const string MCP_TINY_IMAGE = "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"; } From fb83e208f2be2f0c1c0b66c9568391a9003b39be Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 24 Mar 2025 13:17:58 +1100 Subject: [PATCH 03/36] Cleanup from bad merge --- ModelContextProtocol.sln | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ModelContextProtocol.sln b/ModelContextProtocol.sln index e5781b3f..fe4ba0c6 100644 --- a/ModelContextProtocol.sln +++ b/ModelContextProtocol.sln @@ -7,10 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.Tests", "tests\ModelContextProtocol.Tests\ModelContextProtocol.Tests.csproj", "{FF41F619-833D-4FA2-8C53-04D0A1D5AA61}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MEAIToolsConsole", "samples\microsoft.extensions.ai\tools\ToolsConsole\MEAIToolsConsole.csproj", "{76E295FA-9E85-7B99-332A-2CDBFCD5860A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnthropicToolsConsole", "samples\anthropic\tools\ToolsConsole\AnthropicToolsConsole.csproj", "{CA0BB450-1903-2F92-A68D-1285976551D6}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelContextProtocol.TestServer", "tests\ModelContextProtocol.TestServer\ModelContextProtocol.TestServer.csproj", "{7C229573-A085-4ECC-8131-958CDA2BE731}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerWithHosting", "samples\TestServerWithHosting\TestServerWithHosting.csproj", "{6499876E-2F76-44A8-B6EB-5B889C6E9B7F}" @@ -63,14 +59,6 @@ Global {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF41F619-833D-4FA2-8C53-04D0A1D5AA61}.Release|Any CPU.Build.0 = Release|Any CPU - {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76E295FA-9E85-7B99-332A-2CDBFCD5860A}.Release|Any CPU.Build.0 = Release|Any CPU - {CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA0BB450-1903-2F92-A68D-1285976551D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA0BB450-1903-2F92-A68D-1285976551D6}.Release|Any CPU.Build.0 = Release|Any CPU {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C229573-A085-4ECC-8131-958CDA2BE731}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C229573-A085-4ECC-8131-958CDA2BE731}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -98,8 +86,6 @@ Global GlobalSection(NestedProjects) = preSolution {12260CD2-AFFC-4B2E-8898-F442CAA1FA0F} = {A2F1F52A-9107-4BF8-8C3F-2F6670E7D0AD} {FF41F619-833D-4FA2-8C53-04D0A1D5AA61} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} - {76E295FA-9E85-7B99-332A-2CDBFCD5860A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {CA0BB450-1903-2F92-A68D-1285976551D6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {7C229573-A085-4ECC-8131-958CDA2BE731} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} {6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {79B94BF9-E557-33DB-3F19-B2C7D9BF8C56} = {2A77AF5C-138A-4EBB-9A13-9205DCD67928} From 1862fe4c1d6ad418abbf98e5ea93b1079fae3417 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 24 Mar 2025 13:23:25 +1100 Subject: [PATCH 04/36] Adding a second call tool handler --- samples/EverythingServer/Program.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 74046795..27aec94b 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -42,6 +42,17 @@ { Messages = messages }); + }) + .WithCallToolHandler((request, ct) => + { + if (request.Params?.Name == "tiny_image") + { + return Task.FromResult(new CallToolResponse + { + Content = [new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" }] + }); + } + throw new NotSupportedException($"Unknown tool name: {request.Params?.Name}"); }); await builder.Build().RunAsync(); \ No newline at end of file From 393c671970ea9ed6764c9ba3718626a048f66fa7 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 24 Mar 2025 13:24:36 +1100 Subject: [PATCH 05/36] Fixing broken props file --- Directory.Packages.props | 75 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ffd7cd0d..88959275 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,39 +1,40 @@ - - true - 9.0.3 - 10.0.0-preview.2.25163.2 - 9.0.3 - 9.3.0-preview.1.25161.3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + 9.0.3 + 10.0.0-preview.2.25163.2 + 9.0.3 + 9.3.0-preview.1.25161.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 1ef268829f8e7f3e90b1657b84a1ab0088c74a23 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 26 Mar 2025 09:51:08 +1100 Subject: [PATCH 06/36] Updating to latest API design --- samples/EverythingServer/Tools/AddTool.cs | 4 ++-- samples/EverythingServer/Tools/AnnotatedMessageTool.cs | 4 ++-- samples/EverythingServer/Tools/EchoTool.cs | 4 ++-- samples/EverythingServer/Tools/LongRunningTool.cs | 4 ++-- samples/EverythingServer/Tools/PrintEnvTool.cs | 4 ++-- samples/EverythingServer/Tools/SampleLlmTool.cs | 4 ++-- samples/EverythingServer/Tools/TinyImageTool.cs | 4 ++-- src/ModelContextProtocol/README.md | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs index 4de48874..8ca12b9a 100644 --- a/samples/EverythingServer/Tools/AddTool.cs +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -3,9 +3,9 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public static class AddTool { - [McpTool, Description("Adds two numbers.")] + [McpServerTool, Description("Adds two numbers.")] public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; } diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index 3c4b8325..2f763d59 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -3,7 +3,7 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public static class AnnotatedMessageTool { public enum MessageType @@ -13,7 +13,7 @@ public enum MessageType Debug, } - [McpTool, Description("Generates an annotated message")] + [McpServerTool, Description("Generates an annotated message")] public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) { return ["incomplete"]; diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs index 94460ff1..3b7ece45 100644 --- a/samples/EverythingServer/Tools/EchoTool.cs +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -3,9 +3,9 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public static class EchoTool { - [McpTool, Description("Echoes the message back to the client.")] + [McpServerTool, Description("Echoes the message back to the client.")] public static string Echo(string message) => $"Echo: {message}"; } diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index c638e595..cd6a5f19 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -8,10 +8,10 @@ //namespace EverythingServer.Tools; -//[McpToolType] +//[McpServerToolType] //public static class LongRunningTool //{ -// [McpTool, Description("Demonstrates a long running operation with progress updates")] +// [McpServerTool, Description("Demonstrates a long running operation with progress updates")] // public static async Task LongRunningOperation(int duration, int steps) // { // var stepDuration = duration / steps; diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs index d795610b..6bbb22bb 100644 --- a/samples/EverythingServer/Tools/PrintEnvTool.cs +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -5,7 +5,7 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public static class PrintEnvTool { private static readonly JsonSerializerOptions options = new() @@ -13,7 +13,7 @@ public static class PrintEnvTool WriteIndented = true }; - [McpTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] + [McpServerTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] public static string PrintEnv() { Debugger.Launch(); diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index 4947890a..c287f29c 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -4,12 +4,12 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public class SampleLlmTool(IMcpServer server) { private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); - [McpTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] + [McpServerTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] public async Task SampleLLM( [Description("The prompt to send to the LLM")] string prompt, [Description("Maximum number of tokens to generate")] int maxTokens, diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index 5afa4eda..d4ab32e8 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -4,10 +4,10 @@ namespace EverythingServer.Tools; -[McpToolType] +[McpServerToolType] public static class TinyImageTool { - [McpTool("getTinyImage"), Description("Get a tiny image from the server")] + [McpServerTool("getTinyImage"), Description("Get a tiny image from the server")] public static IEnumerable GetTinyImage() { Debugger.Launch(); diff --git a/src/ModelContextProtocol/README.md b/src/ModelContextProtocol/README.md index a109addc..47b01409 100644 --- a/src/ModelContextProtocol/README.md +++ b/src/ModelContextProtocol/README.md @@ -101,10 +101,10 @@ builder.Services .WithTools(); await builder.Build().RunAsync(); -[McpToolType] +[McpServerToolType] public static class EchoTool { - [McpTool, Description("Echoes the message back to the client.")] + [McpServerTool, Description("Echoes the message back to the client.")] public static string Echo(string message) => $"hello {message}"; } ``` From 4893d7009554f1723ec07f906ed15fe1af72cdbd Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 14:04:05 +1100 Subject: [PATCH 07/36] WIP --- samples/EverythingServer/Program.cs | 114 +++++++++--------- .../EverythingServer/Tools/TinyImageTool.cs | 38 +++--- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 27aec94b..a616067d 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -1,58 +1,58 @@ -using ModelContextProtocol; -using Microsoft.Extensions.Hosting; -using ModelContextProtocol.Protocol.Types; -using EverythingServer.Tools; - -var builder = Host.CreateEmptyApplicationBuilder(settings: null); - -builder.Services - .AddMcpServer() - .WithStdioServerTransport() - .WithTools() - .WithListPromptsHandler((ctx, ct) => - { - return Task.FromResult(new ListPromptsResult - { - Prompts = - [ - new Prompt { Name= "simple_prompt", Description = "A prompt without arguments" }, - new Prompt { Name= "complex_prompt", Description = "A prompt with arguments", Arguments = [ - new PromptArgument { Name = "temperature", Description = "Temperature setting", Required = true }, - new PromptArgument { Name = "style", Description = "Output style", Required = false} - ] - } - ] - }); - }) - .WithGetPromptHandler((args, ct) => - { - List messages = args.Params?.Name switch - { - "simple_prompt" => [new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = "This is a simple prompt without arguments" } }], - "complex_prompt" => [ - new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = $"This is a complex prompt with arguments: temperature={args.Params?.Arguments?["temperature"]}, style={args.Params?.Arguments?["style"]}" } }, - new PromptMessage { Role = Role.Assistant, Content = new Content { Type = "text", Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" } }, - new PromptMessage { Role = Role.User, Content = new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" } } - ] - , - _ => throw new NotSupportedException($"Unknown prompt name: {args.Params?.Name}") - }; - - return Task.FromResult(new GetPromptResult - { - Messages = messages - }); - }) - .WithCallToolHandler((request, ct) => - { - if (request.Params?.Name == "tiny_image") - { - return Task.FromResult(new CallToolResponse - { - Content = [new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" }] - }); - } - throw new NotSupportedException($"Unknown tool name: {request.Params?.Name}"); - }); - +using ModelContextProtocol; +using Microsoft.Extensions.Hosting; +using ModelContextProtocol.Protocol.Types; +using EverythingServer.Tools; + +var builder = Host.CreateEmptyApplicationBuilder(settings: null); + +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithTools() + .WithListPromptsHandler((ctx, ct) => + { + return Task.FromResult(new ListPromptsResult + { + Prompts = + [ + new Prompt { Name= "simple_prompt", Description = "A prompt without arguments" }, + new Prompt { Name= "complex_prompt", Description = "A prompt with arguments", Arguments = [ + new PromptArgument { Name = "temperature", Description = "Temperature setting", Required = true }, + new PromptArgument { Name = "style", Description = "Output style", Required = false} + ] + } + ] + }); + }) + .WithGetPromptHandler((args, ct) => + { + List messages = args.Params?.Name switch + { + "simple_prompt" => [new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = "This is a simple prompt without arguments" } }], + "complex_prompt" => [ + new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = $"This is a complex prompt with arguments: temperature={args.Params?.Arguments?["temperature"]}, style={args.Params?.Arguments?["style"]}" } }, + new PromptMessage { Role = Role.Assistant, Content = new Content { Type = "text", Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" } }, + new PromptMessage { Role = Role.User, Content = new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" } } + ] + , + _ => throw new NotSupportedException($"Unknown prompt name: {args.Params?.Name}") + }; + + return Task.FromResult(new GetPromptResult + { + Messages = messages + }); + }) + .WithCallToolHandler((request, ct) => + { + if (request.Params?.Name == "tiny_image") + { + return Task.FromResult(new CallToolResponse + { + Content = [new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" }] + }); + } + throw new NotSupportedException($"Unknown tool name: {request.Params?.Name}"); + }); + await builder.Build().RunAsync(); \ No newline at end of file diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index d4ab32e8..12fa810f 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -1,19 +1,19 @@ -using ModelContextProtocol.Server; -using System.ComponentModel; -using System.Diagnostics; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public static class TinyImageTool -{ - [McpServerTool("getTinyImage"), Description("Get a tiny image from the server")] - public static IEnumerable GetTinyImage() - { - Debugger.Launch(); - return ["test"]; - } - - internal const string MCP_TINY_IMAGE = - "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"; -} +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Diagnostics; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public static class TinyImageTool +{ + [McpServerTool("getTinyImage"), Description("Get a tiny image from the server")] + public static IEnumerable GetTinyImage() + { + Debugger.Launch(); + return ["test"]; + } + + internal const string MCP_TINY_IMAGE = + "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"; +} From 5bbf1c264d2240315bca19a91e2a44dbb8315093 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 14:10:43 +1100 Subject: [PATCH 08/36] Adding support for returning collections from tools This will mean we can return multiple AIContent objects from a tool, such as a mixed text/image set Contributes to #68 --- .../Server/AIFunctionMcpServerTool.cs | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs index ff3f9288..3231ba8b 100644 --- a/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs @@ -22,7 +22,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool public static new AIFunctionMcpServerTool Create( Delegate method, string? name, - string? description, + string? description, IServiceProvider? services) { Throw.IfNull(method); @@ -34,7 +34,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool /// Creates an instance for a method, specified via a instance. /// public static new AIFunctionMcpServerTool Create( - MethodInfo method, + MethodInfo method, object? target, string? name, string? description, @@ -195,57 +195,59 @@ public override async Task InvokeAsync( }; } - switch (result) + return result switch { - case null: - return new() - { - Content = [] - }; - - case string text: - return new() - { - Content = [new() { Text = text, Type = "text" }] - }; - - case TextContent textContent: - return new() - { - Content = [new() { Text = textContent.Text, Type = "text" }] - }; - - case DataContent dataContent: - return new() - { - Content = [new() + null => new() + { + Content = [] + }, + string text => new() + { + Content = [new() { Text = text, Type = "text" }] + }, + TextContent textContent => new() + { + Content = [new() { Text = textContent.Text, Type = "text" }] + }, + DataContent dataContent => new() + { + Content = [new() { Data = dataContent.GetBase64Data(), MimeType = dataContent.MediaType, Type = dataContent.HasTopLevelMediaType("image") ? "image" : "resource", }] - }; + }, + string[] texts => new() + { + Content = [.. texts.Select(x => new Content() { Type = "text", Text = x ?? string.Empty })] + }, - case string[] texts: - return new() - { - Content = texts - .Select(x => new Content() { Type = "text", Text = x ?? string.Empty }) - .ToList() - }; + IEnumerable contentItems => new() + { + Content = [.. contentItems.Select(static item => item switch + { + TextContent textContent => new Content() { Type = "text", Text = textContent.Text }, + DataContent dataContent => new Content() + { + Data = dataContent.GetBase64Data(), + MimeType = dataContent.MediaType, + Type = dataContent.HasTopLevelMediaType("image") ? "image" : "resource", + }, + _ => new Content() { Type = "text", Text = item.ToString() ?? string.Empty } + })] + }, // TODO https://github.com/modelcontextprotocol/csharp-sdk/issues/69: // Add specialization for annotations. - - default: - return new() - { - Content = [new() + _ => new() + { + Content = [new() { Text = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))), Type = "text" }] - }; - } + }, + }; } } \ No newline at end of file From b75b6bc93c50dfcf0142a36649de4535d0476f6c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 14:19:09 +1100 Subject: [PATCH 09/36] Updating --- .../EverythingServer/EverythingServer.csproj | 36 ++++---- samples/EverythingServer/Program.cs | 2 +- samples/EverythingServer/Tools/AddTool.cs | 22 ++--- .../Tools/AnnotatedMessageTool.cs | 42 ++++----- samples/EverythingServer/Tools/EchoTool.cs | 22 ++--- .../EverythingServer/Tools/LongRunningTool.cs | 56 ++++++------ .../EverythingServer/Tools/PrintEnvTool.cs | 46 +++++----- .../EverythingServer/Tools/SampleLlmTool.cs | 86 +++++++++---------- .../EverythingServer/Tools/TinyImageTool.cs | 13 ++- 9 files changed, 165 insertions(+), 160 deletions(-) diff --git a/samples/EverythingServer/EverythingServer.csproj b/samples/EverythingServer/EverythingServer.csproj index 556d896e..3aee2bc2 100644 --- a/samples/EverythingServer/EverythingServer.csproj +++ b/samples/EverythingServer/EverythingServer.csproj @@ -1,18 +1,18 @@ - - - - net9.0 - enable - enable - Exe - - - - - - - - - - - + + + + net9.0 + enable + enable + Exe + + + + + + + + + + + diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index a616067d..53cbfbc5 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -8,7 +8,7 @@ builder.Services .AddMcpServer() .WithStdioServerTransport() - .WithTools() + .WithToolsFromAssembly() .WithListPromptsHandler((ctx, ct) => { return Task.FromResult(new ListPromptsResult diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs index 8ca12b9a..a6b85056 100644 --- a/samples/EverythingServer/Tools/AddTool.cs +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -1,11 +1,11 @@ -using ModelContextProtocol.Server; -using System.ComponentModel; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public static class AddTool -{ - [McpServerTool, Description("Adds two numbers.")] - public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; -} +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public static class AddTool +{ + [McpServerTool, Description("Adds two numbers.")] + public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; +} diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index 2f763d59..a9da4d76 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -1,21 +1,21 @@ -using ModelContextProtocol.Server; -using System.ComponentModel; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public static class AnnotatedMessageTool -{ - public enum MessageType - { - Error, - Success, - Debug, - } - - [McpServerTool, Description("Generates an annotated message")] - public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) - { - return ["incomplete"]; - } -} +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public static class AnnotatedMessageTool +{ + public enum MessageType + { + Error, + Success, + Debug, + } + + [McpServerTool, Description("Generates an annotated message")] + public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) + { + return ["incomplete"]; + } +} diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs index 3b7ece45..d1b0dda8 100644 --- a/samples/EverythingServer/Tools/EchoTool.cs +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -1,11 +1,11 @@ -using ModelContextProtocol.Server; -using System.ComponentModel; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public static class EchoTool -{ - [McpServerTool, Description("Echoes the message back to the client.")] - public static string Echo(string message) => $"Echo: {message}"; -} +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public static class EchoTool +{ + [McpServerTool, Description("Echoes the message back to the client.")] + public static string Echo(string message) => $"Echo: {message}"; +} diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index cd6a5f19..d2a0b141 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -1,28 +1,28 @@ -//using ModelContextProtocol.Server; -//using System; -//using System.Collections.Generic; -//using System.ComponentModel; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; - -//namespace EverythingServer.Tools; - -//[McpServerToolType] -//public static class LongRunningTool -//{ -// [McpServerTool, Description("Demonstrates a long running operation with progress updates")] -// public static async Task LongRunningOperation(int duration, int steps) -// { -// var stepDuration = duration / steps; - -// for (int i = 1; i <= steps + 1; i++) -// { -// // Simulate a long-running operation -// await Task.Delay(stepDuration); -// // Report progress -// var progress = (i * 100) / steps; -// Console.WriteLine($"Progress: {progress}%"); -// } -// } -//} +//using ModelContextProtocol.Server; +//using System; +//using System.Collections.Generic; +//using System.ComponentModel; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace EverythingServer.Tools; + +//[McpServerToolType] +//public static class LongRunningTool +//{ +// [McpServerTool, Description("Demonstrates a long running operation with progress updates")] +// public static async Task LongRunningOperation(int duration, int steps) +// { +// var stepDuration = duration / steps; + +// for (int i = 1; i <= steps + 1; i++) +// { +// // Simulate a long-running operation +// await Task.Delay(stepDuration); +// // Report progress +// var progress = (i * 100) / steps; +// Console.WriteLine($"Progress: {progress}%"); +// } +// } +//} diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs index 6bbb22bb..8c82deba 100644 --- a/samples/EverythingServer/Tools/PrintEnvTool.cs +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -1,23 +1,23 @@ -using ModelContextProtocol.Server; -using System.ComponentModel; -using System.Diagnostics; -using System.Text.Json; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public static class PrintEnvTool -{ - private static readonly JsonSerializerOptions options = new() - { - WriteIndented = true - }; - - [McpServerTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] - public static string PrintEnv() - { - Debugger.Launch(); - var envVars = Environment.GetEnvironmentVariables(); - return JsonSerializer.Serialize(envVars, options); - } -} +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Diagnostics; +using System.Text.Json; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public static class PrintEnvTool +{ + private static readonly JsonSerializerOptions options = new() + { + WriteIndented = true + }; + + [McpServerTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] + public static string PrintEnv() + { + Debugger.Launch(); + var envVars = Environment.GetEnvironmentVariables(); + return JsonSerializer.Serialize(envVars, options); + } +} diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index c287f29c..35df89ce 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -1,43 +1,43 @@ -using ModelContextProtocol.Protocol.Types; -using ModelContextProtocol.Server; -using System.ComponentModel; - -namespace EverythingServer.Tools; - -[McpServerToolType] -public class SampleLlmTool(IMcpServer server) -{ - private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); - - [McpServerTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] - public async Task SampleLLM( - [Description("The prompt to send to the LLM")] string prompt, - [Description("Maximum number of tokens to generate")] int maxTokens, - CancellationToken cancellationToken) - { - var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken); - - return $"LLM sampling result: {sampleResult.Content.Text}"; - } - - private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100) - { - return new CreateMessageRequestParams() - { - Messages = [new SamplingMessage() - { - Role = Role.User, - Content = new Content() - { - Type = "text", - Text = $"Resource {uri} context: {context}" - } - }], - SystemPrompt = "You are a helpful test server.", - MaxTokens = maxTokens, - Temperature = 0.7f, - IncludeContext = ContextInclusion.ThisServer - }; - } -} +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; +using System.ComponentModel; + +namespace EverythingServer.Tools; + +[McpServerToolType] +public class SampleLlmTool(IMcpServer server) +{ + private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); + + [McpServerTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] + public async Task SampleLLM( + [Description("The prompt to send to the LLM")] string prompt, + [Description("Maximum number of tokens to generate")] int maxTokens, + CancellationToken cancellationToken) + { + var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); + var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken); + + return $"LLM sampling result: {sampleResult.Content.Text}"; + } + + private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100) + { + return new CreateMessageRequestParams() + { + Messages = [new SamplingMessage() + { + Role = Role.User, + Content = new Content() + { + Type = "text", + Text = $"Resource {uri} context: {context}" + } + }], + SystemPrompt = "You are a helpful test server.", + MaxTokens = maxTokens, + Temperature = 0.7f, + IncludeContext = ContextInclusion.ThisServer + }; + } +} diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index 12fa810f..d788f23b 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -1,4 +1,5 @@ -using ModelContextProtocol.Server; +using Microsoft.Extensions.AI; +using ModelContextProtocol.Server; using System.ComponentModel; using System.Diagnostics; @@ -8,12 +9,16 @@ namespace EverythingServer.Tools; public static class TinyImageTool { [McpServerTool("getTinyImage"), Description("Get a tiny image from the server")] - public static IEnumerable GetTinyImage() + public static IEnumerable GetTinyImage() { Debugger.Launch(); - return ["test"]; + return [ + new TextContent("This is a tiny image:"), + new DataContent(MCP_TINY_IMAGE, "image/png"), + new TextContent("The image above is the MCP tiny image.") + ]; } internal const string MCP_TINY_IMAGE = - "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"; + "data:image/png;base64,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"; } From b9dfc512bb4c9be91969bfc0ba02a1c58e719b6c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 16:49:00 +1100 Subject: [PATCH 10/36] Completing the tools as best as possible --- samples/EverythingServer/Program.cs | 11 ---- samples/EverythingServer/Tools/AddTool.cs | 2 +- .../Tools/AnnotatedMessageTool.cs | 4 +- samples/EverythingServer/Tools/EchoTool.cs | 2 +- .../EverythingServer/Tools/LongRunningTool.cs | 64 +++++++++++-------- .../EverythingServer/Tools/PrintEnvTool.cs | 2 +- .../EverythingServer/Tools/TinyImageTool.cs | 2 +- 7 files changed, 45 insertions(+), 42 deletions(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 53cbfbc5..29f70dec 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -42,17 +42,6 @@ { Messages = messages }); - }) - .WithCallToolHandler((request, ct) => - { - if (request.Params?.Name == "tiny_image") - { - return Task.FromResult(new CallToolResponse - { - Content = [new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE, MimeType = "image/png" }] - }); - } - throw new NotSupportedException($"Unknown tool name: {request.Params?.Name}"); }); await builder.Build().RunAsync(); \ No newline at end of file diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs index a6b85056..995e880a 100644 --- a/samples/EverythingServer/Tools/AddTool.cs +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -6,6 +6,6 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class AddTool { - [McpServerTool, Description("Adds two numbers.")] + [McpServerTool("add"), Description("Adds two numbers.")] public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; } diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index a9da4d76..4f7fee47 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -13,9 +13,9 @@ public enum MessageType Debug, } - [McpServerTool, Description("Generates an annotated message")] + [McpServerTool("annotatedMessage"), Description("Generates an annotated message")] public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) { - return ["incomplete"]; + throw new NotSupportedException("Unable to write annotations to the output."); } } diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs index d1b0dda8..5983b495 100644 --- a/samples/EverythingServer/Tools/EchoTool.cs +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -6,6 +6,6 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class EchoTool { - [McpServerTool, Description("Echoes the message back to the client.")] + [McpServerTool("echo"), Description("Echoes the message back to the client.")] public static string Echo(string message) => $"Echo: {message}"; } diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index d2a0b141..487feaf1 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -1,28 +1,42 @@ -//using ModelContextProtocol.Server; -//using System; -//using System.Collections.Generic; -//using System.ComponentModel; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; +using ModelContextProtocol.Protocol.Messages; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; +using System.ComponentModel; -//namespace EverythingServer.Tools; +namespace EverythingServer.Tools; -//[McpServerToolType] -//public static class LongRunningTool -//{ -// [McpServerTool, Description("Demonstrates a long running operation with progress updates")] -// public static async Task LongRunningOperation(int duration, int steps) -// { -// var stepDuration = duration / steps; +[McpServerToolType] +public static class LongRunningTool +{ + [McpServerTool("longRunningOperation"), Description("Demonstrates a long running operation with progress updates")] + public static async Task LongRunningOperation( + IMcpServer server, + RequestContext context, + int duration = 10, + int steps = 5) + { + var progressToken = context.Params?.Meta?.ProgressToken; + var stepDuration = duration / steps; -// for (int i = 1; i <= steps + 1; i++) -// { -// // Simulate a long-running operation -// await Task.Delay(stepDuration); -// // Report progress -// var progress = (i * 100) / steps; -// Console.WriteLine($"Progress: {progress}%"); -// } -// } -//} + for (int i = 1; i <= steps + 1; i++) + { + await Task.Delay(stepDuration * 1000); + + if (progressToken is not null) + { + await server.SendMessageAsync(new JsonRpcNotification + { + Method = "notifications/progress", + Params = new + { + Progress = i, + Total = steps, + progressToken + } + }); + } + } + + return $"Long running operation completed. Duration: {duration} seconds. Steps: {steps}."; + } +} diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs index 8c82deba..71ee9237 100644 --- a/samples/EverythingServer/Tools/PrintEnvTool.cs +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -13,7 +13,7 @@ public static class PrintEnvTool WriteIndented = true }; - [McpServerTool, Description("Prints all environment variables, helpful for debugging MCP server configuration")] + [McpServerTool("printEnv"), Description("Prints all environment variables, helpful for debugging MCP server configuration")] public static string PrintEnv() { Debugger.Launch(); diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index d788f23b..d865e546 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -14,7 +14,7 @@ public static IEnumerable GetTinyImage() Debugger.Launch(); return [ new TextContent("This is a tiny image:"), - new DataContent(MCP_TINY_IMAGE, "image/png"), + new DataContent(MCP_TINY_IMAGE), new TextContent("The image above is the MCP tiny image.") ]; } From e3f5fa61c09647d73c6aa10b51004a81cd370ab3 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 28 Mar 2025 13:11:25 +1100 Subject: [PATCH 11/36] Working on improvements to resources --- src/ModelContextProtocol/Server/McpServer.cs | 12 ++- .../Server/McpServerResourceTests.cs | 99 +++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs diff --git a/src/ModelContextProtocol/Server/McpServer.cs b/src/ModelContextProtocol/Server/McpServer.cs index 4c0c6b56..2063057a 100644 --- a/src/ModelContextProtocol/Server/McpServer.cs +++ b/src/ModelContextProtocol/Server/McpServer.cs @@ -150,19 +150,21 @@ private void SetResourcesHandler(McpServerOptions options) return; } - if (resourcesCapability.ListResourcesHandler is not { } listResourcesHandler || + var listResourcesHandler = resourcesCapability.ListResourcesHandler; + var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler; + + if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) || resourcesCapability.ReadResourceHandler is not { } readResourceHandler) { throw new McpServerException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); } + listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult())); + SetRequestHandler("resources/list", (request, ct) => listResourcesHandler(new(this, request), ct)); SetRequestHandler("resources/read", (request, ct) => readResourceHandler(new(this, request), ct)); - // Set the list resource templates handler, or use the default if not specified - var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler - ?? (static (_, _) => Task.FromResult(new ListResourceTemplatesResult())); - + listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult())); SetRequestHandler("resources/templates/list", (request, ct) => listResourceTemplatesHandler(new(this, request), ct)); if (resourcesCapability.Subscribe is not true) diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs new file mode 100644 index 00000000..167dd6cd --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -0,0 +1,99 @@ +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace ModelContextProtocol.Tests.Server; +public class McpServerResourceTests +{ + [Fact] + public void CanCreateServerWithResourceTemplates() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .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) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService(); + } + + [Fact] + public void CanCreateServerWithResources() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .WithListResourcesHandler((ctx, ct) => + { + return Task.FromResult(new ListResourcesResult + { + Resources = + [ + new Resource { Name = "Static Resource", Description = "A static resource with a numeric ID", Uri = "test://static/resource/foo.txt" } + ] + }); + }) + .WithReadResourceHandler((ctx, ct) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService(); + } + + [Fact] + public void CreatingReadHandlerWithNoListHandlerFails() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithStdioServerTransport() + .WithReadResourceHandler((ctx, ct) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + var sp = services.BuildServiceProvider(); + Assert.Throws(() => sp.GetRequiredService()); + } +} From e5227b29ce4c1f20cd0c4b3e57cced7a5b724525 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 28 Mar 2025 13:11:33 +1100 Subject: [PATCH 12/36] WIP --- samples/EverythingServer/Program.cs | 46 ++++++++++++++++++- samples/EverythingServer/ResourceGenerator.cs | 37 +++++++++++++++ .../Protocol/Types/ResourceContents.cs | 16 ++++++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 samples/EverythingServer/ResourceGenerator.cs diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 29f70dec..5d0a9fdc 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Hosting; using ModelContextProtocol.Protocol.Types; using EverythingServer.Tools; +using EverythingServer; var builder = Host.CreateEmptyApplicationBuilder(settings: null); @@ -18,7 +19,7 @@ new Prompt { Name= "simple_prompt", Description = "A prompt without arguments" }, new Prompt { Name= "complex_prompt", Description = "A prompt with arguments", Arguments = [ new PromptArgument { Name = "temperature", Description = "Temperature setting", Required = true }, - new PromptArgument { Name = "style", Description = "Output style", Required = false} + new PromptArgument { Name = "style", Description = "Output style", Required = false } ] } ] @@ -42,6 +43,47 @@ { Messages = messages }); - }); + }) + .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]; + + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = resource.Uri, + MimeType = resource.MimeType, + Blob = resource.Description, + Name = resource.Description + } + ] + }); + }) + ; await builder.Build().RunAsync(); \ No newline at end of file diff --git a/samples/EverythingServer/ResourceGenerator.cs b/samples/EverythingServer/ResourceGenerator.cs new file mode 100644 index 00000000..54764b8c --- /dev/null +++ b/samples/EverythingServer/ResourceGenerator.cs @@ -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 _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 Resources => _resources; +} \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 89b34ab4..952cb1c7 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -3,7 +3,7 @@ namespace ModelContextProtocol.Protocol.Types; /// -/// Represents the content of a resource. +/// A known resource that the server is capable of reading. /// See the schema for details /// public class ResourceContents @@ -32,4 +32,18 @@ public class ResourceContents /// [JsonPropertyName("blob")] public string? Blob { get; set; } + + /// + /// A human-readable name for this resource.\n\nThis can be used by clients to populate UI elements. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// + /// The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + /// + /// This can be used by Hosts to display file sizes and estimate context window usage. + /// + [JsonPropertyName("size")] + public long? Size { get; set; } } \ No newline at end of file From ca59b300f659b0a362ea7608b1d661964709bfc4 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 28 Mar 2025 13:11:25 +1100 Subject: [PATCH 13/36] Supporting resource templates with read resource callback Fixes #123 --- src/ModelContextProtocol/Server/McpServer.cs | 12 ++- .../Server/McpServerResourceTests.cs | 99 +++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs diff --git a/src/ModelContextProtocol/Server/McpServer.cs b/src/ModelContextProtocol/Server/McpServer.cs index 7f425b1f..e6909679 100644 --- a/src/ModelContextProtocol/Server/McpServer.cs +++ b/src/ModelContextProtocol/Server/McpServer.cs @@ -170,19 +170,21 @@ private void SetResourcesHandler(McpServerOptions options) return; } - if (resourcesCapability.ListResourcesHandler is not { } listResourcesHandler || + var listResourcesHandler = resourcesCapability.ListResourcesHandler; + var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler; + + if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) || resourcesCapability.ReadResourceHandler is not { } readResourceHandler) { throw new McpServerException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); } + listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult())); + SetRequestHandler("resources/list", (request, ct) => listResourcesHandler(new(this, request), ct)); SetRequestHandler("resources/read", (request, ct) => readResourceHandler(new(this, request), ct)); - // Set the list resource templates handler, or use the default if not specified - var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler - ?? (static (_, _) => Task.FromResult(new ListResourceTemplatesResult())); - + listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult())); SetRequestHandler("resources/templates/list", (request, ct) => listResourceTemplatesHandler(new(this, request), ct)); if (resourcesCapability.Subscribe is not true) diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs new file mode 100644 index 00000000..167dd6cd --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -0,0 +1,99 @@ +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace ModelContextProtocol.Tests.Server; +public class McpServerResourceTests +{ + [Fact] + public void CanCreateServerWithResourceTemplates() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .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) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService(); + } + + [Fact] + public void CanCreateServerWithResources() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .WithListResourcesHandler((ctx, ct) => + { + return Task.FromResult(new ListResourcesResult + { + Resources = + [ + new Resource { Name = "Static Resource", Description = "A static resource with a numeric ID", Uri = "test://static/resource/foo.txt" } + ] + }); + }) + .WithReadResourceHandler((ctx, ct) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService(); + } + + [Fact] + public void CreatingReadHandlerWithNoListHandlerFails() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithStdioServerTransport() + .WithReadResourceHandler((ctx, ct) => + { + return Task.FromResult(new ReadResourceResult + { + Contents = [new ResourceContents + { + Uri = ctx.Params!.Uri!, + Text = "Static Resource", + MimeType = "text/plain", + }] + }); + }); + var sp = services.BuildServiceProvider(); + Assert.Throws(() => sp.GetRequiredService()); + } +} From c53abef29591ac8c60a1d57e5b5de609e90cacc1 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 28 Mar 2025 14:43:37 +1100 Subject: [PATCH 14/36] Aligning the resource data structures with the 2024-11-05 schema - Introducing separate classes for Text and Blob ResourceContents - Custom JSON converter to allow those types to be returned, making it easier to follow the type system against the spec - Updated tests --- .../AIContentExtensions.cs | 19 ++- .../Client/McpClientExtensions.cs | 12 +- .../ModelContextProtocol.csproj | 2 +- .../Protocol/Types/ResourceContents.cs | 35 ----- .../Types/Resources/BlobResourceContents.cs | 16 +++ .../ListResourceTemplatesRequestParams.cs | 0 .../ListResourceTemplatesResult.cs | 0 .../ListResourcesRequestParams.cs | 0 .../{ => Resources}/ListResourcesResult.cs | 0 .../ReadResourceRequestParams.cs | 0 .../{ => Resources}/ReadResourceResult.cs | 0 .../Types/{ => Resources}/Resource.cs | 14 +- .../Types/Resources/ResourceContents.cs | 120 ++++++++++++++++++ .../Types/Resources/TextResourceContents.cs | 16 +++ .../Program.cs | 6 +- .../Program.cs | 6 +- .../ClientIntegrationTests.cs | 8 +- .../Server/McpServerResourceTests.cs | 6 +- .../Server/McpServerTests.cs | 6 +- .../SseServerIntegrationTests.cs | 8 +- 20 files changed, 201 insertions(+), 73 deletions(-) delete mode 100644 src/ModelContextProtocol/Protocol/Types/ResourceContents.cs create mode 100644 src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ListResourceTemplatesRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ListResourceTemplatesResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ListResourcesRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ListResourcesResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ReadResourceRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/ReadResourceResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{ => Resources}/Resource.cs (61%) create mode 100644 src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs create mode 100644 src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs diff --git a/src/ModelContextProtocol/AIContentExtensions.cs b/src/ModelContextProtocol/AIContentExtensions.cs index 87f40daa..08be7ed9 100644 --- a/src/ModelContextProtocol/AIContentExtensions.cs +++ b/src/ModelContextProtocol/AIContentExtensions.cs @@ -39,11 +39,7 @@ public static AIContent ToAIContent(this Content content) } else if (content is { Type: "resource" } && content.Resource is { } resourceContents) { - ac = resourceContents.Blob is not null && resourceContents.MimeType is not null ? - new DataContent(Convert.FromBase64String(resourceContents.Blob), resourceContents.MimeType) : - new TextContent(resourceContents.Text); - - (ac.AdditionalProperties ??= [])["uri"] = resourceContents.Uri; + ac = resourceContents.ToAIContent(); } else { @@ -62,9 +58,12 @@ public static AIContent ToAIContent(this ResourceContents content) { Throw.IfNull(content); - AIContent ac = content.Blob is not null && content.MimeType is not null ? - new DataContent(Convert.FromBase64String(content.Blob), content.MimeType) : - new TextContent(content.Text); + AIContent ac = content switch + { + BlobResourceContents blobResource => new DataContent(Convert.FromBase64String(blobResource.Blob), blobResource.MimeType ?? "application/octet-stream"), + TextResourceContents textResource => new TextContent(textResource.Text), + _ => throw new NotSupportedException($"Resource type '{content.GetType().Name}' is not supported.") + }; (ac.AdditionalProperties ??= [])["uri"] = content.Uri; ac.RawRepresentation = content; @@ -79,7 +78,7 @@ public static IList ToAIContents(this IEnumerable contents) { Throw.IfNull(contents); - return contents.Select(ToAIContent).ToList(); + return [.. contents.Select(ToAIContent)]; } /// Creates a list of from a sequence of . @@ -89,7 +88,7 @@ public static IList ToAIContents(this IEnumerable c { Throw.IfNull(contents); - return contents.Select(ToAIContent).ToList(); + return [.. contents.Select(ToAIContent)]; } /// Extracts the data from a as a Base64 string. diff --git a/src/ModelContextProtocol/Client/McpClientExtensions.cs b/src/ModelContextProtocol/Client/McpClientExtensions.cs index bf453cd9..a847ca87 100644 --- a/src/ModelContextProtocol/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol/Client/McpClientExtensions.cs @@ -475,17 +475,7 @@ internal static (IList Messages, ChatOptions? Options) ToChatClient } else if (sm.Content is { Type: "resource", Resource: not null }) { - ResourceContents resource = sm.Content.Resource; - - if (resource.Text is not null) - { - message.Contents.Add(new TextContent(resource.Text)); - } - - if (resource.Blob is not null && resource.MimeType is not null) - { - message.Contents.Add(new DataContent(Convert.FromBase64String(resource.Blob), resource.MimeType)); - } + message.Contents.Add(sm.Content.Resource.ToAIContent()); } messages.Add(message); diff --git a/src/ModelContextProtocol/ModelContextProtocol.csproj b/src/ModelContextProtocol/ModelContextProtocol.csproj index dcee6278..6860381d 100644 --- a/src/ModelContextProtocol/ModelContextProtocol.csproj +++ b/src/ModelContextProtocol/ModelContextProtocol.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs deleted file mode 100644 index 89b34ab4..00000000 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text.Json.Serialization; - -namespace ModelContextProtocol.Protocol.Types; - -/// -/// Represents the content of a resource. -/// See the schema for details -/// -public class ResourceContents -{ - /// - /// The URI of the resource. - /// - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; - - /// - /// The type of content. - /// - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// - /// The text content of the resource. - /// - [JsonPropertyName("text")] - public string? Text { get; set; } - - - /// - /// The base64-encoded binary content of the resource. - /// - [JsonPropertyName("blob")] - public string? Blob { get; set; } -} \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs new file mode 100644 index 00000000..d2b2d938 --- /dev/null +++ b/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace ModelContextProtocol.Protocol.Types; + +/// +/// Binary contents of a resource. +/// See the schema for details +/// +public class BlobResourceContents : ResourceContents +{ + /// + /// The binary content of the resource. + /// + [JsonPropertyName("blob")] + public string Blob { get; set; } = default!; +} \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesResult.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesResult.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/ListResourcesRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ListResourcesRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/ListResourcesResult.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ListResourcesResult.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/ReadResourceRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ReadResourceRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/ReadResourceResult.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/ReadResourceResult.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resource.cs b/src/ModelContextProtocol/Protocol/Types/Resources/Resource.cs similarity index 61% rename from src/ModelContextProtocol/Protocol/Types/Resource.cs rename to src/ModelContextProtocol/Protocol/Types/Resources/Resource.cs index fbe44bce..cb1fc397 100644 --- a/src/ModelContextProtocol/Protocol/Types/Resource.cs +++ b/src/ModelContextProtocol/Protocol/Types/Resources/Resource.cs @@ -3,7 +3,7 @@ namespace ModelContextProtocol.Protocol.Types; /// -/// Represents a known resource that the server is capable of reading. +/// A known resource that the server is capable of reading. /// See the schema for details /// public record Resource : Annotated @@ -16,12 +16,16 @@ public record Resource : Annotated /// /// A human-readable name for this resource. + /// + /// This can be used by clients to populate UI elements. /// [JsonPropertyName("name")] public required string Name { get; init; } /// /// A description of what this resource represents. + /// + /// This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model. /// [JsonPropertyName("description")] public string? Description { get; init; } @@ -31,4 +35,12 @@ public record Resource : Annotated /// [JsonPropertyName("mimeType")] public string? MimeType { get; init; } + + /// + /// The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + /// + /// This can be used by Hosts to display file sizes and estimate context window usage. + /// + [JsonPropertyName("size")] + public long? Size { get; init; } } diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs new file mode 100644 index 00000000..e525be63 --- /dev/null +++ b/src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs @@ -0,0 +1,120 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ModelContextProtocol.Protocol.Types; + +/// +/// The contents of a specific resource or sub-resource. +/// See the schema for details +/// +[JsonConverter(typeof(ResourceContentsConverter))] +public abstract class ResourceContents +{ + /// + /// The URI of the resource. + /// + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; + + /// + /// The type of content. + /// + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } +} + +internal class ResourceContentsConverter : JsonConverter +{ + public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + string? uri = null; + string? mimeType = null; + string? blob = null; + string? text = null; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + string? propertyName = reader.GetString(); + + if (propertyName == null) + { + continue; + } + + switch (propertyName) + { + case "uri": + uri = reader.GetString(); + break; + case "mimeType": + mimeType = reader.GetString(); + break; + case "blob": + blob = reader.GetString(); + break; + case "text": + text = reader.GetString(); + break; + default: + break; + } + } + + if (blob is not null) + { + return new BlobResourceContents + { + Uri = uri ?? string.Empty, + MimeType = mimeType, + Blob = blob + }; + } + if (text is not null) + { + return new TextResourceContents + { + Uri = uri ?? string.Empty, + MimeType = mimeType, + Text = text + }; + } + return null; + } + + public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + writer.WriteString("uri", value.Uri); + writer.WriteString("mimeType", value.MimeType); + if (value is BlobResourceContents blobResource) + { + writer.WriteString("blob", blobResource.Blob); + } + else if (value is TextResourceContents textResource) + { + writer.WriteString("text", textResource.Text); + } + writer.WriteEndObject(); + } +} \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs new file mode 100644 index 00000000..4e0985ff --- /dev/null +++ b/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace ModelContextProtocol.Protocol.Types; + +/// +/// Text contents of a resource. +/// See the schema for details +/// +public class TextResourceContents : ResourceContents +{ + /// + /// The text content of the resource. + /// + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; +} diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index e0558447..e8336d05 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -330,7 +330,7 @@ private static ResourcesCapability ConfigureResources() Name = $"Resource {i + 1}", MimeType = "text/plain" }); - resourceContents.Add(new ResourceContents() + resourceContents.Add(new TextResourceContents() { Uri = uri, MimeType = "text/plain", @@ -346,7 +346,7 @@ private static ResourcesCapability ConfigureResources() Name = $"Resource {i + 1}", MimeType = "application/octet-stream" }); - resourceContents.Add(new ResourceContents() + resourceContents.Add(new BlobResourceContents() { Uri = uri, MimeType = "application/octet-stream", @@ -420,7 +420,7 @@ private static ResourcesCapability ConfigureResources() return Task.FromResult(new ReadResourceResult() { Contents = [ - new ResourceContents() + new TextResourceContents() { Uri = request.Params.Uri, MimeType = "text/plain", diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index 598b4adf..cc03edc8 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -83,7 +83,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st Name = $"Resource {i + 1}", MimeType = "text/plain" }); - resourceContents.Add(new ResourceContents() + resourceContents.Add(new TextResourceContents() { Uri = uri, MimeType = "text/plain", @@ -99,7 +99,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st Name = $"Resource {i + 1}", MimeType = "application/octet-stream" }); - resourceContents.Add(new ResourceContents() + resourceContents.Add(new BlobResourceContents() { Uri = uri, MimeType = "application/octet-stream", @@ -264,7 +264,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st return Task.FromResult(new ReadResourceResult() { Contents = [ - new ResourceContents() + new TextResourceContents() { Uri = request.Params.Uri, MimeType = "text/plain", diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs index 69321675..f11dcabd 100644 --- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs @@ -224,7 +224,9 @@ public async Task ReadResource_Stdio_TextResource(string clientId) Assert.NotNull(result); Assert.Single(result.Contents); - Assert.NotNull(result.Contents[0].Text); + + TextResourceContents textResource = Assert.IsType(result.Contents[0]); + Assert.NotNull(textResource.Text); } [Theory] @@ -241,7 +243,9 @@ public async Task ReadResource_Stdio_BinaryResource(string clientId) Assert.NotNull(result); Assert.Single(result.Contents); - Assert.NotNull(result.Contents[0].Blob); + + BlobResourceContents blobResource = Assert.IsType(result.Contents[0]); + Assert.NotNull(blobResource.Blob); } // Not supported by "everything" server version on npx diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index 167dd6cd..706e2099 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -26,7 +26,7 @@ public void CanCreateServerWithResourceTemplates() { return Task.FromResult(new ReadResourceResult { - Contents = [new ResourceContents + Contents = [new TextResourceContents { Uri = ctx.Params!.Uri!, Text = "Static Resource", @@ -61,7 +61,7 @@ public void CanCreateServerWithResources() { return Task.FromResult(new ReadResourceResult { - Contents = [new ResourceContents + Contents = [new TextResourceContents { Uri = ctx.Params!.Uri!, Text = "Static Resource", @@ -85,7 +85,7 @@ public void CreatingReadHandlerWithNoListHandlerFails() { return Task.FromResult(new ReadResourceResult { - Contents = [new ResourceContents + Contents = [new TextResourceContents { Uri = ctx.Params!.Uri!, Text = "Static Resource", diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 3f4dd1d8..4660d54e 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -384,7 +384,7 @@ await Can_Handle_Requests( { return Task.FromResult(new ReadResourceResult { - Contents = [new() { Text = "test" }] + Contents = [new TextResourceContents() { Text = "test" }] }); }, ListResourcesHandler = (request, ct) => throw new NotImplementedException(), @@ -399,7 +399,9 @@ await Can_Handle_Requests( var result = (ReadResourceResult)response; Assert.NotNull(result.Contents); Assert.NotEmpty(result.Contents); - Assert.Equal("test", result.Contents[0].Text); + + TextResourceContents textResource = Assert.IsType(result.Contents[0]); + Assert.Equal("test", textResource.Text); }); } diff --git a/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs b/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs index b834f843..7a30bd19 100644 --- a/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs @@ -113,7 +113,9 @@ public async Task ReadResource_Sse_TextResource() Assert.NotNull(result); Assert.Single(result.Contents); - Assert.NotNull(result.Contents[0].Text); + + TextResourceContents textContent = Assert.IsType(result.Contents[0]); + Assert.NotNull(textContent.Text); } [Fact] @@ -130,7 +132,9 @@ public async Task ReadResource_Sse_BinaryResource() Assert.NotNull(result); Assert.Single(result.Contents); - Assert.NotNull(result.Contents[0].Blob); + + BlobResourceContents blobContent = Assert.IsType(result.Contents[0]); + Assert.NotNull(blobContent.Blob); } [Fact] From d08062c93bc473291f2ba74dbf371fef4dcf03ff Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Sat, 29 Mar 2025 10:12:59 +1100 Subject: [PATCH 15/36] Apply suggestions from code review Co-authored-by: Stephen Toub Co-authored-by: Peder Holdgaard Pedersen <127606677+PederHP@users.noreply.github.com> --- .../Protocol/Types/Resources/BlobResourceContents.cs | 2 +- .../Protocol/Types/Resources/TextResourceContents.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs index d2b2d938..4f31846e 100644 --- a/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs @@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol.Types; public class BlobResourceContents : ResourceContents { /// - /// The binary content of the resource. + /// The base64-encoded string representing the binary data of the item. /// [JsonPropertyName("blob")] public string Blob { get; set; } = default!; diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs index 4e0985ff..1f4b202c 100644 --- a/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs @@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol.Types; public class TextResourceContents : ResourceContents { /// - /// The text content of the resource. + /// The text of the item. This must only be set if the item can actually be represented as text (not binary data). /// [JsonPropertyName("text")] public string Text { get; set; } = string.Empty; From 3ed3b99179603ba146f38c6727d739f1b17d6618 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Sat, 29 Mar 2025 10:14:22 +1100 Subject: [PATCH 16/36] Putting files back where they belong --- .../Protocol/Types/{Resources => }/BlobResourceContents.cs | 0 .../Types/{Resources => }/ListResourceTemplatesRequestParams.cs | 0 .../Protocol/Types/{Resources => }/ListResourceTemplatesResult.cs | 0 .../Protocol/Types/{Resources => }/ListResourcesRequestParams.cs | 0 .../Protocol/Types/{Resources => }/ListResourcesResult.cs | 0 .../Protocol/Types/{Resources => }/ReadResourceRequestParams.cs | 0 .../Protocol/Types/{Resources => }/ReadResourceResult.cs | 0 .../Protocol/Types/{Resources => }/Resource.cs | 0 .../Protocol/Types/{Resources => }/ResourceContents.cs | 0 .../Protocol/Types/{Resources => }/TextResourceContents.cs | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/BlobResourceContents.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ListResourceTemplatesRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ListResourceTemplatesResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ListResourcesRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ListResourcesResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ReadResourceRequestParams.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ReadResourceResult.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/Resource.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/ResourceContents.cs (100%) rename src/ModelContextProtocol/Protocol/Types/{Resources => }/TextResourceContents.cs (100%) diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/BlobResourceContents.cs rename to src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesResult.cs b/src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ListResourceTemplatesResult.cs rename to src/ModelContextProtocol/Protocol/Types/ListResourceTemplatesResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/ListResourcesRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/ListResourcesRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesResult.cs b/src/ModelContextProtocol/Protocol/Types/ListResourcesResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ListResourcesResult.cs rename to src/ModelContextProtocol/Protocol/Types/ListResourcesResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceRequestParams.cs b/src/ModelContextProtocol/Protocol/Types/ReadResourceRequestParams.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceRequestParams.cs rename to src/ModelContextProtocol/Protocol/Types/ReadResourceRequestParams.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceResult.cs b/src/ModelContextProtocol/Protocol/Types/ReadResourceResult.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ReadResourceResult.cs rename to src/ModelContextProtocol/Protocol/Types/ReadResourceResult.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/Resource.cs b/src/ModelContextProtocol/Protocol/Types/Resource.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/Resource.cs rename to src/ModelContextProtocol/Protocol/Types/Resource.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/ResourceContents.cs rename to src/ModelContextProtocol/Protocol/Types/ResourceContents.cs diff --git a/src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs similarity index 100% rename from src/ModelContextProtocol/Protocol/Types/Resources/TextResourceContents.cs rename to src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs From a49c560d5e6ff7d2cc9c534db7df79faa012f7e4 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 08:05:46 +1100 Subject: [PATCH 17/36] Adding an internal constructor to prevent overloads --- src/ModelContextProtocol/Protocol/Types/ResourceContents.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 7cee1607..6130adfd 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -10,6 +10,10 @@ namespace ModelContextProtocol.Protocol.Types; [JsonConverter(typeof(ResourceContentsConverter))] public abstract class ResourceContents { + internal ResourceContents() + { + } + /// /// The URI of the resource. /// From 25d54a6d7b221b3c813722b07475d1f531ad4e4d Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 08:08:02 +1100 Subject: [PATCH 18/36] Addressing feedback --- .../Protocol/Types/ResourceContents.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 6130adfd..460389af 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.ComponentModel; +using System.Text.Json; using System.Text.Json.Serialization; namespace ModelContextProtocol.Protocol.Types; @@ -27,8 +28,13 @@ internal ResourceContents() public string? MimeType { get; set; } } -internal class ResourceContentsConverter : JsonConverter +/// +/// Converter for . +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public class ResourceContentsConverter : JsonConverter { + /// public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) @@ -100,9 +106,10 @@ internal class ResourceContentsConverter : JsonConverter return null; } + /// public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options) { - if (value == null) + if (value is null) { writer.WriteNullValue(); return; From 54c7829249a0b35d04d719f55440749973054cdd Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 12:02:13 +1100 Subject: [PATCH 19/36] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../Protocol/Types/Resource.cs | 1 - .../Protocol/Types/ResourceContents.cs | 16 +++++++--------- .../Server/McpServerResourceTests.cs | 1 + 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ModelContextProtocol/Protocol/Types/Resource.cs b/src/ModelContextProtocol/Protocol/Types/Resource.cs index 20d9cb1a..b5fc15fe 100644 --- a/src/ModelContextProtocol/Protocol/Types/Resource.cs +++ b/src/ModelContextProtocol/Protocol/Types/Resource.cs @@ -42,7 +42,6 @@ public record Resource /// This can be used by Hosts to display file sizes and estimate context window usage. /// [JsonPropertyName("size")] - public long? Size { get; init; } /// diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 460389af..8836a2d9 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -52,20 +52,15 @@ public class ResourceContentsConverter : JsonConverter string? blob = null; string? text = null; - while (reader.Read()) + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - if (reader.TokenType == JsonTokenType.EndObject) - { - break; - } - - string? propertyName = reader.GetString(); - - if (propertyName == null) + if (reader.TokenType != JsonTokenType.PropertyName) { continue; } + string propertyName = reader.GetString(); + switch (propertyName) { case "uri": @@ -94,6 +89,7 @@ public class ResourceContentsConverter : JsonConverter Blob = blob }; } + if (text is not null) { return new TextResourceContents @@ -103,6 +99,7 @@ public class ResourceContentsConverter : JsonConverter Text = text }; } + return null; } @@ -118,6 +115,7 @@ public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSe writer.WriteStartObject(); writer.WriteString("uri", value.Uri); writer.WriteString("mimeType", value.MimeType); + Debug.Assert(value is BlobResourceContents or TextResourceContents); if (value is BlobResourceContents blobResource) { writer.WriteString("blob", blobResource.Blob); diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index 706e2099..ff946333 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -3,6 +3,7 @@ using ModelContextProtocol.Server; namespace ModelContextProtocol.Tests.Server; + public class McpServerResourceTests { [Fact] From f24dd181018e0102d03c2e3c2030fda94127bd31 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 30 Mar 2025 21:05:12 -0400 Subject: [PATCH 20/36] Update src/ModelContextProtocol/Protocol/Types/ResourceContents.cs --- src/ModelContextProtocol/Protocol/Types/ResourceContents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 8836a2d9..1cfea5a2 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -59,7 +59,7 @@ public class ResourceContentsConverter : JsonConverter continue; } - string propertyName = reader.GetString(); + string? propertyName = reader.GetString(); switch (propertyName) { From d7531838f61b94c38847b13fb748cc1a2a9b8ebf Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 12:06:08 +1100 Subject: [PATCH 21/36] Addressing feedback --- .../Protocol/Types/BlobResourceContents.cs | 4 +- .../Protocol/Types/ResourceContents.cs | 166 +++++++++--------- .../Protocol/Types/TextResourceContents.cs | 2 +- 3 files changed, 87 insertions(+), 85 deletions(-) diff --git a/src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs index 4f31846e..f0a16eaf 100644 --- a/src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/BlobResourceContents.cs @@ -4,7 +4,7 @@ namespace ModelContextProtocol.Protocol.Types; /// /// Binary contents of a resource. -/// See the schema for details +/// See the schema for details /// public class BlobResourceContents : ResourceContents { @@ -12,5 +12,5 @@ public class BlobResourceContents : ResourceContents /// The base64-encoded string representing the binary data of the item. /// [JsonPropertyName("blob")] - public string Blob { get; set; } = default!; + public string Blob { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs index 8836a2d9..1daece16 100644 --- a/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/ResourceContents.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics; using System.Text.Json; using System.Text.Json.Serialization; @@ -8,7 +9,7 @@ namespace ModelContextProtocol.Protocol.Types; /// Represents the content of a resource. /// See the schema for details /// -[JsonConverter(typeof(ResourceContentsConverter))] +[JsonConverter(typeof(Converter))] public abstract class ResourceContents { internal ResourceContents() @@ -26,104 +27,105 @@ internal ResourceContents() /// [JsonPropertyName("mimeType")] public string? MimeType { get; set; } -} -/// -/// Converter for . -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public class ResourceContentsConverter : JsonConverter -{ - /// - public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - if (reader.TokenType != JsonTokenType.StartObject) + /// + /// Converter for . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class Converter : JsonConverter + { + /// + public override ResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new JsonException(); - } - - string? uri = null; - string? mimeType = null; - string? blob = null; - string? text = null; + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } - while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) - { - if (reader.TokenType != JsonTokenType.PropertyName) + if (reader.TokenType != JsonTokenType.StartObject) { - continue; + throw new JsonException(); } - string propertyName = reader.GetString(); + string? uri = null; + string? mimeType = null; + string? blob = null; + string? text = null; - switch (propertyName) + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - case "uri": - uri = reader.GetString(); - break; - case "mimeType": - mimeType = reader.GetString(); - break; - case "blob": - blob = reader.GetString(); - break; - case "text": - text = reader.GetString(); - break; - default: - break; + if (reader.TokenType != JsonTokenType.PropertyName) + { + continue; + } + + string? propertyName = reader.GetString(); + + switch (propertyName) + { + case "uri": + uri = reader.GetString(); + break; + case "mimeType": + mimeType = reader.GetString(); + break; + case "blob": + blob = reader.GetString(); + break; + case "text": + text = reader.GetString(); + break; + default: + break; + } } - } - if (blob is not null) - { - return new BlobResourceContents + if (blob is not null) { - Uri = uri ?? string.Empty, - MimeType = mimeType, - Blob = blob - }; - } + return new BlobResourceContents + { + Uri = uri ?? string.Empty, + MimeType = mimeType, + Blob = blob + }; + } - if (text is not null) - { - return new TextResourceContents + if (text is not null) { - Uri = uri ?? string.Empty, - MimeType = mimeType, - Text = text - }; - } - - return null; - } + return new TextResourceContents + { + Uri = uri ?? string.Empty, + MimeType = mimeType, + Text = text + }; + } - /// - public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options) - { - if (value is null) - { - writer.WriteNullValue(); - return; + return null; } - writer.WriteStartObject(); - writer.WriteString("uri", value.Uri); - writer.WriteString("mimeType", value.MimeType); - Debug.Assert(value is BlobResourceContents or TextResourceContents); - if (value is BlobResourceContents blobResource) + /// + public override void Write(Utf8JsonWriter writer, ResourceContents value, JsonSerializerOptions options) { - writer.WriteString("blob", blobResource.Blob); - } - else if (value is TextResourceContents textResource) - { - writer.WriteString("text", textResource.Text); + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + writer.WriteString("uri", value.Uri); + writer.WriteString("mimeType", value.MimeType); + Debug.Assert(value is BlobResourceContents or TextResourceContents); + if (value is BlobResourceContents blobResource) + { + writer.WriteString("blob", blobResource.Blob); + } + else if (value is TextResourceContents textResource) + { + writer.WriteString("text", textResource.Text); + } + writer.WriteEndObject(); } - writer.WriteEndObject(); } -} \ No newline at end of file +} diff --git a/src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs b/src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs index 1f4b202c..dd14a6c2 100644 --- a/src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs +++ b/src/ModelContextProtocol/Protocol/Types/TextResourceContents.cs @@ -4,7 +4,7 @@ namespace ModelContextProtocol.Protocol.Types; /// /// Text contents of a resource. -/// See the schema for details +/// See the schema for details /// public class TextResourceContents : ResourceContents { From 0358a34470980e333ddf49900328e6e8c520c091 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 14:04:14 +1100 Subject: [PATCH 22/36] Updating from merge --- samples/EverythingServer/Program.cs | 27 ++++++++++++++----- samples/EverythingServer/Tools/AddTool.cs | 2 +- .../Tools/AnnotatedMessageTool.cs | 2 +- samples/EverythingServer/Tools/EchoTool.cs | 2 +- .../EverythingServer/Tools/LongRunningTool.cs | 2 +- .../EverythingServer/Tools/PrintEnvTool.cs | 2 +- .../EverythingServer/Tools/SampleLlmTool.cs | 2 +- .../EverythingServer/Tools/TinyImageTool.cs | 2 +- 8 files changed, 27 insertions(+), 14 deletions(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 5d0a9fdc..1a99225b 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -72,17 +72,30 @@ var resource = ResourceGenerator.Resources[index]; - return Task.FromResult(new ReadResourceResult + if (resource.MimeType == "text/plain") { - Contents = [new ResourceContents + 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, - Blob = resource.Description, - Name = resource.Description - } - ] - }); + Uri = resource.Uri, + }] + }); + } }) ; diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs index 995e880a..132fb4bd 100644 --- a/samples/EverythingServer/Tools/AddTool.cs +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -6,6 +6,6 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class AddTool { - [McpServerTool("add"), Description("Adds two numbers.")] + [McpServerTool(Name = "add"), Description("Adds two numbers.")] public static string Add(int a, int b) => $"The sum of {a} and {b} is {a + b}"; } diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index 4f7fee47..3abd2132 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -13,7 +13,7 @@ public enum MessageType Debug, } - [McpServerTool("annotatedMessage"), Description("Generates an annotated message")] + [McpServerTool(Name = "annotatedMessage"), Description("Generates an annotated message")] public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) { throw new NotSupportedException("Unable to write annotations to the output."); diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs index 5983b495..aa93cf2a 100644 --- a/samples/EverythingServer/Tools/EchoTool.cs +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -6,6 +6,6 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class EchoTool { - [McpServerTool("echo"), Description("Echoes the message back to the client.")] + [McpServerTool(Name = "echo"), Description("Echoes the message back to the client.")] public static string Echo(string message) => $"Echo: {message}"; } diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index 487feaf1..d518acc2 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -8,7 +8,7 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class LongRunningTool { - [McpServerTool("longRunningOperation"), Description("Demonstrates a long running operation with progress updates")] + [McpServerTool(Name = "longRunningOperation"), Description("Demonstrates a long running operation with progress updates")] public static async Task LongRunningOperation( IMcpServer server, RequestContext context, diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs index 71ee9237..fe8c8d36 100644 --- a/samples/EverythingServer/Tools/PrintEnvTool.cs +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -13,7 +13,7 @@ public static class PrintEnvTool WriteIndented = true }; - [McpServerTool("printEnv"), Description("Prints all environment variables, helpful for debugging MCP server configuration")] + [McpServerTool(Name = "printEnv"), Description("Prints all environment variables, helpful for debugging MCP server configuration")] public static string PrintEnv() { Debugger.Launch(); diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index 35df89ce..87e1da15 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -9,7 +9,7 @@ public class SampleLlmTool(IMcpServer server) { private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); - [McpServerTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] + [McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] public async Task SampleLLM( [Description("The prompt to send to the LLM")] string prompt, [Description("Maximum number of tokens to generate")] int maxTokens, diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index d865e546..ec5137d1 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -8,7 +8,7 @@ namespace EverythingServer.Tools; [McpServerToolType] public static class TinyImageTool { - [McpServerTool("getTinyImage"), Description("Get a tiny image from the server")] + [McpServerTool(Name = "getTinyImage"), Description("Get a tiny image from the server")] public static IEnumerable GetTinyImage() { Debugger.Launch(); From 6889563aa101a15c8d2242e7cf126a0324b6bcff Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 14:59:52 +1100 Subject: [PATCH 23/36] Adding subscriptions --- samples/EverythingServer/Program.cs | 44 ++++++++++++++++++- .../SubscriptionMessageSender.cs | 32 ++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 samples/EverythingServer/SubscriptionMessageSender.cs diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index ba240e3c..2db30c3b 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -3,9 +3,14 @@ using ModelContextProtocol.Protocol.Types; using EverythingServer.Tools; using EverythingServer; +using ModelContextProtocol.Server; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; var builder = Host.CreateEmptyApplicationBuilder(settings: null); +HashSet subscriptions = []; + builder.Services .AddMcpServer() .WithStdioServerTransport() @@ -97,6 +102,43 @@ }); } }) + .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()); + }) ; -await builder.Build().RunAsync(); \ No newline at end of file +builder.Services.AddHostedService(sp => +{ + var server = sp.GetRequiredService(); + return new SubscriptionMessageSender(server, subscriptions); +}); + +await builder.Build().RunAsync(); diff --git a/samples/EverythingServer/SubscriptionMessageSender.cs b/samples/EverythingServer/SubscriptionMessageSender.cs new file mode 100644 index 00000000..2ffe4a5c --- /dev/null +++ b/samples/EverythingServer/SubscriptionMessageSender.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Hosting; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; +using ModelContextProtocol.Protocol.Messages; + +internal class SubscriptionMessageSender(IMcpServer server, HashSet subscriptions) : IHostedService +{ + public async Task StartAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + foreach (var uri in subscriptions) + { + await server.SendMessageAsync(new JsonRpcNotification + { + Method = "notifications/resource/updated", + Params = new ResourceUpdatedNotificationParams + { + Uri = uri, + } + }, cancellationToken); + } + + await Task.Delay(5000, cancellationToken); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} From ddf39eebab154d5c2e65b1c7406c92af1cb7668a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 31 Mar 2025 15:46:06 +1100 Subject: [PATCH 24/36] Adding completion handler --- samples/EverythingServer/Program.cs | 51 +++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 2db30c3b..accdd9d5 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -133,6 +133,57 @@ await ctx.Server.RequestSamplingAsync([ } return Task.FromResult(new EmptyResult()); }) + .WithGetCompletionHandler((ctx, ct) => + { + var exampleCompletions = new Dictionary> + { + { "style", ["casual", "formal", "technical", "friendly"] }, + { "temperature", ["0", "0.5", "0.7", "1.0"] }, + { "resourceId", ["1", "2", "3", "4", "5"] } + }; + + var @ref = ctx.Params?.Ref; + + if (@ref is null) + { + throw new NotSupportedException($"Reference is required."); + } + + var argument = ctx.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? 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}"); + }) ; builder.Services.AddHostedService(sp => From cab54dd94804cd2d2d9b8d6f76779af2031c5992 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Tue, 1 Apr 2025 11:16:49 +1100 Subject: [PATCH 25/36] Moving to using the PromptType attribute --- samples/EverythingServer/Program.cs | 50 +++++-------------- .../Prompts/ComplexPromptType.cs | 22 ++++++++ .../Prompts/SimplePromptType.cs | 11 ++++ samples/EverythingServer/Tools/AddTool.cs | 2 +- .../Tools/AnnotatedMessageTool.cs | 2 +- samples/EverythingServer/Tools/EchoTool.cs | 2 +- .../EverythingServer/Tools/LongRunningTool.cs | 2 +- .../EverythingServer/Tools/PrintEnvTool.cs | 11 ++-- .../EverythingServer/Tools/SampleLlmTool.cs | 9 ++-- .../EverythingServer/Tools/TinyImageTool.cs | 9 +--- .../McpServerBuilderExtensions.cs | 4 +- 11 files changed, 60 insertions(+), 64 deletions(-) create mode 100644 samples/EverythingServer/Prompts/ComplexPromptType.cs create mode 100644 samples/EverythingServer/Prompts/SimplePromptType.cs diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index accdd9d5..24615274 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -1,11 +1,11 @@ -using ModelContextProtocol; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; using ModelContextProtocol.Protocol.Types; -using EverythingServer.Tools; using EverythingServer; using ModelContextProtocol.Server; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; +using EverythingServer.Prompts; +using EverythingServer.Tools; var builder = Host.CreateEmptyApplicationBuilder(settings: null); @@ -14,41 +14,15 @@ builder.Services .AddMcpServer() .WithStdioServerTransport() - .WithToolsFromAssembly() - .WithListPromptsHandler((ctx, ct) => - { - return Task.FromResult(new ListPromptsResult - { - Prompts = - [ - new Prompt { Name= "simple_prompt", Description = "A prompt without arguments" }, - new Prompt { Name= "complex_prompt", Description = "A prompt with arguments", Arguments = [ - new PromptArgument { Name = "temperature", Description = "Temperature setting", Required = true }, - new PromptArgument { Name = "style", Description = "Output style", Required = false } - ] - } - ] - }); - }) - .WithGetPromptHandler((args, ct) => - { - List messages = args.Params?.Name switch - { - "simple_prompt" => [new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = "This is a simple prompt without arguments" } }], - "complex_prompt" => [ - new PromptMessage { Role = Role.User, Content = new Content { Type = "text", Text = $"This is a complex prompt with arguments: temperature={args.Params?.Arguments?["temperature"]}, style={(args.Params?.Arguments?.ContainsKey("style") == true ? args.Params?.Arguments?["style"] : "")}" } }, - new PromptMessage { Role = Role.Assistant, Content = new Content { Type = "text", Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" } }, - new PromptMessage { Role = Role.User, Content = new Content { Type = "image", Data = TinyImageTool.MCP_TINY_IMAGE.Split(",").Last(), MimeType = "image/png" } } - ] - , - _ => throw new NotSupportedException($"Unknown prompt name: {args.Params?.Name}") - }; - - return Task.FromResult(new GetPromptResult - { - Messages = messages - }); - }) + .WithTools() + .WithTools() + .WithTools() + .WithTools() + .WithTools() + .WithTools() + .WithTools() + .WithPrompts() + .WithPrompts() .WithListResourceTemplatesHandler((ctx, ct) => { return Task.FromResult(new ListResourceTemplatesResult diff --git a/samples/EverythingServer/Prompts/ComplexPromptType.cs b/samples/EverythingServer/Prompts/ComplexPromptType.cs new file mode 100644 index 00000000..8b47a07e --- /dev/null +++ b/samples/EverythingServer/Prompts/ComplexPromptType.cs @@ -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 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)]) + ]; + } +} diff --git a/samples/EverythingServer/Prompts/SimplePromptType.cs b/samples/EverythingServer/Prompts/SimplePromptType.cs new file mode 100644 index 00000000..d6ba51a3 --- /dev/null +++ b/samples/EverythingServer/Prompts/SimplePromptType.cs @@ -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"; +} diff --git a/samples/EverythingServer/Tools/AddTool.cs b/samples/EverythingServer/Tools/AddTool.cs index 132fb4bd..ccaa306d 100644 --- a/samples/EverythingServer/Tools/AddTool.cs +++ b/samples/EverythingServer/Tools/AddTool.cs @@ -4,7 +4,7 @@ namespace EverythingServer.Tools; [McpServerToolType] -public static class AddTool +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}"; diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index 3abd2132..db12034f 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -4,7 +4,7 @@ namespace EverythingServer.Tools; [McpServerToolType] -public static class AnnotatedMessageTool +public class AnnotatedMessageTool { public enum MessageType { diff --git a/samples/EverythingServer/Tools/EchoTool.cs b/samples/EverythingServer/Tools/EchoTool.cs index aa93cf2a..6abd6d36 100644 --- a/samples/EverythingServer/Tools/EchoTool.cs +++ b/samples/EverythingServer/Tools/EchoTool.cs @@ -4,7 +4,7 @@ namespace EverythingServer.Tools; [McpServerToolType] -public static class EchoTool +public class EchoTool { [McpServerTool(Name = "echo"), Description("Echoes the message back to the client.")] public static string Echo(string message) => $"Echo: {message}"; diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index d518acc2..a1df2ca2 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -6,7 +6,7 @@ namespace EverythingServer.Tools; [McpServerToolType] -public static class LongRunningTool +public class LongRunningTool { [McpServerTool(Name = "longRunningOperation"), Description("Demonstrates a long running operation with progress updates")] public static async Task LongRunningOperation( diff --git a/samples/EverythingServer/Tools/PrintEnvTool.cs b/samples/EverythingServer/Tools/PrintEnvTool.cs index fe8c8d36..ca289b5f 100644 --- a/samples/EverythingServer/Tools/PrintEnvTool.cs +++ b/samples/EverythingServer/Tools/PrintEnvTool.cs @@ -1,12 +1,11 @@ using ModelContextProtocol.Server; using System.ComponentModel; -using System.Diagnostics; using System.Text.Json; namespace EverythingServer.Tools; [McpServerToolType] -public static class PrintEnvTool +public class PrintEnvTool { private static readonly JsonSerializerOptions options = new() { @@ -14,10 +13,6 @@ public static class PrintEnvTool }; [McpServerTool(Name = "printEnv"), Description("Prints all environment variables, helpful for debugging MCP server configuration")] - public static string PrintEnv() - { - Debugger.Launch(); - var envVars = Environment.GetEnvironmentVariables(); - return JsonSerializer.Serialize(envVars, options); - } + public static string PrintEnv() => + JsonSerializer.Serialize(Environment.GetEnvironmentVariables(), options); } diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index 87e1da15..53ebfff2 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -5,18 +5,17 @@ namespace EverythingServer.Tools; [McpServerToolType] -public class SampleLlmTool(IMcpServer server) +public class SampleLlmTool { - private readonly IMcpServer _server = server ?? throw new ArgumentNullException(nameof(server)); - [McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] - public async Task SampleLLM( + public static async Task SampleLLM( + IMcpServer server, [Description("The prompt to send to the LLM")] string prompt, [Description("Maximum number of tokens to generate")] int maxTokens, CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken); + var sampleResult = await server.RequestSamplingAsync(samplingParams, cancellationToken); return $"LLM sampling result: {sampleResult.Content.Text}"; } diff --git a/samples/EverythingServer/Tools/TinyImageTool.cs b/samples/EverythingServer/Tools/TinyImageTool.cs index ec5137d1..bd88ce98 100644 --- a/samples/EverythingServer/Tools/TinyImageTool.cs +++ b/samples/EverythingServer/Tools/TinyImageTool.cs @@ -1,23 +1,18 @@ using Microsoft.Extensions.AI; using ModelContextProtocol.Server; using System.ComponentModel; -using System.Diagnostics; namespace EverythingServer.Tools; [McpServerToolType] -public static class TinyImageTool +public class TinyImageTool { [McpServerTool(Name = "getTinyImage"), Description("Get a tiny image from the server")] - public static IEnumerable GetTinyImage() - { - Debugger.Launch(); - return [ + public static IEnumerable GetTinyImage() => [ new TextContent("This is a tiny image:"), new DataContent(MCP_TINY_IMAGE), new TextContent("The image above is the MCP tiny image.") ]; - } internal const string MCP_TINY_IMAGE = "data:image/png;base64,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"; diff --git a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs index 3fbc4fea..a59de8ce 100644 --- a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs +++ b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs @@ -176,7 +176,7 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, para } /// - /// Adds types marked with the attribute from the given assembly as prompts to the server. + /// Adds types marked with the attribute from the given assembly as prompts to the server. /// /// The builder instance. /// The assembly to load the types from. Null to get the current assembly @@ -190,7 +190,7 @@ public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder b return builder.WithPrompts( from t in promptAssembly.GetTypes() - where t.GetCustomAttribute() is not null + where t.GetCustomAttribute() is not null select t); } #endregion From 8f318f5d202aa67463fb037a32447b6f474a55aa Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Tue, 1 Apr 2025 11:27:40 +1100 Subject: [PATCH 26/36] Implementing annotated tool call --- .../Tools/AnnotatedMessageTool.cs | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs index db12034f..25027faf 100644 --- a/samples/EverythingServer/Tools/AnnotatedMessageTool.cs +++ b/samples/EverythingServer/Tools/AnnotatedMessageTool.cs @@ -1,4 +1,5 @@ -using ModelContextProtocol.Server; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; using System.ComponentModel; namespace EverythingServer.Tools; @@ -14,8 +15,42 @@ public enum MessageType } [McpServerTool(Name = "annotatedMessage"), Description("Generates an annotated message")] - public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) + public static IEnumerable AnnotatedMessage(MessageType messageType, bool includeImage = true) { - throw new NotSupportedException("Unable to write annotations to the output."); + List contents = messageType switch + { + MessageType.Error => [new() + { + Type = "text", + Text = "Error: Operation failed", + Annotations = new() { Audience = [Role.User, Role.Assistant], Priority = 1.0f } + }], + MessageType.Success => [new() + { + Type = "text", + Text = "Operation completed successfully", + Annotations = new() { Audience = [Role.User], Priority = 0.7f } + }], + MessageType.Debug => [new() + { + Type = "text", + Text = "Debug: Cache hit ratio 0.95, latency 150ms", + Annotations = new() { Audience = [Role.Assistant], Priority = 0.3f } + }], + _ => throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null) + }; + + if (includeImage) + { + contents.Add(new() + { + Type = "image", + Data = TinyImageTool.MCP_TINY_IMAGE.Split(",").Last(), + MimeType = "image/png", + Annotations = new() { Audience = [Role.User], Priority = 0.5f } + }); + } + + return contents; } } From 20ab4765d184bbeb5ea3529927ab2010d0695f6b Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Tue, 1 Apr 2025 12:03:26 +1100 Subject: [PATCH 27/36] Improving how to setup logging and implementing logging handler --- .../LoggingUpdateMessageSender.cs | 54 +++++++++++++++++++ samples/EverythingServer/Program.cs | 32 +++++++++++ .../McpServerBuilderExtensions.cs | 14 ++++- .../Server/McpServerHandlers.cs | 1 + 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 samples/EverythingServer/LoggingUpdateMessageSender.cs diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs new file mode 100644 index 00000000..6da657d7 --- /dev/null +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using ModelContextProtocol.Protocol.Messages; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace EverythingServer; + +public class LoggingUpdateMessageSender(IMcpServer server) : IHostedService +{ + readonly Dictionary _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" } + }; + + public async Task StartAsync(CancellationToken cancellationToken) + { + var currentLevel = server.Services!.GetRequiredService>(); + + while (!cancellationToken.IsCancellationRequested) + { + var newLevel = (LoggingLevel)Enum.GetValues().GetValue(new Random().Next(0, Enum.GetValues(typeof(LoggingLevel)).Length))!; + + var message = new JsonRpcNotification + { + Method = "notifications/message", + Params = new + { + Level = newLevel.ToString().ToLower(), + Data = _loggingLevelMap[newLevel], + } + }; + + if (newLevel > currentLevel()) + { + await server.SendMessageAsync(message, cancellationToken); + } + + await Task.Delay(15000, cancellationToken); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 24615274..f3853122 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -6,10 +6,12 @@ using Microsoft.Extensions.DependencyInjection; using EverythingServer.Prompts; using EverythingServer.Tools; +using ModelContextProtocol.Protocol.Messages; var builder = Host.CreateEmptyApplicationBuilder(settings: null); HashSet subscriptions = []; +var _minimumLoggingLevel = LoggingLevel.Debug; builder.Services .AddMcpServer() @@ -158,6 +160,28 @@ await ctx.Server.RequestSamplingAsync([ 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.SendMessageAsync(new JsonRpcNotification + { + Method = "notifications/message", + Params = new + { + Level = "debug", + Logger = "test-server", + Data = $"Logging level set to {_minimumLoggingLevel}", + } + }, ct); + + return new EmptyResult(); + }) ; builder.Services.AddHostedService(sp => @@ -166,4 +190,12 @@ await ctx.Server.RequestSamplingAsync([ return new SubscriptionMessageSender(server, subscriptions); }); +builder.Services.AddHostedService(sp => +{ + var server = sp.GetRequiredService(); + return new LoggingUpdateMessageSender(server); +}); + +builder.Services.AddScoped>(_ => () => _minimumLoggingLevel); + await builder.Build().RunAsync(); diff --git a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs index a59de8ce..a564fd39 100644 --- a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs +++ b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs @@ -314,7 +314,7 @@ public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerB } /// - /// Sets or sets the handler for subscribe to resources messages. + /// Sets the handler for subscribe to resources messages. /// /// The builder instance. /// The handler. @@ -325,6 +325,18 @@ public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpSer builder.Services.Configure(s => s.UnsubscribeFromResourcesHandler = handler); return builder; } + + /// + /// Sets the handler for setting the logging level. + /// + /// The builder instance. + /// The handler. + public static IMcpServerBuilder WithSetLoggingLevelHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler) + { + Throw.IfNull(builder); + builder.Services.Configure(s => s.SetLoggingLevelHandler = handler); + return builder; + } #endregion #region Transports diff --git a/src/ModelContextProtocol/Server/McpServerHandlers.cs b/src/ModelContextProtocol/Server/McpServerHandlers.cs index e472a7ae..b591182d 100644 --- a/src/ModelContextProtocol/Server/McpServerHandlers.cs +++ b/src/ModelContextProtocol/Server/McpServerHandlers.cs @@ -113,6 +113,7 @@ internal void OverwriteWithSetHandlers(McpServerOptions options) options.Capabilities.Prompts = promptsCapability; options.Capabilities.Resources = resourcesCapability; options.Capabilities.Tools = toolsCapability; + options.Capabilities.Logging = loggingCapability; options.GetCompletionHandler = GetCompletionHandler ?? options.GetCompletionHandler; } From aadf1c7f3c39a2fe09f73e144522173910e0f95a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 3 Apr 2025 09:33:36 +1100 Subject: [PATCH 28/36] Apply suggestions from code review Co-authored-by: Stephen Halter --- .../LoggingUpdateMessageSender.cs | 2 +- samples/EverythingServer/Program.cs | 23 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs index 6da657d7..c88e66bc 100644 --- a/samples/EverythingServer/LoggingUpdateMessageSender.cs +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -26,7 +26,7 @@ public async Task StartAsync(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { - var newLevel = (LoggingLevel)Enum.GetValues().GetValue(new Random().Next(0, Enum.GetValues(typeof(LoggingLevel)).Length))!; + var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count); var message = new JsonRpcNotification { diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index f3853122..c4789a83 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -8,7 +8,12 @@ using EverythingServer.Tools; using ModelContextProtocol.Protocol.Messages; -var builder = Host.CreateEmptyApplicationBuilder(settings: null); +var builder = Host.CreateApplicationBuilder(args); +builder.Logging.AddConsole(consoleLogOptions => +{ + // Configure all logs to go to stderr + consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; +}); HashSet subscriptions = []; var _minimumLoggingLevel = LoggingLevel.Debug; @@ -184,18 +189,10 @@ await ctx.Server.SendMessageAsync(new JsonRpcNotification }) ; -builder.Services.AddHostedService(sp => -{ - var server = sp.GetRequiredService(); - return new SubscriptionMessageSender(server, subscriptions); -}); - -builder.Services.AddHostedService(sp => -{ - var server = sp.GetRequiredService(); - return new LoggingUpdateMessageSender(server); -}); +builder.Services.AddSingleton(subscriptions); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); -builder.Services.AddScoped>(_ => () => _minimumLoggingLevel); +builder.Services.AddSingleton>(_ => () => _minimumLoggingLevel); await builder.Build().RunAsync(); From 6ba94be558cb1098b25fd785b1a2e7067176ebd1 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 3 Apr 2025 09:51:15 +1100 Subject: [PATCH 29/36] Fixing build errors --- .../LoggingUpdateMessageSender.cs | 11 ++++------- samples/EverythingServer/Program.cs | 15 +++------------ .../EverythingServer/SubscriptionMessageSender.cs | 12 ++++-------- samples/EverythingServer/Tools/LongRunningTool.cs | 11 ++++------- 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs index c88e66bc..5324f314 100644 --- a/samples/EverythingServer/LoggingUpdateMessageSender.cs +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using ModelContextProtocol; using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; @@ -28,19 +29,15 @@ public async Task StartAsync(CancellationToken cancellationToken) { var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count); - var message = new JsonRpcNotification - { - Method = "notifications/message", - Params = new + var message = new { Level = newLevel.ToString().ToLower(), Data = _loggingLevelMap[newLevel], - } - }; + }; if (newLevel > currentLevel()) { - await server.SendMessageAsync(message, cancellationToken); + await server.SendNotificationAsync("notifications/message", message, cancellationToken: cancellationToken); } await Task.Delay(15000, cancellationToken); diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index c4789a83..758f5a3e 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -6,14 +6,9 @@ using Microsoft.Extensions.DependencyInjection; using EverythingServer.Prompts; using EverythingServer.Tools; -using ModelContextProtocol.Protocol.Messages; +using ModelContextProtocol; var builder = Host.CreateApplicationBuilder(args); -builder.Logging.AddConsole(consoleLogOptions => -{ - // Configure all logs to go to stderr - consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; -}); HashSet subscriptions = []; var _minimumLoggingLevel = LoggingLevel.Debug; @@ -174,16 +169,12 @@ await ctx.Server.RequestSamplingAsync([ _minimumLoggingLevel = ctx.Params.Level; - await ctx.Server.SendMessageAsync(new JsonRpcNotification - { - Method = "notifications/message", - Params = new + await ctx.Server.SendNotificationAsync("notifications/message", new { Level = "debug", Logger = "test-server", Data = $"Logging level set to {_minimumLoggingLevel}", - } - }, ct); + }, cancellationToken: ct); return new EmptyResult(); }) diff --git a/samples/EverythingServer/SubscriptionMessageSender.cs b/samples/EverythingServer/SubscriptionMessageSender.cs index 2ffe4a5c..c1709bc0 100644 --- a/samples/EverythingServer/SubscriptionMessageSender.cs +++ b/samples/EverythingServer/SubscriptionMessageSender.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Hosting; -using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol; using ModelContextProtocol.Server; -using ModelContextProtocol.Protocol.Messages; internal class SubscriptionMessageSender(IMcpServer server, HashSet subscriptions) : IHostedService { @@ -11,14 +10,11 @@ public async Task StartAsync(CancellationToken cancellationToken) { foreach (var uri in subscriptions) { - await server.SendMessageAsync(new JsonRpcNotification - { - Method = "notifications/resource/updated", - Params = new ResourceUpdatedNotificationParams + await server.SendNotificationAsync("notifications/resource/updated", + new { Uri = uri, - } - }, cancellationToken); + }, cancellationToken: cancellationToken); } await Task.Delay(5000, cancellationToken); diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index a1df2ca2..86acc84d 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -1,4 +1,5 @@ -using ModelContextProtocol.Protocol.Messages; +using ModelContextProtocol; +using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; using System.ComponentModel; @@ -24,16 +25,12 @@ public static async Task LongRunningOperation( if (progressToken is not null) { - await server.SendMessageAsync(new JsonRpcNotification - { - Method = "notifications/progress", - Params = new + await server.SendNotificationAsync("notifications/progress", new { Progress = i, Total = steps, progressToken - } - }); + }); } } From dd1ffe54fd0ccc5c3745bd07b25171c7c81adec3 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 3 Apr 2025 10:11:08 +1100 Subject: [PATCH 30/36] Converting to proper background services --- .../LoggingUpdateMessageSender.cs | 16 +++++----------- samples/EverythingServer/Program.cs | 18 ++++++++++++------ .../SubscriptionMessageSender.cs | 15 +++++---------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs index 5324f314..c7daaf06 100644 --- a/samples/EverythingServer/LoggingUpdateMessageSender.cs +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ModelContextProtocol; -using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Server; namespace EverythingServer; -public class LoggingUpdateMessageSender(IMcpServer server) : IHostedService +public class LoggingUpdateMessageSender(IMcpServer server) : BackgroundService { readonly Dictionary _loggingLevelMap = new() { @@ -21,11 +20,11 @@ public class LoggingUpdateMessageSender(IMcpServer server) : IHostedService { LoggingLevel.Emergency, "Emergency-level message" } }; - public async Task StartAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var currentLevel = server.Services!.GetRequiredService>(); - while (!cancellationToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) { var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count); @@ -37,15 +36,10 @@ public async Task StartAsync(CancellationToken cancellationToken) if (newLevel > currentLevel()) { - await server.SendNotificationAsync("notifications/message", message, cancellationToken: cancellationToken); + await server.SendNotificationAsync("notifications/message", message, cancellationToken: stoppingToken); } - await Task.Delay(15000, cancellationToken); + await Task.Delay(15000, stoppingToken); } } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } } \ No newline at end of file diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 758f5a3e..7b13f4a5 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -1,14 +1,20 @@ -using Microsoft.Extensions.Hosting; -using ModelContextProtocol.Protocol.Types; -using EverythingServer; -using ModelContextProtocol.Server; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.DependencyInjection; +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 subscriptions = []; var _minimumLoggingLevel = LoggingLevel.Debug; diff --git a/samples/EverythingServer/SubscriptionMessageSender.cs b/samples/EverythingServer/SubscriptionMessageSender.cs index c1709bc0..774d9852 100644 --- a/samples/EverythingServer/SubscriptionMessageSender.cs +++ b/samples/EverythingServer/SubscriptionMessageSender.cs @@ -2,11 +2,11 @@ using ModelContextProtocol; using ModelContextProtocol.Server; -internal class SubscriptionMessageSender(IMcpServer server, HashSet subscriptions) : IHostedService +internal class SubscriptionMessageSender(IMcpServer server, HashSet subscriptions) : BackgroundService { - public async Task StartAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!cancellationToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) { foreach (var uri in subscriptions) { @@ -14,15 +14,10 @@ await server.SendNotificationAsync("notifications/resource/updated", new { Uri = uri, - }, cancellationToken: cancellationToken); + }, cancellationToken: stoppingToken); } - await Task.Delay(5000, cancellationToken); + await Task.Delay(5000, stoppingToken); } } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } } From 7519f9059d629591ea9cd220c5f5f13b23a767f4 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 3 Apr 2025 10:18:18 +1100 Subject: [PATCH 31/36] Adding tests for setlogginghandler --- .../Server/McpServerLoggingLevelTests.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs new file mode 100644 index 00000000..a97fe6f3 --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol.Protocol.Types; +using ModelContextProtocol.Server; + +namespace ModelContextProtocol.Tests.Server; +public class McpServerLoggingLevelTests +{ + [Fact] + public void CanCreateServerWithLoggingLevelHandler() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .WithSetLoggingLevelHandler((ctx, ct) => + { + return Task.FromResult(new EmptyResult()); + }); + + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService(); + } + + [Fact] + public void AddingLoggingLevelHandlerSetsLoggingCapability() + { + var services = new ServiceCollection(); + + services.AddMcpServer() + .WithStdioServerTransport() + .WithSetLoggingLevelHandler((ctx, ct) => + { + return Task.FromResult(new EmptyResult()); + }); + + var provider = services.BuildServiceProvider(); + + var server = provider.GetRequiredService(); + + Assert.NotNull(server.ServerOptions.Capabilities?.Logging); + Assert.NotNull(server.ServerOptions.Capabilities.Logging.SetLoggingLevelHandler); + } + + [Fact] + public void ServerWithoutCallingLoggingLevelHandlerDoesNotSetLoggingCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithStdioServerTransport(); + var provider = services.BuildServiceProvider(); + var server = provider.GetRequiredService(); + Assert.Null(server.ServerOptions.Capabilities?.Logging); + } +} From 1f25fe3cd4e2d1e7c211c40fdf80450ff8728844 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 3 Apr 2025 11:25:52 +1100 Subject: [PATCH 32/36] Fixing primary constructor usgae --- samples/EverythingServer/LoggingUpdateMessageSender.cs | 4 +--- .../Server/McpServerResourceTests.cs | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs index c7daaf06..7cb2c86b 100644 --- a/samples/EverythingServer/LoggingUpdateMessageSender.cs +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -6,7 +6,7 @@ namespace EverythingServer; -public class LoggingUpdateMessageSender(IMcpServer server) : BackgroundService +public class LoggingUpdateMessageSender(IMcpServer server, Func currentLevel) : BackgroundService { readonly Dictionary _loggingLevelMap = new() { @@ -22,8 +22,6 @@ public class LoggingUpdateMessageSender(IMcpServer server) : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - var currentLevel = server.Services!.GetRequiredService>(); - while (!stoppingToken.IsCancellationRequested) { var newLevel = (LoggingLevel)Random.Shared.Next(_loggingLevelMap.Count); diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index 706e2099..ff946333 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -3,6 +3,7 @@ using ModelContextProtocol.Server; namespace ModelContextProtocol.Tests.Server; + public class McpServerResourceTests { [Fact] From 387e8fee766e98019859ca5f6c4cb7315872d775 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 4 Apr 2025 09:20:37 +1100 Subject: [PATCH 33/36] Apply suggestions from code review Co-authored-by: Stephen Halter --- samples/EverythingServer/Program.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 7b13f4a5..1ede6a19 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -124,14 +124,13 @@ await ctx.Server.RequestSamplingAsync([ { "resourceId", ["1", "2", "3", "4", "5"] } }; - var @ref = ctx.Params?.Ref; - - if (@ref is null) + if (ctx.Params is not { } params) { - throw new NotSupportedException($"Reference is required."); + throw new NotSupportedException($"Params are required."); } - - var argument = ctx.Params!.Argument; + + var @ref = params.Ref; + var argument = params.Argument; if (@ref.Type == "ref/resource") { From 6428259a7b9b6adcdefd43b4cf5012eee1b642b4 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 4 Apr 2025 09:20:59 +1100 Subject: [PATCH 34/36] Apply suggestions from code review Co-authored-by: Stephen Halter --- samples/EverythingServer/LoggingUpdateMessageSender.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/EverythingServer/LoggingUpdateMessageSender.cs b/samples/EverythingServer/LoggingUpdateMessageSender.cs index 7cb2c86b..7b64aa2c 100644 --- a/samples/EverythingServer/LoggingUpdateMessageSender.cs +++ b/samples/EverythingServer/LoggingUpdateMessageSender.cs @@ -6,7 +6,7 @@ namespace EverythingServer; -public class LoggingUpdateMessageSender(IMcpServer server, Func currentLevel) : BackgroundService +public class LoggingUpdateMessageSender(IMcpServer server, Func getMinLevel) : BackgroundService { readonly Dictionary _loggingLevelMap = new() { @@ -32,7 +32,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) Data = _loggingLevelMap[newLevel], }; - if (newLevel > currentLevel()) + if (newLevel > getMinLevel()) { await server.SendNotificationAsync("notifications/message", message, cancellationToken: stoppingToken); } From f61784a6a14184ff5ee156d7160884a33b1828a3 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 4 Apr 2025 09:27:46 +1100 Subject: [PATCH 35/36] Fixing compiler error --- samples/EverythingServer/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 1ede6a19..6590e250 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -124,13 +124,13 @@ await ctx.Server.RequestSamplingAsync([ { "resourceId", ["1", "2", "3", "4", "5"] } }; - if (ctx.Params is not { } params) + if (ctx.Params is not { } @params) { throw new NotSupportedException($"Params are required."); } - var @ref = params.Ref; - var argument = params.Argument; + var @ref = @params.Ref; + var argument = @params.Argument; if (@ref.Type == "ref/resource") { From 7fece161fb9e721fa554a29f2b5fc1b6fd9f2d08 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 4 Apr 2025 13:15:52 -0400 Subject: [PATCH 36/36] Fix McpServerException rename --- samples/EverythingServer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 6590e250..17ee0753 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -169,7 +169,7 @@ await ctx.Server.RequestSamplingAsync([ { if (ctx.Params?.Level is null) { - throw new McpServerException("Missing required argument 'level'"); + throw new McpException("Missing required argument 'level'"); } _minimumLoggingLevel = ctx.Params.Level;