Skip to content
This repository was archived by the owner on Apr 18, 2026. It is now read-only.

Commit 930890e

Browse files
ANcpLuaclaude
andcommitted
feat: add MCP Tasks, Extensions, and ToolExecution experimental APIs
Implement all missing experimental features from official MCP C# SDK v1.1.0: - Tasks (QYLEXP001): McpTaskStatus, McpTask, IMcpTaskStore, InMemoryMcpTaskStore with protocol methods tasks/list, tasks/get, tasks/cancel - Extensions: empty extensions object in capabilities for future extensibility - ToolExecution: ToolTaskSupport enum (Forbidden/Optional/Required) on ToolAttribute and McpToolInfo, emitted as execution.taskSupport in tools/list - Experimentals class with QYLEXP001/QYLEXP002 diagnostic ID constants - ExperimentalAttribute polyfill for netstandard2.0 Hosting APIs (McpEndpoints, McpHost) accept optional IMcpTaskStore. Generator extracts TaskSupport from [Tool] attribute and emits on McpToolInfo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 79a3373 commit 930890e

18 files changed

Lines changed: 462 additions & 9 deletions

AGENTS.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ src/
5353
│ ├── Extraction/ # ServerExtractor, ToolExtractor, ParameterExtractor
5454
│ ├── Models/ # ServerModel, ToolModel, ToolParameterModel (value-equatable)
5555
│ └── Generation/ # DispatchEmitter, SchemaEmitter, OTelEmitter, JsonContextEmitter, MetadataEmitter
56-
└── Qyl.Agents/ # Runtime: McpHost (stdio), McpProtocolHandler (JSON-RPC)
56+
└── Qyl.Agents/ # Runtime: McpHost (stdio), McpEndpoints (HTTP), McpProtocolHandler (JSON-RPC), Tasks/
5757
5858
tests/
5959
├── NetAgents.Tests/
@@ -63,6 +63,15 @@ tests/
6363
shared/Polyfills/ # netstandard2.0 polyfills for Abstractions + Generator
6464
```
6565

66+
## Relationship to official MCP C# SDK
67+
68+
- Qyl.Agents is a compile-time source generator — no DI container, no runtime reflection.
69+
- `McpEndpoints.MapMcpServer<T>()` ≈ official SDK `MapMcp()`.
70+
- Official SDK's `AddMcpServer()`/`WithHttpTransport()`/`WithTools<T>()` DI pattern has no equivalent — tools are wired at compile time.
71+
- Official SDK experimental `RunSessionHandler` is not applicable (stateless HTTP POST, no sessions).
72+
- Tasks (`MCPEXP001`) implemented in `Qyl.Agents.Tasks``IMcpTaskStore`, `InMemoryMcpTaskStore`, protocol methods.
73+
- `[Experimental]` polyfill exists in `shared/Polyfills/` but must NOT be applied to types consumed by `Qyl.Agents` runtime — the internal polyfill metadata leaks to net10.0 consumers and causes QYLEXP errors that cannot be suppressed per repo rules.
74+
6675
## Cross-repo dependencies
6776

6877
- `Qyl.Agents.Generator` -> `ANcpLua.Roslyn.Utilities`
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// <auto-generated />
4+
5+
#nullable enable
6+
7+
#if !NET8_0_OR_GREATER
8+
9+
namespace System.Diagnostics.CodeAnalysis;
10+
11+
/// <summary>
12+
/// Indicates that an API is experimental and it may change in the future.
13+
/// </summary>
14+
[AttributeUsage(
15+
AttributeTargets.Assembly |
16+
AttributeTargets.Module |
17+
AttributeTargets.Class |
18+
AttributeTargets.Struct |
19+
AttributeTargets.Enum |
20+
AttributeTargets.Constructor |
21+
AttributeTargets.Method |
22+
AttributeTargets.Property |
23+
AttributeTargets.Field |
24+
AttributeTargets.Event |
25+
AttributeTargets.Interface |
26+
AttributeTargets.Delegate,
27+
Inherited = false)]
28+
[ExcludeFromCodeCoverage]
29+
internal sealed class ExperimentalAttribute : Attribute
30+
{
31+
public ExperimentalAttribute(string diagnosticId)
32+
{
33+
DiagnosticId = diagnosticId;
34+
}
35+
36+
/// <summary>Gets the ID that the compiler will use when reporting a use of the API the attribute applies to.</summary>
37+
public string DiagnosticId { get; }
38+
39+
/// <summary>Gets or sets the URL for corresponding documentation.</summary>
40+
public string? UrlFormat { get; set; }
41+
}
42+
43+
#endif
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace Qyl.Agents;
2+
3+
/// <summary>
4+
/// Diagnostic IDs for APIs annotated with <see cref="System.Diagnostics.CodeAnalysis.ExperimentalAttribute" />.
5+
/// <list type="bullet">
6+
/// <item><c>QYLEXP001</c> — experimental MCP specification features (Tasks, Extensions).</item>
7+
/// <item><c>QYLEXP002</c> — experimental SDK-level extensibility hooks.</item>
8+
/// </list>
9+
/// </summary>
10+
public static class Experimentals
11+
{
12+
/// <summary>Diagnostic ID for experimental MCP Tasks feature.</summary>
13+
public const string Tasks_DiagnosticId = "QYLEXP001";
14+
15+
/// <summary>Message for experimental MCP Tasks feature.</summary>
16+
public const string Tasks_Message =
17+
"The Tasks feature is experimental per the MCP specification and is subject to change.";
18+
19+
/// <summary>Diagnostic ID for experimental MCP Extensions feature.</summary>
20+
public const string Extensions_DiagnosticId = "QYLEXP001";
21+
22+
/// <summary>Message for experimental MCP Extensions feature.</summary>
23+
public const string Extensions_Message =
24+
"The Extensions feature is part of a future MCP specification version and is subject to change.";
25+
26+
/// <summary>Diagnostic ID for experimental SDK extensibility hooks.</summary>
27+
public const string Sdk_DiagnosticId = "QYLEXP002";
28+
29+
/// <summary>Message for experimental SDK extensibility hooks.</summary>
30+
public const string Sdk_Message =
31+
"This API is experimental and may be removed or changed in a future release.";
32+
}

src/Qyl.Agents.Abstractions/McpToolInfo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ public sealed class McpToolInfo
2525

2626
/// <summary>Safety annotation: open-world hint. Emitted as <c>openWorldHint</c> in MCP wire format.</summary>
2727
public bool? OpenWorldHint { get; init; }
28+
29+
/// <summary>Task execution support. Emitted as <c>execution.taskSupport</c> in MCP wire format.</summary>
30+
public ToolTaskSupport TaskSupport { get; init; }
2831
}

src/Qyl.Agents.Abstractions/Qyl.Agents.Abstractions.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@
2323
Link="Polyfills\CompilerFeatureRequiredAttribute.cs"/>
2424
<Compile Include="..\..\shared\Polyfills\SetsRequiredMembersAttribute.cs"
2525
Link="Polyfills\SetsRequiredMembersAttribute.cs"/>
26+
<Compile Include="..\..\shared\Polyfills\ExperimentalAttribute.cs"
27+
Link="Polyfills\ExperimentalAttribute.cs"/>
2628
</ItemGroup>
2729
</Project>

src/Qyl.Agents.Abstractions/ToolAttribute.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ public ToolAttribute(string name)
3333

3434
/// <summary>Hint that the tool interacts with open-world systems.</summary>
3535
public ToolHint OpenWorld { get; set; }
36+
37+
/// <summary>Declares whether the tool supports long-running task execution.</summary>
38+
public ToolTaskSupport TaskSupport { get; set; }
3639
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Qyl.Agents;
2+
3+
/// <summary>
4+
/// Declares whether a tool supports long-running task execution.
5+
/// Emitted as <c>execution.taskSupport</c> in the MCP <c>tools/list</c> response.
6+
/// </summary>
7+
public enum ToolTaskSupport : byte
8+
{
9+
/// <summary>Developer did not declare — omitted from wire format.</summary>
10+
Unset = 0,
11+
12+
/// <summary>Task execution is forbidden for this tool.</summary>
13+
Forbidden = 1,
14+
15+
/// <summary>Task execution is optional — server may return a task or an immediate result.</summary>
16+
Optional = 2,
17+
18+
/// <summary>Task execution is required — server always returns a task.</summary>
19+
Required = 3
20+
}

src/Qyl.Agents.Generator/Extraction/ToolExtractor.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static DiagnosticFlow<ToolModel> Extract(
4444
var destructive = ReadHint(toolAttr, "Destructive");
4545
var idempotent = ReadHint(toolAttr, "Idempotent");
4646
var openWorld = ReadHint(toolAttr, "OpenWorld");
47+
var taskSupport = ReadTaskSupport(toolAttr);
4748

4849
var flow = ParameterExtractor.ExtractParameters(method, cancellationToken)
4950
.Select(parameters => new ToolModel(
@@ -57,7 +58,8 @@ public static DiagnosticFlow<ToolModel> Extract(
5758
readOnly,
5859
destructive,
5960
idempotent,
60-
openWorld));
61+
openWorld,
62+
taskSupport));
6163

6264
if (readOnly == ToolHintValue.Unset &&
6365
destructive == ToolHintValue.Unset &&
@@ -115,6 +117,21 @@ private static ToolHintValue ReadHint(AttributeData? attr, string name)
115117
return ToolHintValue.Unset;
116118
}
117119

120+
private static ToolTaskSupportValue ReadTaskSupport(AttributeData? attr)
121+
{
122+
if (attr is null || attr.NamedArguments.IsDefaultOrEmpty)
123+
return ToolTaskSupportValue.Unset;
124+
125+
foreach (var arg in attr.NamedArguments)
126+
{
127+
if (string.Equals(arg.Key, "TaskSupport", StringComparison.Ordinal) &&
128+
arg.Value.Value is not null)
129+
return (ToolTaskSupportValue)Convert.ToByte(arg.Value.Value);
130+
}
131+
132+
return ToolTaskSupportValue.Unset;
133+
}
134+
118135
private static bool HasCancellationToken(IMethodSymbol method)
119136
{
120137
foreach (var p in method.Parameters)

src/Qyl.Agents.Generator/Generation/MetadataEmitter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public static void Emit(IndentedStringBuilder sb, ServerModel server)
5353
if (tool.OpenWorld != ToolHintValue.Unset)
5454
sb.AppendLine(
5555
$"OpenWorldHint = {HintBool(tool.OpenWorld)},");
56+
if (tool.TaskSupport != ToolTaskSupportValue.Unset)
57+
sb.AppendLine(
58+
$"TaskSupport = global::Qyl.Agents.ToolTaskSupport.{tool.TaskSupport},");
5659
}
5760

5861
sb.AppendLine(",");

src/Qyl.Agents.Generator/Models/ToolModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ internal readonly record struct ToolModel(
2121
ToolHintValue ReadOnly,
2222
ToolHintValue Destructive,
2323
ToolHintValue Idempotent,
24-
ToolHintValue OpenWorld)
24+
ToolHintValue OpenWorld,
25+
ToolTaskSupportValue TaskSupport)
2526
{
2627
public byte ReadOnlyHint => (byte)ReadOnly;
2728
public byte IdempotentHint => (byte)Idempotent;

0 commit comments

Comments
 (0)