-
Notifications
You must be signed in to change notification settings - Fork 752
Expand file tree
/
Copy pathMcpAppsIntegrationTests.cs
More file actions
96 lines (75 loc) · 3.64 KB
/
Copy pathMcpAppsIntegrationTests.cs
File metadata and controls
96 lines (75 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#pragma warning disable MCPEXP003
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Extensions.Apps;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Tests.Server;
/// <summary>
/// Round-trip integration tests for the MCP Apps <c>_meta.ui</c> metadata: tools registered
/// with <see cref="McpAppUiAttribute"/> and processed by <c>WithMcpApps()</c> are listed through
/// <see cref="McpClient.ListToolsAsync"/> and the structured <c>_meta.ui</c> object is verified
/// after serializing across the client-server transport.
/// </summary>
public class McpAppsIntegrationTests : ClientServerTestBase
{
public McpAppsIntegrationTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
{
mcpServerBuilder
.WithTools<AppUiTools>()
.WithMcpApps();
}
[Fact]
public async Task ListToolsAsync_RoundTripsMetaUi_ForToolWithAppUiAttribute()
{
await using McpClient client = await CreateMcpClientForServer();
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
var weatherTool = Assert.Single(tools, t => t.Name == "weather_tool");
JsonObject? uiNode = weatherTool.ProtocolTool.Meta?["ui"]?.AsObject();
Assert.NotNull(uiNode);
Assert.Equal("ui://weather/view.html", uiNode["resourceUri"]?.GetValue<string>());
// Visibility was not restricted, so the attribute must not add the property.
Assert.Null(uiNode["visibility"]);
}
[Fact]
public async Task ListToolsAsync_RoundTripsMetaUiVisibility_ForRestrictedTool()
{
await using McpClient client = await CreateMcpClientForServer();
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
var modelOnlyTool = Assert.Single(tools, t => t.Name == "model_only_tool");
JsonObject? uiNode = modelOnlyTool.ProtocolTool.Meta?["ui"]?.AsObject();
Assert.NotNull(uiNode);
Assert.Equal("ui://model-only/view.html", uiNode["resourceUri"]?.GetValue<string>());
JsonArray? visibility = uiNode["visibility"]?.AsArray();
Assert.NotNull(visibility);
JsonNode? single = Assert.Single(visibility);
Assert.Equal(McpUiToolVisibility.Model, single?.GetValue<string>());
}
[Fact]
public async Task ListToolsAsync_HasNoUiMeta_ForToolWithoutAppUiAttribute()
{
await using McpClient client = await CreateMcpClientForServer();
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
var plainTool = Assert.Single(tools, t => t.Name == "plain_tool");
Assert.Null(plainTool.ProtocolTool.Meta?["ui"]);
}
public sealed class AppUiTools
{
[McpServerTool(Name = "weather_tool")]
[McpAppUi(ResourceUri = "ui://weather/view.html")]
[Description("Get weather")]
public static string WeatherTool(string location) => $"Weather for {location}";
[McpServerTool(Name = "model_only_tool")]
[McpAppUi(ResourceUri = "ui://model-only/view.html", Visibility = [McpUiToolVisibility.Model])]
[Description("Model only")]
public static string ModelOnlyTool(string location) => $"Model only for {location}";
[McpServerTool(Name = "plain_tool")]
[Description("Plain tool without app UI")]
public static string PlainTool(string input) => input;
}
}