Skip to content

Commit eee03da

Browse files
authored
Added support for Model Context Protocol (#8480)
1 parent be2fc1b commit eee03da

File tree

86 files changed

+7174
-7
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+7174
-7
lines changed

src/All.slnx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@
213213
<Project Path="HotChocolate/Marten/test/Data.Marten.Filters.Tests/Data.Marten.Filters.Tests.csproj" />
214214
<Project Path="HotChocolate/Marten/test/Data.Marten.Sorting.Tests/Data.Marten.Sorting.Tests.csproj" />
215215
</Folder>
216+
<Folder Name="/HotChocolate/ModelContextProtocol/" />
217+
<Folder Name="/HotChocolate/ModelContextProtocol/src/">
218+
<Project Path="HotChocolate/ModelContextProtocol/src/HotChocolate.ModelContextProtocol/HotChocolate.ModelContextProtocol.csproj" />
219+
</Folder>
220+
<Folder Name="/HotChocolate/ModelContextProtocol/test/">
221+
<Project Path="HotChocolate/ModelContextProtocol/test/HotChocolate.ModelContextProtocol.Tests/HotChocolate.ModelContextProtocol.Tests.csproj" />
222+
</Folder>
216223
<Folder Name="/HotChocolate/MongoDb/" />
217224
<Folder Name="/HotChocolate/MongoDb/src/">
218225
<Project Path="HotChocolate/MongoDb/src/Data/HotChocolate.Data.MongoDb.csproj" />

src/Directory.Packages.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
<PackageVersion Include="Basic.Reference.Assemblies.Net100" Version="1.8.2" />
1414
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.2" />
1515
<PackageVersion Include="Basic.Reference.Assemblies.Net90" Version="1.8.2" />
16+
<PackageVersion Include="CaseConverter" Version="2.0.1" />
17+
<PackageVersion Include="ChilliCream.ModelContextProtocol.AspNetCore" Version="0.0.1-p.2" />
1618
<PackageVersion Include="ChilliCream.Nitro.App" Version="$(NitroVersion)" />
1719
<PackageVersion Include="ChilliCream.Testing.Utilities" Version="0.2.0" />
1820
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
@@ -21,6 +23,7 @@
2123
<PackageVersion Include="Glob" Version="1.1.9" />
2224
<PackageVersion Include="IdentityModel" Version="4.1.1" />
2325
<PackageVersion Include="JsonPointer.Net" Version="5.0.0" />
26+
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
2427
<PackageVersion Include="Marten" Version="7.33.0" />
2528
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.0.1" />
2629
<PackageVersion Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />

src/HotChocolate/Core/src/Types/Types/Scalars/TimeSpanType.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace HotChocolate.Types;
1111
public class TimeSpanType
1212
: ScalarType<TimeSpan, StringValueNode>
1313
{
14-
private readonly TimeSpanFormat _format;
14+
public TimeSpanFormat Format { get; }
1515

1616
public TimeSpanType(
1717
TimeSpanFormat format = TimeSpanFormat.Iso8601,
@@ -27,7 +27,7 @@ public TimeSpanType(
2727
BindingBehavior bind = BindingBehavior.Explicit)
2828
: base(name, bind)
2929
{
30-
_format = format;
30+
Format = format;
3131
Description = description;
3232
}
3333

@@ -39,7 +39,7 @@ public TimeSpanType()
3939

4040
protected override TimeSpan ParseLiteral(StringValueNode valueSyntax)
4141
{
42-
if (TryDeserializeFromString(valueSyntax.Value, _format, out var value)
42+
if (TryDeserializeFromString(valueSyntax.Value, Format, out var value)
4343
&& value != null)
4444
{
4545
return value.Value;
@@ -52,7 +52,7 @@ protected override TimeSpan ParseLiteral(StringValueNode valueSyntax)
5252

5353
protected override StringValueNode ParseValue(TimeSpan runtimeValue)
5454
{
55-
return _format == TimeSpanFormat.Iso8601
55+
return Format == TimeSpanFormat.Iso8601
5656
? new StringValueNode(XmlConvert.ToString(runtimeValue))
5757
: new StringValueNode(runtimeValue.ToString("c"));
5858
}
@@ -65,7 +65,7 @@ public override IValueNode ParseResult(object? resultValue)
6565
}
6666

6767
if (resultValue is string s
68-
&& TryDeserializeFromString(s, _format, out var timeSpan))
68+
&& TryDeserializeFromString(s, Format, out var timeSpan))
6969
{
7070
return ParseValue(timeSpan);
7171
}
@@ -90,7 +90,7 @@ public override bool TrySerialize(object? runtimeValue, out object? resultValue)
9090

9191
if (runtimeValue is TimeSpan timeSpan)
9292
{
93-
if (_format == TimeSpanFormat.Iso8601)
93+
if (Format == TimeSpanFormat.Iso8601)
9494
{
9595
resultValue = XmlConvert.ToString(timeSpan);
9696
return true;
@@ -113,7 +113,7 @@ public override bool TryDeserialize(object? resultValue, out object? runtimeValu
113113
}
114114

115115
if (resultValue is string s
116-
&& TryDeserializeFromString(s, _format, out var timeSpan))
116+
&& TryDeserializeFromString(s, Format, out var timeSpan))
117117
{
118118
runtimeValue = timeSpan;
119119
return true;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Project>
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />
3+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Solution>
2+
<Folder Name="/src/">
3+
<Project Path="src/HotChocolate.ModelContextProtocol/HotChocolate.ModelContextProtocol.csproj" />
4+
</Folder>
5+
<Folder Name="/test/">
6+
<Project Path="test/HotChocolate.ModelContextProtocol.Tests/HotChocolate.ModelContextProtocol.Tests.csproj" />
7+
</Folder>
8+
</Solution>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project>
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />
3+
4+
<PropertyGroup>
5+
<NoWarn>$(NoWarn);CA1812</NoWarn>
6+
</PropertyGroup>
7+
8+
<PropertyGroup>
9+
<IsAotCompatible>true</IsAotCompatible>
10+
</PropertyGroup>
11+
12+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Reflection;
2+
using HotChocolate.ModelContextProtocol.Directives;
3+
using HotChocolate.Types;
4+
using HotChocolate.Types.Descriptors;
5+
6+
namespace HotChocolate.ModelContextProtocol.Attributes;
7+
8+
/// <summary>
9+
/// Additional properties describing a Tool to clients.
10+
/// </summary>
11+
[AttributeUsage(AttributeTargets.Method)]
12+
public sealed class McpToolAnnotationsAttribute : DescriptorAttribute
13+
{
14+
private readonly bool? _destructiveHint;
15+
private readonly bool? _idempotentHint;
16+
private readonly bool? _openWorldHint;
17+
18+
/// <summary>
19+
/// If <c>true</c>, the tool may perform destructive updates to its environment. If
20+
/// <c>false</c>, the tool performs only additive updates.
21+
/// </summary>
22+
public bool DestructiveHint
23+
{
24+
get => _destructiveHint ?? throw new InvalidOperationException();
25+
init => _destructiveHint = value;
26+
}
27+
28+
/// <summary>
29+
/// If <c>true</c>, calling the tool repeatedly with the same arguments will have no additional
30+
/// effect on its environment.
31+
/// </summary>
32+
public bool IdempotentHint
33+
{
34+
get => _idempotentHint ?? throw new InvalidOperationException();
35+
init => _idempotentHint = value;
36+
}
37+
38+
/// <summary>
39+
/// If <c>true</c>, this tool may interact with an “open world” of external entities. If
40+
/// <c>false</c>, the tool’s domain of interaction is closed. For example, the world of a web
41+
/// search tool is open, whereas that of a memory tool is not.
42+
/// </summary>
43+
public bool OpenWorldHint
44+
{
45+
get => _openWorldHint ?? throw new InvalidOperationException();
46+
init => _openWorldHint = value;
47+
}
48+
49+
protected override void TryConfigure(
50+
IDescriptorContext context,
51+
IDescriptor descriptor,
52+
ICustomAttributeProvider element)
53+
{
54+
if (descriptor is IObjectFieldDescriptor objectFieldDescriptor)
55+
{
56+
objectFieldDescriptor.Directive(
57+
new McpToolAnnotationsDirective
58+
{
59+
DestructiveHint = _destructiveHint,
60+
IdempotentHint = _idempotentHint,
61+
OpenWorldHint = _openWorldHint
62+
});
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace HotChocolate.ModelContextProtocol.Directives;
2+
3+
/// <summary>
4+
/// Additional properties describing a Tool to clients.
5+
/// </summary>
6+
public sealed class McpToolAnnotationsDirective
7+
{
8+
/// <summary>
9+
/// If <c>true</c>, the tool may perform destructive updates to its environment. If
10+
/// <c>false</c>, the tool performs only additive updates.
11+
/// </summary>
12+
public bool? DestructiveHint { get; init; }
13+
14+
/// <summary>
15+
/// If <c>true</c>, calling the tool repeatedly with the same arguments will have no additional
16+
/// effect on its environment.
17+
/// </summary>
18+
public bool? IdempotentHint { get; init; }
19+
20+
/// <summary>
21+
/// If <c>true</c>, this tool may interact with an “open world” of external entities. If
22+
/// <c>false</c>, the tool’s domain of interaction is closed. For example, the world of a web
23+
/// search tool is open, whereas that of a memory tool is not.
24+
/// </summary>
25+
public bool? OpenWorldHint { get; init; }
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace HotChocolate.ModelContextProtocol.Directives;
2+
3+
internal sealed class McpToolDirective
4+
{
5+
public string? Title { get; init; }
6+
7+
/// <summary>
8+
/// If <c>true</c>, the tool may perform destructive updates to its environment. If
9+
/// <c>false</c>, the tool performs only additive updates.
10+
/// </summary>
11+
public bool? DestructiveHint { get; init; }
12+
13+
/// <summary>
14+
/// If <c>true</c>, calling the tool repeatedly with the same arguments will have no additional
15+
/// effect on its environment.
16+
/// </summary>
17+
public bool? IdempotentHint { get; init; }
18+
19+
/// <summary>
20+
/// If <c>true</c>, this tool may interact with an “open world” of external entities. If
21+
/// <c>false</c>, the tool’s domain of interaction is closed. For example, the world of a web
22+
/// search tool is open, whereas that of a memory tool is not.
23+
/// </summary>
24+
public bool? OpenWorldHint { get; init; }
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using HotChocolate.Language;
2+
using static HotChocolate.ModelContextProtocol.Properties.ModelContextProtocolResources;
3+
4+
namespace HotChocolate.ModelContextProtocol.Directives;
5+
6+
internal static class McpToolDirectiveParser
7+
{
8+
public static McpToolDirective Parse(DirectiveNode directive)
9+
{
10+
string? title = null;
11+
bool? destructiveHint = null;
12+
bool? idempotentHint = null;
13+
bool? openWorldHint = null;
14+
15+
foreach (var argument in directive.Arguments)
16+
{
17+
switch (argument.Name.Value)
18+
{
19+
case WellKnownArgumentNames.Title:
20+
if (argument.Value is StringValueNode titleString)
21+
{
22+
title = titleString.Value;
23+
}
24+
25+
break;
26+
27+
case WellKnownArgumentNames.DestructiveHint:
28+
if (argument.Value is BooleanValueNode destructiveHintBoolean)
29+
{
30+
destructiveHint = destructiveHintBoolean.Value;
31+
}
32+
33+
break;
34+
35+
case WellKnownArgumentNames.IdempotentHint:
36+
if (argument.Value is BooleanValueNode idempotentHintBoolean)
37+
{
38+
idempotentHint = idempotentHintBoolean.Value;
39+
}
40+
41+
break;
42+
43+
case WellKnownArgumentNames.OpenWorldHint:
44+
if (argument.Value is BooleanValueNode openWorldHintBoolean)
45+
{
46+
openWorldHint = openWorldHintBoolean.Value;
47+
}
48+
49+
break;
50+
51+
default:
52+
throw new Exception(
53+
string.Format(
54+
McpToolDirectiveParser_ArgumentNotSupportedOnMcpToolDirective,
55+
argument.Name.Value));
56+
}
57+
}
58+
59+
return new McpToolDirective()
60+
{
61+
Title = title,
62+
DestructiveHint = destructiveHint,
63+
IdempotentHint = idempotentHint,
64+
OpenWorldHint = openWorldHint
65+
};
66+
}
67+
}

0 commit comments

Comments
 (0)