Add request options bag to high level requests and include Meta (#970)
Co-authored-by: Stephen Toub <stoub@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
627de76ca6
commit
c447973972
3 changed files with 882 additions and 164 deletions
141
tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs
Normal file
141
tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Client;
|
||||
|
||||
public class McpClientMetaTests : ClientServerTestBase
|
||||
{
|
||||
public McpClientMetaTests(ITestOutputHelper outputHelper)
|
||||
: base(outputHelper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
|
||||
{
|
||||
services.Configure<McpServerOptions>(o =>
|
||||
{
|
||||
o.ServerInfo = new Implementation
|
||||
{
|
||||
Name = "test-server",
|
||||
Version = "1.0.0",
|
||||
Description = "A test server for unit testing",
|
||||
WebsiteUrl = "https://example.com",
|
||||
};
|
||||
o.ToolCollection = new ();
|
||||
o.ResourceCollection = new ();
|
||||
o.PromptCollection = new ();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ToolCallWithMetaFields()
|
||||
{
|
||||
Server.ServerOptions.ToolCollection?.Add(McpServerTool.Create(
|
||||
async (RequestContext<CallToolRequestParams> context) =>
|
||||
{
|
||||
// Access the foo property of _meta field from the request parameters
|
||||
var metaFoo = context.Params?.Meta?["foo"]?.ToString();
|
||||
|
||||
// Assert that the meta foo is correctly passed
|
||||
Assert.NotNull(metaFoo);
|
||||
|
||||
return $"Meta foo is {metaFoo}";
|
||||
},
|
||||
new () { Name = "meta_tool" }));
|
||||
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var requestOptions = new RequestOptions()
|
||||
{
|
||||
Meta = new JsonObject()
|
||||
{
|
||||
{ "foo", "bar baz" }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await client.CallToolAsync("meta_tool", options: requestOptions, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Null(result.IsError);
|
||||
|
||||
var textContent = result.Content.OfType<TextContentBlock>().FirstOrDefault();
|
||||
Assert.NotNull(textContent);
|
||||
Assert.Contains("bar baz", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResourceReadWithMetaFields()
|
||||
{
|
||||
Server.ServerOptions.ResourceCollection?.Add(McpServerResource.Create(
|
||||
(RequestContext<ReadResourceRequestParams> context) =>
|
||||
{
|
||||
// Access the foo property of _meta field from the request parameters
|
||||
var metaFoo = context.Params?.Meta?["foo"]?.ToString();
|
||||
|
||||
// Assert that the meta foo is correctly passed
|
||||
Assert.NotNull(metaFoo);
|
||||
|
||||
return $"Resource with Meta foo is {metaFoo}";
|
||||
},
|
||||
new () { UriTemplate = "test://meta_resource" }));
|
||||
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var requestOptions = new RequestOptions()
|
||||
{
|
||||
Meta = new JsonObject()
|
||||
{
|
||||
{ "foo", "bar baz" }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await client.ReadResourceAsync("test://meta_resource", options: requestOptions, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
var textContent = result.Contents.OfType<TextResourceContents>().FirstOrDefault();
|
||||
Assert.NotNull(textContent);
|
||||
Assert.Contains("bar baz", textContent.Text);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task PromptGetWithMetaFields()
|
||||
{
|
||||
Server.ServerOptions.PromptCollection?.Add(McpServerPrompt.Create(
|
||||
(RequestContext<GetPromptRequestParams> context) =>
|
||||
{
|
||||
// Access the foo property of _meta field from the request parameters
|
||||
var metaFoo = context.Params?.Meta?["foo"]?.ToString();
|
||||
|
||||
// Assert that the meta foo is correctly passed
|
||||
Assert.NotNull(metaFoo);
|
||||
|
||||
return $"Prompt with Meta foo is {metaFoo}";
|
||||
},
|
||||
new () { Name = "meta_prompt" }));
|
||||
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var requestOptions = new RequestOptions()
|
||||
{
|
||||
Meta = new JsonObject()
|
||||
{
|
||||
{ "foo", "bar baz" }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await client.GetPromptAsync("meta_prompt", options: requestOptions, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
var message = result.Messages.First();
|
||||
Assert.NotNull(message.Content);
|
||||
var textContent = message.Content as TextContentBlock;
|
||||
Assert.NotNull(textContent);
|
||||
Assert.Contains("bar baz", textContent.Text);
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var result = await client.ReadResourceAsync(uriTemplate, variables, TestContext.Current.CancellationToken);
|
||||
var result = await client.ReadResourceAsync(uriTemplate, variables, null, TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
var actualUri = Assert.IsType<TextResourceContents>(Assert.Single(result.Contents)).Text;
|
||||
|
||||
|
|
@ -110,13 +110,13 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
// The JSON from the test case files has been extracted below.
|
||||
|
||||
// Copyright 2011- The Authors
|
||||
//
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
|
@ -255,7 +255,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["X{.var:3}", "X.val"],
|
||||
["X{.list}", "X.red,green,blue"],
|
||||
["X{.list*}", "X.red.green.blue"],
|
||||
["X{.keys}", [
|
||||
["X{.keys}", [
|
||||
"X.comma,%2C,dot,.,semi,%3B",
|
||||
"X.comma,%2C,semi,%3B,dot,.",
|
||||
"X.dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -275,7 +275,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"/semi,%3B,comma,%2C,dot,.",
|
||||
"/semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{/keys*}", [
|
||||
["{/keys*}", [
|
||||
"/comma=%2C/dot=./semi=%3B",
|
||||
"/comma=%2C/semi=%3B/dot=.",
|
||||
"/dot=./comma=%2C/semi=%3B",
|
||||
|
|
@ -286,7 +286,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{;hello:5}", ";hello=Hello"],
|
||||
["{;list}", ";list=red,green,blue"],
|
||||
["{;list*}", ";list=red;list=green;list=blue"],
|
||||
["{;keys}", [
|
||||
["{;keys}", [
|
||||
";keys=comma,%2C,dot,.,semi,%3B",
|
||||
";keys=comma,%2C,semi,%3B,dot,.",
|
||||
";keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -294,7 +294,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
";keys=semi,%3B,comma,%2C,dot,.",
|
||||
";keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{;keys*}", [
|
||||
["{;keys*}", [
|
||||
";comma=%2C;dot=.;semi=%3B",
|
||||
";comma=%2C;semi=%3B;dot=.",
|
||||
";dot=.;comma=%2C;semi=%3B",
|
||||
|
|
@ -305,7 +305,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{?var:3}", "?var=val"],
|
||||
["{?list}", "?list=red,green,blue"],
|
||||
["{?list*}", "?list=red&list=green&list=blue"],
|
||||
["{?keys}", [
|
||||
["{?keys}", [
|
||||
"?keys=comma,%2C,dot,.,semi,%3B",
|
||||
"?keys=comma,%2C,semi,%3B,dot,.",
|
||||
"?keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -313,7 +313,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"?keys=semi,%3B,comma,%2C,dot,.",
|
||||
"?keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{?keys*}", [
|
||||
["{?keys*}", [
|
||||
"?comma=%2C&dot=.&semi=%3B",
|
||||
"?comma=%2C&semi=%3B&dot=.",
|
||||
"?dot=.&comma=%2C&semi=%3B",
|
||||
|
|
@ -324,7 +324,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{&var:3}", "&var=val"],
|
||||
["{&list}", "&list=red,green,blue"],
|
||||
["{&list*}", "&list=red&list=green&list=blue"],
|
||||
["{&keys}", [
|
||||
["{&keys}", [
|
||||
"&keys=comma,%2C,dot,.,semi,%3B",
|
||||
"&keys=comma,%2C,semi,%3B,dot,.",
|
||||
"&keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -332,7 +332,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"&keys=semi,%3B,comma,%2C,dot,.",
|
||||
"&keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{&keys*}", [
|
||||
["{&keys*}", [
|
||||
"&comma=%2C&dot=.&semi=%3B",
|
||||
"&comma=%2C&semi=%3B&dot=.",
|
||||
"&dot=.&comma=%2C&semi=%3B",
|
||||
|
|
@ -636,7 +636,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"/semi,%3B,comma,%2C,dot,.",
|
||||
"/semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{/keys*}", [
|
||||
["{/keys*}", [
|
||||
"/comma=%2C/dot=./semi=%3B",
|
||||
"/comma=%2C/semi=%3B/dot=.",
|
||||
"/dot=./comma=%2C/semi=%3B",
|
||||
|
|
@ -679,7 +679,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{;x,y,undef}", ";x=1024;y=768"],
|
||||
["{;list}", ";list=red,green,blue"],
|
||||
["{;list*}", ";list=red;list=green;list=blue"],
|
||||
["{;keys}", [
|
||||
["{;keys}", [
|
||||
";keys=comma,%2C,dot,.,semi,%3B",
|
||||
";keys=comma,%2C,semi,%3B,dot,.",
|
||||
";keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -687,7 +687,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
";keys=semi,%3B,comma,%2C,dot,.",
|
||||
";keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{;keys*}", [
|
||||
["{;keys*}", [
|
||||
";comma=%2C;dot=.;semi=%3B",
|
||||
";comma=%2C;semi=%3B;dot=.",
|
||||
";dot=.;comma=%2C;semi=%3B",
|
||||
|
|
@ -727,7 +727,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{?var:3}", "?var=val"],
|
||||
["{?list}", "?list=red,green,blue"],
|
||||
["{?list*}", "?list=red&list=green&list=blue"],
|
||||
["{?keys}", [
|
||||
["{?keys}", [
|
||||
"?keys=comma,%2C,dot,.,semi,%3B",
|
||||
"?keys=comma,%2C,semi,%3B,dot,.",
|
||||
"?keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -735,7 +735,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"?keys=semi,%3B,comma,%2C,dot,.",
|
||||
"?keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{?keys*}", [
|
||||
["{?keys*}", [
|
||||
"?comma=%2C&dot=.&semi=%3B",
|
||||
"?comma=%2C&semi=%3B&dot=.",
|
||||
"?dot=.&comma=%2C&semi=%3B",
|
||||
|
|
@ -775,7 +775,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
["{&x,y,undef}", "&x=1024&y=768"],
|
||||
["{&list}", "&list=red,green,blue"],
|
||||
["{&list*}", "&list=red&list=green&list=blue"],
|
||||
["{&keys}", [
|
||||
["{&keys}", [
|
||||
"&keys=comma,%2C,dot,.,semi,%3B",
|
||||
"&keys=comma,%2C,semi,%3B,dot,.",
|
||||
"&keys=dot,.,comma,%2C,semi,%3B",
|
||||
|
|
@ -783,7 +783,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
"&keys=semi,%3B,comma,%2C,dot,.",
|
||||
"&keys=semi,%3B,dot,.,comma,%2C"
|
||||
]],
|
||||
["{&keys*}", [
|
||||
["{&keys*}", [
|
||||
"&comma=%2C&dot=.&semi=%3B",
|
||||
"&comma=%2C&semi=%3B&dot=.",
|
||||
"&dot=.&comma=%2C&semi=%3B",
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await client.GetPromptAsync("Prompt", new Dictionary<string, object?> { ["i"] = 42 }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await client.GetPromptAsync("Prompt", new Dictionary<string, object?> { ["i"] = 42 }, new RequestOptions { JsonSerializerOptions = emptyOptions }, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -319,7 +319,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
JsonSerializerOptions options = new(JsonSerializerOptions.Default);
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).First();
|
||||
var tool = (await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)).First();
|
||||
var originalName = tool.Name;
|
||||
var renamedTool = tool.WithName("RenamedTool");
|
||||
|
||||
|
|
@ -333,7 +333,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
{
|
||||
JsonSerializerOptions options = new(JsonSerializerOptions.Default);
|
||||
await using McpClient client = await CreateMcpClientForServer();
|
||||
var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).FirstOrDefault();
|
||||
var tool = (await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)).FirstOrDefault();
|
||||
var originalDescription = tool?.Description;
|
||||
var redescribedTool = tool?.WithDescription("ToolWithNewDescription");
|
||||
Assert.NotNull(redescribedTool);
|
||||
|
|
@ -407,7 +407,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
Assert.False(logger.IsEnabled(LogLevel.Error));
|
||||
Assert.False(logger.IsEnabled(LogLevel.Critical));
|
||||
|
||||
await client.SetLoggingLevel(LoggingLevel.Info, TestContext.Current.CancellationToken);
|
||||
await client.SetLoggingLevel(LoggingLevel.Info, options: null, TestContext.Current.CancellationToken);
|
||||
|
||||
DateTime start = DateTime.UtcNow;
|
||||
while (Server.LoggingLevel is null)
|
||||
|
|
|
|||
Loading…
Reference in a new issue