Skip to content

Commit efc0626

Browse files
authored
Merge branch 'main' into copilot/scaffold-analyzer-source-generator
2 parents fe179cf + 7e0c0f0 commit efc0626

File tree

13 files changed

+120
-53
lines changed

13 files changed

+120
-53
lines changed

.github/workflows/ci-build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
run: brew install mono
5858

5959
- name: 🔧 Set up Node.js
60-
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
60+
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
6161
with:
6262
node-version: '20'
6363

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123

124124
- name: Publish to GitHub NuGet package registry
125125
run: dotnet nuget push
126-
${{github.workspace}}/build-artifacts/packages/*.nupkg
126+
${{github.workspace}}/packages/*.nupkg
127127
--source "github"
128128
--api-key ${{ secrets.GITHUB_TOKEN }}
129129
--skip-duplicate
@@ -145,7 +145,7 @@ jobs:
145145

146146
- name: Upload release asset
147147
run: gh release upload ${{ github.event.release.tag_name }}
148-
${{ github.workspace }}/build-artifacts/packages/*.*nupkg
148+
${{ github.workspace }}/packages/*.*nupkg
149149
env:
150150
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
151151

@@ -170,7 +170,7 @@ jobs:
170170

171171
- name: Publish to NuGet.org (Releases only)
172172
run: dotnet nuget push
173-
${{github.workspace}}/build-artifacts/packages/*.nupkg
173+
${{github.workspace}}/packages/*.nupkg
174174
--source https://api.nuget.org/v3/index.json
175175
--api-key ${{ secrets.NUGET_KEY_MODELCONTEXTPROTOCOL }}
176176
--skip-duplicate

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@
7575
<PackageVersion Include="Moq" Version="4.20.72" />
7676
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
7777
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
78-
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
78+
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.13.1" />
7979
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
80-
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
80+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.13.1" />
8181
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
8282
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
8383
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.1" />

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<RepositoryUrl>https://github.com/modelcontextprotocol/csharp-sdk</RepositoryUrl>
77
<RepositoryType>git</RepositoryType>
88
<VersionPrefix>0.4.0</VersionPrefix>
9-
<VersionSuffix>preview.3</VersionSuffix>
9+
<VersionSuffix>preview.4</VersionSuffix>
1010
<Authors>ModelContextProtocolOfficial</Authors>
1111
<Copyright>© Anthropic and Contributors.</Copyright>
1212
<PackageTags>ModelContextProtocol;mcp;ai;llm</PackageTags>

src/ModelContextProtocol.AspNetCore/HttpServerTransportOptions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ public class HttpServerTransportOptions
2323
public Func<HttpContext, McpServer, CancellationToken, Task>? RunSessionHandler { get; set; }
2424

2525
/// <summary>
26-
/// Gets or sets whether the server should run in a stateless mode which allows for load balancing without session affinity.
26+
/// Gets or sets whether the server should run in a stateless mode which does not track state between requests
27+
/// allowing for load balancing without session affinity.
2728
/// </summary>
2829
/// <remarks>
29-
/// If <see langword="true"/>, <see cref="RunSessionHandler"/> is called once for every request for each request,
30-
/// the "/sse" endpoint will be disabled, and the "MCP-Session-Id" header will not be used.
30+
/// If <see langword="true"/>, <see cref="McpSession.SessionId"/> will be null, and the "MCP-Session-Id" header will not be used,
31+
/// the <see cref="RunSessionHandler"/> will be called once for for each request, and the "/sse" endpoint will be disabled.
3132
/// Unsolicited server-to-client messages and all server-to-client requests are also unsupported, because any responses
3233
/// may arrive at another ASP.NET Core application process.
3334
/// Client sampling and roots capabilities are also disabled in stateless mode, because the server cannot make requests.

src/ModelContextProtocol.Core/McpException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace ModelContextProtocol;
1212
///
1313
/// This exception type can be thrown by MCP tools or tool call filters to propagate detailed error messages
1414
/// from <see cref="Exception.Message"/> when a tool execution fails via a <see cref="CallToolResult"/>.
15-
/// For non-tool calls, this exception controls the message propogated via a <see cref="JsonRpcError"/>.
16-
///
15+
/// For non-tool calls, this exception controls the message propagated via a <see cref="JsonRpcError"/>.
16+
///
1717
/// <see cref="McpProtocolException"/> is a derived type that can be used to also specify the
1818
/// <see cref="McpErrorCode"/> that should be used for the resulting <see cref="JsonRpcError"/>.
1919
/// </remarks>

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,34 @@ private AIFunctionMcpServerResource(AIFunction function, ResourceTemplate resour
314314
public override IReadOnlyList<object> Metadata => _metadata;
315315

316316
/// <inheritdoc />
317-
public override async ValueTask<ReadResourceResult?> ReadAsync(
317+
public override bool IsMatch(string uri)
318+
{
319+
Throw.IfNull(uri);
320+
321+
// For templates, use the Regex to parse. For static resources, we can just compare the URIs.
322+
if (_uriParser is null)
323+
{
324+
// This resource is not templated.
325+
return UriTemplate.UriTemplateComparer.Instance.Equals(uri, ProtocolResourceTemplate.UriTemplate);
326+
}
327+
328+
return _uriParser.IsMatch(uri);
329+
}
330+
331+
private bool TryMatch(string uri, out Match? match)
332+
{
333+
if (_uriParser is null)
334+
{
335+
match = null;
336+
return UriTemplate.UriTemplateComparer.Instance.Equals(uri, ProtocolResourceTemplate.UriTemplate);
337+
}
338+
339+
match = _uriParser.Match(uri);
340+
return match.Success;
341+
}
342+
343+
/// <inheritdoc />
344+
public override async ValueTask<ReadResourceResult> ReadAsync(
318345
RequestContext<ReadResourceRequestParams> request, CancellationToken cancellationToken = default)
319346
{
320347
Throw.IfNull(request);
@@ -323,20 +350,9 @@ private AIFunctionMcpServerResource(AIFunction function, ResourceTemplate resour
323350

324351
cancellationToken.ThrowIfCancellationRequested();
325352

326-
// Check to see if this URI template matches the request URI. If it doesn't, return null.
327-
// For templates, use the Regex to parse. For static resources, we can just compare the URIs.
328-
Match? match = null;
329-
if (_uriParser is not null)
330-
{
331-
match = _uriParser.Match(request.Params.Uri);
332-
if (!match.Success)
333-
{
334-
return null;
335-
}
336-
}
337-
else if (!UriTemplate.UriTemplateComparer.Instance.Equals(request.Params.Uri, ProtocolResource!.Uri))
353+
if (!TryMatch(request.Params.Uri, out Match? match))
338354
{
339-
return null;
355+
throw new InvalidOperationException($"Resource '{ProtocolResourceTemplate.UriTemplate}' does not match the provided URI '{request.Params.Uri}'.");
340356
}
341357

342358
// Build up the arguments for the AIFunction call, including all of the name/value pairs from the URI.

src/ModelContextProtocol.Core/Server/DelegatingMcpServerResource.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ protected DelegatingMcpServerResource(McpServerResource innerResource)
2626
public override ResourceTemplate ProtocolResourceTemplate => _innerResource.ProtocolResourceTemplate;
2727

2828
/// <inheritdoc />
29-
public override ValueTask<ReadResourceResult?> ReadAsync(RequestContext<ReadResourceRequestParams> request, CancellationToken cancellationToken = default) =>
29+
public override bool IsMatch(string uri) => _innerResource.IsMatch(uri);
30+
31+
/// <inheritdoc />
32+
public override ValueTask<ReadResourceResult> ReadAsync(RequestContext<ReadResourceRequestParams> request, CancellationToken cancellationToken = default) =>
3033
_innerResource.ReadAsync(request, cancellationToken);
3134

3235
/// <inheritdoc />

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,7 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure
342342
{
343343
if (request.MatchedPrimitive is McpServerResource matchedResource)
344344
{
345-
if (await matchedResource.ReadAsync(request, cancellationToken).ConfigureAwait(false) is { } result)
346-
{
347-
return result;
348-
}
345+
return await matchedResource.ReadAsync(request, cancellationToken).ConfigureAwait(false);
349346
}
350347

351348
return await originalReadResourceHandler(request, cancellationToken).ConfigureAwait(false);
@@ -366,22 +363,17 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure
366363
if (request.Params?.Uri is { } uri && resources is not null)
367364
{
368365
// First try an O(1) lookup by exact match.
369-
if (resources.TryGetPrimitive(uri, out var resource))
366+
if (resources.TryGetPrimitive(uri, out var resource) && !resource.IsTemplated)
370367
{
371368
request.MatchedPrimitive = resource;
372369
}
373370
else
374371
{
375372
// Fall back to an O(N) lookup, trying to match against each URI template.
376-
// The number of templates is controlled by the server developer, and the number is expected to be
377-
// not terribly large. If that changes, this can be tweaked to enable a more efficient lookup.
378373
foreach (var resourceTemplate in resources)
379374
{
380-
// Check if this template would handle the request by testing if ReadAsync would succeed
381-
if (resourceTemplate.IsTemplated)
375+
if (resourceTemplate.IsMatch(uri))
382376
{
383-
// This is a simplified check - a more robust implementation would match the URI pattern
384-
// For now, we'll let the actual handler attempt the match
385377
request.MatchedPrimitive = resourceTemplate;
386378
break;
387379
}

src/ModelContextProtocol.Core/Server/McpServerResource.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ protected McpServerResource()
162162
/// </remarks>
163163
public abstract IReadOnlyList<object> Metadata { get; }
164164

165+
/// <summary>
166+
/// Evaluates whether the <paramref name="uri"/> matches the <see cref="ProtocolResourceTemplate"/>
167+
/// and can be used as the <see cref="ReadResourceRequestParams.Uri"/> passed to <see cref="ReadAsync"/>.
168+
/// </summary>
169+
/// <param name="uri">The URI being evaluated for this resource.</param>
170+
/// <returns>
171+
/// <see langword="true"/> if the <paramref name="uri"/> matches the <see cref="ProtocolResourceTemplate"/>; otherwise, <see langword="false"/>.
172+
/// </returns>
173+
public abstract bool IsMatch(string uri);
174+
165175
/// <summary>
166176
/// Gets the resource, rendering it with the provided request parameters and returning the resource result.
167177
/// </summary>
@@ -174,12 +184,14 @@ protected McpServerResource()
174184
/// </param>
175185
/// <returns>
176186
/// A <see cref="ValueTask{ReadResourceResult}"/> representing the asynchronous operation, containing a <see cref="ReadResourceResult"/> with
177-
/// the resource content and messages. If and only if this <see cref="McpServerResource"/> doesn't match the <see cref="ReadResourceRequestParams.Uri"/>,
178-
/// the method returns <see langword="null"/>.
187+
/// the resource content and messages.
179188
/// </returns>
180189
/// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
181-
/// <exception cref="InvalidOperationException">The resource implementation returned <see langword="null"/> or an unsupported result type.</exception>
182-
public abstract ValueTask<ReadResourceResult?> ReadAsync(
190+
/// <exception cref="InvalidOperationException">
191+
/// The <see cref="ReadResourceRequestParams.Uri"/> did not match the <see cref="ProtocolResourceTemplate"/> for this resource,
192+
/// the resource implementation returned <see langword="null"/>, or the resource implementation returned an unsupported result type.
193+
/// </exception>
194+
public abstract ValueTask<ReadResourceResult> ReadAsync(
183195
RequestContext<ReadResourceRequestParams> request,
184196
CancellationToken cancellationToken = default);
185197

0 commit comments

Comments
 (0)