Skip to content

Commit 0ff5336

Browse files
authored
Merge pull request #323 from keboola/feature/mcp-server-upstream
Add MCP server with OAuth 2.1 authentication
2 parents a1c9adb + 6546253 commit 0ff5336

File tree

10 files changed

+1456
-4
lines changed

10 files changed

+1456
-4
lines changed

Directory.Packages.props

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.0-preview.1.25458.4" />
4343
<PackageVersion Include="Microsoft.SemanticKernel.Abstractions" Version="1.65.0" />
4444
<PackageVersion Include="Microsoft.SemanticKernel.Core" Version="1.65.0" />
45-
<PackageVersion Include="ModelContextProtocol.Core" Version="0.6.0-preview.1" />
46-
<PackageVersion Include="ModelContextProtocolServer.Sse" Version="0.6.0" />
45+
<PackageVersion Include="ModelContextProtocol" Version="0.8.0-preview.1" />
4746
<PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.0-rc" />
4847
<PackageVersion Include="OpenAI" Version="2.8.0" />
4948
<PackageVersion Include="Scalar.AspNetCore" Version="2.12.36" />
@@ -101,7 +100,7 @@
101100
<PackageVersion Include="FastService" Version="0.2.2" />
102101
<PackageVersion Include="FastService.Analyzers" Version="0.2.2" />
103102
<!-- 协议支持 -->
104-
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.6.0-preview.1" />
103+
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.8.0-preview.1" />
105104
<PackageVersion Include="SharpToken" Version="2.0.4" />
106105
<!-- Thor 框架相关包 (来自子模块) -->
107106
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Microsoft.AspNetCore.Authentication;
2+
using Microsoft.AspNetCore.Authentication.JwtBearer;
3+
using Microsoft.IdentityModel.Tokens;
4+
5+
namespace OpenDeepWiki.MCP;
6+
7+
/// <summary>
8+
/// Configures MCP server authentication using Google as the OAuth 2.1 authorization server.
9+
/// Implements Protected Resource Metadata (RFC 9728) for MCP client discovery.
10+
/// </summary>
11+
public static class McpAuthConfiguration
12+
{
13+
public const string McpGoogleScheme = "McpGoogle";
14+
public const string McpPolicyName = "McpAccess";
15+
16+
/// <summary>
17+
/// Adds Google OAuth token validation as a secondary authentication scheme for MCP.
18+
/// </summary>
19+
public static AuthenticationBuilder AddMcpGoogleAuth(
20+
this AuthenticationBuilder builder,
21+
IConfiguration configuration)
22+
{
23+
var googleClientId = configuration["GOOGLE_CLIENT_ID"]
24+
?? configuration["Google:ClientId"]
25+
?? throw new InvalidOperationException(
26+
"GOOGLE_CLIENT_ID is required for MCP OAuth authentication");
27+
28+
builder.AddJwtBearer(McpGoogleScheme, options =>
29+
{
30+
options.Authority = "https://accounts.google.com";
31+
options.TokenValidationParameters = new TokenValidationParameters
32+
{
33+
ValidateIssuer = true,
34+
ValidIssuers = new[]
35+
{
36+
"https://accounts.google.com",
37+
"accounts.google.com"
38+
},
39+
ValidateAudience = true,
40+
ValidAudience = googleClientId,
41+
ValidateLifetime = true,
42+
ValidateIssuerSigningKey = true,
43+
NameClaimType = "name",
44+
RoleClaimType = "roles"
45+
};
46+
47+
options.Events = new JwtBearerEvents
48+
{
49+
OnAuthenticationFailed = context =>
50+
{
51+
var logger = context.HttpContext.RequestServices
52+
.GetRequiredService<ILoggerFactory>()
53+
.CreateLogger("OpenDeepWiki.MCP.McpAuthConfiguration");
54+
logger.LogDebug("MCP Google auth failed: {Error}", context.Exception.Message);
55+
return Task.CompletedTask;
56+
}
57+
};
58+
});
59+
60+
return builder;
61+
}
62+
63+
/// <summary>
64+
/// Maps the Protected Resource Metadata endpoint (RFC 9728).
65+
/// Claude and other MCP clients fetch this to discover the authorization server.
66+
/// Mapped at both /.well-known/ (standard) and /api/.well-known/ (for reverse proxy setups).
67+
/// </summary>
68+
public static WebApplication MapProtectedResourceMetadata(
69+
this WebApplication app)
70+
{
71+
app.MapGet("/.well-known/oauth-protected-resource", HandleProtectedResourceMetadata)
72+
.AllowAnonymous();
73+
app.MapGet("/api/.well-known/oauth-protected-resource", HandleProtectedResourceMetadata)
74+
.AllowAnonymous();
75+
76+
return app;
77+
}
78+
79+
private static IResult HandleProtectedResourceMetadata(HttpContext context)
80+
{
81+
var config = context.RequestServices.GetRequiredService<IConfiguration>();
82+
// Derive public base URL from CORS origins or forwarded headers
83+
var baseUrl = config["ASPNETCORE_CORS_ORIGINS"]?.TrimEnd('/');
84+
if (string.IsNullOrEmpty(baseUrl))
85+
{
86+
var request = context.Request;
87+
var scheme = request.Headers["X-Forwarded-Proto"].FirstOrDefault() ?? request.Scheme;
88+
var host = request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? request.Host.ToString();
89+
baseUrl = $"{scheme}://{host}";
90+
}
91+
92+
var metadata = new
93+
{
94+
resource = $"{baseUrl}/api/mcp",
95+
authorization_servers = new[] { baseUrl },
96+
scopes_supported = new[] { "openid", "email", "profile" },
97+
resource_documentation = $"{baseUrl}/docs/mcp"
98+
};
99+
100+
return Results.Json(metadata);
101+
}
102+
}

0 commit comments

Comments
 (0)