Skip to content

Commit 42b3c75

Browse files
authored
[MCP] Add archive for collection (#9031)
1 parent 835f30e commit 42b3c75

24 files changed

+2234
-7
lines changed

src/All.slnx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,17 +185,19 @@
185185
<Folder Name="/HotChocolate/Adapters/src/">
186186
<Project Path="HotChocolate/Adapters/src/Adapters.Mcp/HotChocolate.Adapters.Mcp.csproj" />
187187
<Project Path="HotChocolate/Adapters/src/Adapters.Mcp.Core/HotChocolate.Adapters.Mcp.Core.csproj" />
188+
<Project Path="HotChocolate/Adapters/src/Adapters.Mcp.Packaging/HotChocolate.Adapters.Mcp.Packaging.csproj" />
188189
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi/HotChocolate.Adapters.OpenApi.csproj" />
190+
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/HotChocolate.Adapters.OpenApi.AspNetCore.csproj" />
189191
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi.Core/HotChocolate.Adapters.OpenApi.Core.csproj" />
192+
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi.Packaging/HotChocolate.Adapters.OpenApi.Packaging.csproj" />
190193
<Project Path="HotChocolate/Adapters/src/Fusion.Adapters.Mcp/HotChocolate.Fusion.Adapters.Mcp.csproj" />
191194
<Project Path="HotChocolate/Adapters/src/Fusion.Adapters.OpenApi/HotChocolate.Fusion.Adapters.OpenApi.csproj" />
192-
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi.Packaging/HotChocolate.Adapters.OpenApi.Packaging.csproj" />
193-
<Project Path="HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/HotChocolate.Adapters.OpenApi.AspNetCore.csproj" />
194195
</Folder>
195196
<Folder Name="/HotChocolate/Adapters/test/">
197+
<Project Path="HotChocolate/Adapters/test/Adapters.Mcp.Packaging.Tests/HotChocolate.Adapters.Mcp.Packaging.Tests.csproj" />
196198
<Project Path="HotChocolate/Adapters/test/Adapters.Mcp.Tests/HotChocolate.Adapters.Mcp.Tests.csproj" />
197-
<Project Path="HotChocolate/Adapters/test/Adapters.OpenApi.Tests/HotChocolate.Adapters.OpenApi.Tests.csproj" />
198199
<Project Path="HotChocolate/Adapters/test/Adapters.OpenApi.Packaging.Tests/HotChocolate.Adapters.OpenApi.Packaging.Tests.csproj" />
200+
<Project Path="HotChocolate/Adapters/test/Adapters.OpenApi.Tests/HotChocolate.Adapters.OpenApi.Tests.csproj" />
199201
</Folder>
200202
<Folder Name="/HotChocolate/Fusion-vnext/" />
201203
<Folder Name="/HotChocolate/Fusion-vnext/benchmarks/">
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
<Solution>
22
<Folder Name="/src/" />
33
<Folder Name="/src/Adapters.Mcp/">
4-
<Project Path="src\Adapters.Mcp.Core\HotChocolate.Adapters.Mcp.Core.csproj" />
54
<Project Path="src\Adapters.Mcp\HotChocolate.Adapters.Mcp.csproj" />
5+
<Project Path="src\Adapters.Mcp.Core\HotChocolate.Adapters.Mcp.Core.csproj" />
6+
<Project Path="src\Adapters.Mcp.Packaging\HotChocolate.Adapters.Mcp.Packaging.csproj" />
67
<Project Path="src\Fusion.Adapters.Mcp\HotChocolate.Fusion.Adapters.Mcp.csproj" />
78
</Folder>
89
<Folder Name="/src/Adapters.OpenApi/">
9-
<Project Path="src\Adapters.OpenApi.Core\HotChocolate.Adapters.OpenApi.Core.csproj" />
1010
<Project Path="src\Adapters.OpenApi\HotChocolate.Adapters.OpenApi.csproj" />
11-
<Project Path="src\Adapters.OpenApi.Packaging\HotChocolate.Adapters.OpenApi.Packaging.csproj" />
1211
<Project Path="src\Adapters.OpenApi.AspNetCore\HotChocolate.Adapters.OpenApi.AspNetCore.csproj" />
12+
<Project Path="src\Adapters.OpenApi.Core\HotChocolate.Adapters.OpenApi.Core.csproj" />
13+
<Project Path="src\Adapters.OpenApi.Packaging\HotChocolate.Adapters.OpenApi.Packaging.csproj" />
1314
<Project Path="src\Fusion.Adapters.OpenApi\HotChocolate.Fusion.Adapters.OpenApi.csproj" />
1415
</Folder>
1516
<Folder Name="/test/" />
1617
<Folder Name="/test/Adapters.Mcp.Tests/">
18+
<Project Path="test\Adapters.Mcp.Packaging.Tests\HotChocolate.Adapters.Mcp.Packaging.Tests.csproj" />
1719
<Project Path="test\Adapters.Mcp.Tests\HotChocolate.Adapters.Mcp.Tests.csproj" />
1820
</Folder>
1921
<Folder Name="/test/Adapters.OpenApi.Tests/">
20-
<Project Path="test\Adapters.OpenApi.Tests\HotChocolate.Adapters.OpenApi.Tests.csproj" />
2122
<Project Path="test\Adapters.OpenApi.Packaging.Tests\HotChocolate.Adapters.OpenApi.Packaging.Tests.csproj" />
23+
<Project Path="test\Adapters.OpenApi.Tests\HotChocolate.Adapters.OpenApi.Tests.csproj" />
2224
</Folder>
2325
</Solution>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace HotChocolate.Adapters.Mcp.Serialization;
4+
5+
public sealed record McpPromptSettingsDto
6+
{
7+
public string? Title { get; set; }
8+
9+
public string? Description { get; set; }
10+
11+
public List<McpPromptSettingsArgumentDto>? Arguments { get; set; }
12+
13+
public List<McpPromptSettingsIconDto>? Icons { get; set; }
14+
15+
public required List<McpPromptSettingsMessageDto> Messages { get; set; }
16+
}
17+
18+
public sealed record McpPromptSettingsArgumentDto
19+
{
20+
public required string Name { get; set; }
21+
22+
public string? Title { get; set; }
23+
24+
public string? Description { get; set; }
25+
26+
public bool Required { get; set; }
27+
}
28+
29+
public sealed record McpPromptSettingsIconDto
30+
{
31+
public required Uri Source { get; set; }
32+
33+
public string? MimeType { get; set; }
34+
35+
public List<string>? Sizes { get; set; }
36+
37+
public string? Theme { get; set; }
38+
}
39+
40+
public sealed record McpPromptSettingsMessageDto
41+
{
42+
public required string Role { get; set; }
43+
44+
public required McpPromptSettingsMessageContentDto Content { get; set; }
45+
}
46+
47+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
48+
[JsonDerivedType(typeof(McpPromptSettingsTextContentDto), "text")]
49+
public abstract record McpPromptSettingsMessageContentDto;
50+
51+
public sealed record McpPromptSettingsTextContentDto : McpPromptSettingsMessageContentDto
52+
{
53+
public required string Text { get; set; }
54+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Text.Json;
2+
3+
namespace HotChocolate.Adapters.Mcp.Serialization;
4+
5+
public static class McpPromptSettingsSerializer
6+
{
7+
public static JsonDocument Format(McpPromptSettingsDto settings)
8+
{
9+
return JsonSerializer.SerializeToDocument(
10+
settings,
11+
McpSettingsSerializerContext.Default.McpPromptSettingsDto);
12+
}
13+
14+
public static McpPromptSettingsDto Parse(JsonDocument document)
15+
{
16+
return document.Deserialize(McpSettingsSerializerContext.Default.McpPromptSettingsDto)
17+
?? throw new JsonException("Failed to deserialize prompt settings.");
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace HotChocolate.Adapters.Mcp.Serialization;
4+
5+
[JsonSerializable(typeof(McpPromptSettingsDto))]
6+
[JsonSerializable(typeof(McpToolSettingsDto))]
7+
[JsonSourceGenerationOptions(
8+
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
9+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
10+
internal partial class McpSettingsSerializerContext : JsonSerializerContext;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Collections.Immutable;
2+
3+
namespace HotChocolate.Adapters.Mcp.Serialization;
4+
5+
public sealed record McpToolSettingsDto
6+
{
7+
public string? Title { get; set; }
8+
9+
public List<McpToolSettingsIconDto>? Icons { get; set; }
10+
11+
public McpToolSettingsAnnotationsDto? Annotations { get; set; }
12+
13+
public McpToolSettingsOpenAiComponentDto? OpenAiComponent { get; set; }
14+
}
15+
16+
public sealed record McpToolSettingsIconDto
17+
{
18+
public required Uri Source { get; set; }
19+
20+
public string? MimeType { get; set; }
21+
22+
public List<string>? Sizes { get; set; }
23+
24+
public string? Theme { get; set; }
25+
}
26+
27+
public sealed record McpToolSettingsAnnotationsDto
28+
{
29+
public bool? DestructiveHint { get; set; }
30+
31+
public bool? IdempotentHint { get; set; }
32+
33+
public bool? OpenWorldHint { get; set; }
34+
}
35+
36+
public sealed record McpToolSettingsOpenAiComponentDto
37+
{
38+
public string? Description { get; set; }
39+
40+
public string? Domain { get; set; }
41+
42+
public bool? PrefersBorder { get; set; }
43+
44+
public string? ToolInvokingStatusText { get; set; }
45+
46+
public string? ToolInvokedStatusText { get; set; }
47+
48+
public bool? AllowToolCalls { get; set; }
49+
50+
public McpToolSettingsCspDto? Csp { get; set; }
51+
}
52+
53+
public sealed record McpToolSettingsCspDto
54+
{
55+
public ImmutableArray<string>? ConnectDomains { get; set; }
56+
57+
public ImmutableArray<string>? ResourceDomains { get; set; }
58+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Text.Json;
2+
3+
namespace HotChocolate.Adapters.Mcp.Serialization;
4+
5+
public static class McpToolSettingsSerializer
6+
{
7+
public static JsonDocument Format(McpToolSettingsDto settings)
8+
{
9+
return JsonSerializer.SerializeToDocument(
10+
settings,
11+
McpSettingsSerializerContext.Default.McpToolSettingsDto);
12+
}
13+
14+
public static McpToolSettingsDto Parse(JsonDocument document)
15+
{
16+
return document.Deserialize(McpSettingsSerializerContext.Default.McpToolSettingsDto)
17+
?? throw new JsonException("Failed to deserialize tool settings.");
18+
}
19+
}

src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Storage/OperationToolDefinition.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text;
44
using System.Text.RegularExpressions;
55
using CaseConverter;
6+
using HotChocolate.Adapters.Mcp.Serialization;
67
using HotChocolate.Language;
78
using static HotChocolate.Adapters.Mcp.Properties.McpAdapterResources;
89

@@ -116,6 +117,42 @@ public OpenAiComponent? OpenAiComponent
116117

117118
public string? OpenAiComponentOutputTemplate { get; private set; }
118119

120+
public static OperationToolDefinition From(
121+
DocumentNode document,
122+
string name,
123+
McpToolSettingsDto? settings,
124+
string? openAiComponentHtml)
125+
{
126+
return new OperationToolDefinition(document)
127+
{
128+
Name = name,
129+
Title = settings?.Title,
130+
Icons =
131+
settings?.Icons?.Select(
132+
i => new IconDefinition(i.Source)
133+
{
134+
MimeType = i.MimeType,
135+
Sizes = i.Sizes,
136+
Theme = i.Theme
137+
}).ToImmutableArray(),
138+
DestructiveHint = settings?.Annotations?.DestructiveHint,
139+
IdempotentHint = settings?.Annotations?.IdempotentHint,
140+
OpenWorldHint = settings?.Annotations?.OpenWorldHint,
141+
OpenAiComponent = openAiComponentHtml is null ? null : new OpenAiComponent(openAiComponentHtml)
142+
{
143+
Csp = settings?.OpenAiComponent?.Csp is { } csp
144+
? new OpenAiComponentCsp(csp.ConnectDomains, csp.ResourceDomains)
145+
: null,
146+
Description = settings?.OpenAiComponent?.Description,
147+
Domain = settings?.OpenAiComponent?.Domain,
148+
PrefersBorder = settings?.OpenAiComponent?.PrefersBorder,
149+
ToolInvokingStatusText = settings?.OpenAiComponent?.ToolInvokingStatusText,
150+
ToolInvokedStatusText = settings?.OpenAiComponent?.ToolInvokedStatusText,
151+
AllowToolCalls = settings?.OpenAiComponent?.AllowToolCalls ?? false
152+
}
153+
};
154+
}
155+
119156
/// <summary>Regex that validates tool names.</summary>
120157
[GeneratedRegex(@"^[A-Za-z0-9_.-]{1,128}\z")]
121158
private static partial Regex ValidateToolNameRegex();

src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Storage/PromptDefinition.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Immutable;
2+
using HotChocolate.Adapters.Mcp.Serialization;
23

34
namespace HotChocolate.Adapters.Mcp.Storage;
45

@@ -33,4 +34,50 @@ public sealed class PromptDefinition(string name)
3334
/// Gets the messages that make up the prompt.
3435
/// </summary>
3536
public ImmutableArray<PromptMessageDefinition> Messages { get; init; } = [];
37+
38+
public static PromptDefinition From(string name, McpPromptSettingsDto settings)
39+
{
40+
return new PromptDefinition(name)
41+
{
42+
Title = settings.Title,
43+
Description = settings.Description,
44+
Arguments = settings.Arguments?.Select(
45+
a => new PromptArgumentDefinition(a.Name)
46+
{
47+
Title = a.Title,
48+
Description = a.Description,
49+
Required = a.Required
50+
}).ToImmutableArray(),
51+
Icons = settings.Icons?.Select(
52+
i => new IconDefinition(i.Source)
53+
{
54+
MimeType = i.MimeType,
55+
Sizes = i.Sizes,
56+
Theme = i.Theme
57+
}).ToImmutableArray(),
58+
Messages = settings.Messages.Select(
59+
m =>
60+
{
61+
return m.Content switch
62+
{
63+
McpPromptSettingsTextContentDto content => new PromptMessageDefinition(
64+
MapRole(m.Role),
65+
new TextContentBlockDefinition(content.Text)),
66+
_ =>
67+
throw new NotSupportedException(
68+
$"Message content type '{m.Content.GetType().Name}' is not supported.")
69+
};
70+
}).ToImmutableArray()
71+
};
72+
}
73+
74+
private static RoleDefinition MapRole(string role)
75+
{
76+
return role switch
77+
{
78+
"user" => RoleDefinition.User,
79+
"assistant" => RoleDefinition.Assistant,
80+
_ => throw new NotSupportedException($"Role '{role}' is not supported.")
81+
};
82+
}
3683
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Immutable;
2+
3+
namespace HotChocolate.Adapters.Mcp.Packaging;
4+
5+
/// <summary>
6+
/// Contains metadata about an MCP Feature Collection archive.
7+
/// </summary>
8+
public record ArchiveMetadata
9+
{
10+
/// <summary>
11+
/// Gets or sets the version of the MCP Feature Collection archive format specification.
12+
/// Used to ensure compatibility between different versions of tooling.
13+
/// </summary>
14+
public Version FormatVersion { get; init; } = new("1.0.0");
15+
16+
/// <summary>
17+
/// Gets or sets the names of prompts contained in this archive.
18+
/// </summary>
19+
public ImmutableArray<string> Prompts { get; init; } = [];
20+
21+
/// <summary>
22+
/// Gets or sets the names of tools contained in this archive.
23+
/// </summary>
24+
public ImmutableArray<string> Tools { get; init; } = [];
25+
}

0 commit comments

Comments
 (0)