From 5d445cd9267b928a53f58423461a27dd0fcc118c Mon Sep 17 00:00:00 2001 From: "Abdullah D." <48760103+abdebek@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:23:41 +0300 Subject: [PATCH 1/2] feat: implement ITokenProvider and various token provider classes for flexible authentication management --- MCPify/Core/Auth/ITokenProvider.cs | 15 +++++++ .../AuthenticationFactoryTokenProvider.cs | 40 ++++++++++++++++++ .../TokenProviders/CompositeTokenProvider.cs | 29 +++++++++++++ .../TokenProviders/McpContextTokenProvider.cs | 27 ++++++++++++ .../Auth/TokenProviders/NoTokenProvider.cs | 22 ++++++++++ .../TokenProviders/TokenProviderFactory.cs | 38 +++++++++++++++++ MCPify/Core/McpifyOptions.cs | 41 +++++++++++++++++++ MCPify/Hosting/McpifyEndpointExtensions.cs | 4 +- MCPify/Hosting/McpifyServiceRegistrar.cs | 7 +++- MCPify/Tools/OpenApiProxyTool.cs | 25 +++++++---- Tests/MCPify.Tests/OpenApiProxyToolTests.cs | 12 +++++- 11 files changed, 247 insertions(+), 13 deletions(-) create mode 100644 MCPify/Core/Auth/ITokenProvider.cs create mode 100644 MCPify/Core/Auth/TokenProviders/AuthenticationFactoryTokenProvider.cs create mode 100644 MCPify/Core/Auth/TokenProviders/CompositeTokenProvider.cs create mode 100644 MCPify/Core/Auth/TokenProviders/McpContextTokenProvider.cs create mode 100644 MCPify/Core/Auth/TokenProviders/NoTokenProvider.cs create mode 100644 MCPify/Core/Auth/TokenProviders/TokenProviderFactory.cs diff --git a/MCPify/Core/Auth/ITokenProvider.cs b/MCPify/Core/Auth/ITokenProvider.cs new file mode 100644 index 0000000..67a84a6 --- /dev/null +++ b/MCPify/Core/Auth/ITokenProvider.cs @@ -0,0 +1,15 @@ +namespace MCPify.Core.Auth; + +/// +/// Abstraction for retrieving authentication tokens. +/// Separates token acquisition from token application. +/// +public interface ITokenProvider +{ + /// + /// Retrieves an authentication token if available. + /// + /// Cancellation token + /// The token string, or null if no token is available + Task GetTokenAsync(CancellationToken cancellationToken = default); +} diff --git a/MCPify/Core/Auth/TokenProviders/AuthenticationFactoryTokenProvider.cs b/MCPify/Core/Auth/TokenProviders/AuthenticationFactoryTokenProvider.cs new file mode 100644 index 0000000..e91911c --- /dev/null +++ b/MCPify/Core/Auth/TokenProviders/AuthenticationFactoryTokenProvider.cs @@ -0,0 +1,40 @@ +namespace MCPify.Core.Auth.TokenProviders; + +/// +/// Token provider that wraps existing IAuthenticationProvider implementations. +/// Used for server-managed authentication (OAuth, API keys, etc.). +/// +public class AuthenticationFactoryTokenProvider : ITokenProvider +{ + private readonly Func? _authenticationFactory; + private readonly IServiceProvider _serviceProvider; + + public AuthenticationFactoryTokenProvider( + Func? authenticationFactory, + IServiceProvider serviceProvider) + { + _authenticationFactory = authenticationFactory; + _serviceProvider = serviceProvider; + } + + public async Task GetTokenAsync(CancellationToken cancellationToken = default) + { + if (_authenticationFactory == null) + { + return null; + } + + var authProvider = _authenticationFactory.Invoke(_serviceProvider); + + using var tempRequest = new HttpRequestMessage(); + await authProvider.ApplyAsync(tempRequest, cancellationToken); + + var authHeader = tempRequest.Headers.Authorization; + if (authHeader != null) + { + return $"{authHeader.Scheme} {authHeader.Parameter}"; + } + + return null; + } +} diff --git a/MCPify/Core/Auth/TokenProviders/CompositeTokenProvider.cs b/MCPify/Core/Auth/TokenProviders/CompositeTokenProvider.cs new file mode 100644 index 0000000..f93d623 --- /dev/null +++ b/MCPify/Core/Auth/TokenProviders/CompositeTokenProvider.cs @@ -0,0 +1,29 @@ +namespace MCPify.Core.Auth.TokenProviders; + +/// +/// Token provider that tries multiple providers in order until one returns a token. +/// Useful for implementing fallback behavior (e.g., try client token first, then server). +/// +public class CompositeTokenProvider : ITokenProvider +{ + private readonly IEnumerable _providers; + + public CompositeTokenProvider(IEnumerable providers) + { + _providers = providers ?? throw new ArgumentNullException(nameof(providers)); + } + + public async Task GetTokenAsync(CancellationToken cancellationToken = default) + { + foreach (var provider in _providers) + { + var token = await provider.GetTokenAsync(cancellationToken); + if (!string.IsNullOrEmpty(token)) + { + return token; + } + } + + return null; + } +} diff --git a/MCPify/Core/Auth/TokenProviders/McpContextTokenProvider.cs b/MCPify/Core/Auth/TokenProviders/McpContextTokenProvider.cs new file mode 100644 index 0000000..f38143c --- /dev/null +++ b/MCPify/Core/Auth/TokenProviders/McpContextTokenProvider.cs @@ -0,0 +1,27 @@ +namespace MCPify.Core.Auth.TokenProviders; + +/// +/// Token provider that retrieves tokens from the MCP context. +/// Used when the MCP client manages authentication and provides tokens. +/// +public class McpContextTokenProvider : ITokenProvider +{ + private readonly IMcpContextAccessor _mcpContextAccessor; + + public McpContextTokenProvider(IMcpContextAccessor mcpContextAccessor) + { + _mcpContextAccessor = mcpContextAccessor; + } + + public Task GetTokenAsync(CancellationToken cancellationToken = default) + { + var token = _mcpContextAccessor.AccessToken; + + if (!string.IsNullOrEmpty(token) && !token.Contains(' ')) + { + token = $"Bearer {token}"; + } + + return Task.FromResult(token); + } +} diff --git a/MCPify/Core/Auth/TokenProviders/NoTokenProvider.cs b/MCPify/Core/Auth/TokenProviders/NoTokenProvider.cs new file mode 100644 index 0000000..985e929 --- /dev/null +++ b/MCPify/Core/Auth/TokenProviders/NoTokenProvider.cs @@ -0,0 +1,22 @@ +namespace MCPify.Core.Auth.TokenProviders; + +/// +/// Token provider that provides no authentication token. +/// Used when no authentication is required (e.g., public APIs). +/// +public sealed class NoTokenProvider : ITokenProvider +{ + /// + /// Singleton instance of the no-token provider. + /// + public static readonly NoTokenProvider Instance = new(); + + private NoTokenProvider() + { + } + + public Task GetTokenAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(null); + } +} diff --git a/MCPify/Core/Auth/TokenProviders/TokenProviderFactory.cs b/MCPify/Core/Auth/TokenProviders/TokenProviderFactory.cs new file mode 100644 index 0000000..07a3c84 --- /dev/null +++ b/MCPify/Core/Auth/TokenProviders/TokenProviderFactory.cs @@ -0,0 +1,38 @@ +namespace MCPify.Core.Auth.TokenProviders; + +/// +/// Factory for creating ITokenProvider instances based on TokenSource configuration. +/// +public static class TokenProviderFactory +{ + /// + /// Creates an ITokenProvider based on the specified TokenSource and authentication factory. + /// + /// Service provider for resolving dependencies + /// The token source configuration + /// Optional authentication factory for server-managed auth + /// An ITokenProvider instance + public static ITokenProvider Create( + IServiceProvider serviceProvider, + TokenSource tokenSource, + Func? authenticationFactory = null) + { + return tokenSource switch + { + TokenSource.Server => new AuthenticationFactoryTokenProvider(authenticationFactory, serviceProvider), + + TokenSource.Client => new McpContextTokenProvider( + serviceProvider.GetRequiredService()), + + TokenSource.Both => new CompositeTokenProvider(new ITokenProvider[] + { + new McpContextTokenProvider(serviceProvider.GetRequiredService()), + new AuthenticationFactoryTokenProvider(authenticationFactory, serviceProvider) + }), + + TokenSource.None => NoTokenProvider.Instance, + + _ => throw new ArgumentOutOfRangeException(nameof(tokenSource), tokenSource, "Invalid TokenSource value") + }; + } +} diff --git a/MCPify/Core/McpifyOptions.cs b/MCPify/Core/McpifyOptions.cs index 8254d47..822692a 100644 --- a/MCPify/Core/McpifyOptions.cs +++ b/MCPify/Core/McpifyOptions.cs @@ -72,6 +72,35 @@ public class McpifyOptions } +/// +/// Defines where authentication tokens should be sourced from. +/// +public enum TokenSource +{ + /// + /// MCPify server manages authentication (OAuth flows, API keys, etc.). + /// This is the default for backward compatibility. + /// + Server, + + /// + /// MCP client provides the authentication token directly. + /// Use this when the client handles OAuth or other authentication flows. + /// + Client, + + /// + /// Try client token first, then fall back to server authentication. + /// Useful for hybrid scenarios. + /// + Both, + + /// + /// No authentication required. + /// + None +} + /// /// Defines the available transport types for the MCP server. /// @@ -142,6 +171,12 @@ public class LocalEndpointsOptions /// A factory for creating an authentication provider for local endpoints. /// public Func? AuthenticationFactory { get; set; } + + /// + /// Specifies where authentication tokens should be sourced from. + /// Defaults to Server for backward compatibility. + /// + public TokenSource TokenSource { get; set; } = TokenSource.Server; } /// @@ -183,4 +218,10 @@ public class ExternalApiOptions /// A factory for creating an authentication provider for this API. /// public Func? AuthenticationFactory { get; set; } + + /// + /// Specifies where authentication tokens should be sourced from. + /// Defaults to Server for backward compatibility. + /// + public TokenSource TokenSource { get; set; } = TokenSource.Server; } diff --git a/MCPify/Hosting/McpifyEndpointExtensions.cs b/MCPify/Hosting/McpifyEndpointExtensions.cs index cf564f9..4f4825d 100644 --- a/MCPify/Hosting/McpifyEndpointExtensions.cs +++ b/MCPify/Hosting/McpifyEndpointExtensions.cs @@ -1,5 +1,6 @@ using MCPify.Core; using MCPify.Core.Auth; +using MCPify.Core.Auth.TokenProviders; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -87,7 +88,8 @@ string BaseUrlProvider() DefaultHeaders = options.LocalEndpoints.DefaultHeaders }; - var tool = new OpenApiProxyTool(descriptor, BaseUrlProvider, httpClient, services.GetRequiredService(), localOpts, options.LocalEndpoints.AuthenticationFactory); + var tokenProvider = TokenProviderFactory.Create(services, options.LocalEndpoints.TokenSource, options.LocalEndpoints.AuthenticationFactory); + var tool = new OpenApiProxyTool(descriptor, BaseUrlProvider, httpClient, services.GetRequiredService(), localOpts, tokenProvider); toolCollection.Add(tool); count++; } diff --git a/MCPify/Hosting/McpifyServiceRegistrar.cs b/MCPify/Hosting/McpifyServiceRegistrar.cs index f8c1462..529ac81 100644 --- a/MCPify/Hosting/McpifyServiceRegistrar.cs +++ b/MCPify/Hosting/McpifyServiceRegistrar.cs @@ -4,6 +4,7 @@ using MCPify.Schema; using MCPify.Tools; using MCPify.Core.Auth; +using MCPify.Core.Auth.TokenProviders; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; @@ -146,7 +147,8 @@ private async Task RegisterExternalEndpointsAsync() apiOpts.DefaultHeaders[header.Key] = header.Value; } - var tool = new OpenApiProxyTool(descriptor, apiOptions.ApiBaseUrl, httpClient, _schema, apiOpts, apiOptions.AuthenticationFactory); + var tokenProvider = TokenProviderFactory.Create(_serviceProvider, apiOptions.TokenSource, apiOptions.AuthenticationFactory); + var tool = new OpenApiProxyTool(descriptor, apiOptions.ApiBaseUrl, httpClient, _schema, apiOpts, tokenProvider); var decoratedTool = new SessionAwareToolDecorator(tool, _serviceProvider); toolCollection.Add(decoratedTool); count++; @@ -215,7 +217,8 @@ string BaseUrlProvider() ? _options.LocalEndpoints.AuthenticationFactory : null; - var tool = new OpenApiProxyTool(descriptor, BaseUrlProvider, httpClient, _schema, localOpts, effectiveAuthFactory); + var tokenProvider = TokenProviderFactory.Create(_serviceProvider, _options.LocalEndpoints.TokenSource, effectiveAuthFactory); + var tool = new OpenApiProxyTool(descriptor, BaseUrlProvider, httpClient, _schema, localOpts, tokenProvider); var decoratedTool = new SessionAwareToolDecorator(tool, _serviceProvider); toolCollection.Add(decoratedTool); count++; diff --git a/MCPify/Tools/OpenApiProxyTool.cs b/MCPify/Tools/OpenApiProxyTool.cs index 54b2207..b2a82bf 100644 --- a/MCPify/Tools/OpenApiProxyTool.cs +++ b/MCPify/Tools/OpenApiProxyTool.cs @@ -5,6 +5,7 @@ using Microsoft.OpenApi.Models; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -20,7 +21,7 @@ public class OpenApiProxyTool : McpServerTool private readonly Func _apiBaseUrlProvider; private readonly OpenApiOperationDescriptor _descriptor; private readonly McpifyOptions _options; - private readonly Func? _authenticationFactory; + private readonly ITokenProvider _tokenProvider; private IReadOnlyList? _metadata; public OpenApiProxyTool( @@ -29,8 +30,8 @@ public OpenApiProxyTool( HttpClient http, IJsonSchemaGenerator schema, McpifyOptions options, - Func? authenticationFactory = null) - : this(descriptor, () => apiBaseUrl, http, schema, options, authenticationFactory) + ITokenProvider tokenProvider) + : this(descriptor, () => apiBaseUrl, http, schema, options, tokenProvider) { } @@ -40,14 +41,14 @@ public OpenApiProxyTool( HttpClient http, IJsonSchemaGenerator schema, McpifyOptions options, - Func? authenticationFactory = null) + ITokenProvider tokenProvider) { _descriptor = descriptor; _apiBaseUrlProvider = apiBaseUrlProvider; _http = http; _schema = schema; _options = options; - _authenticationFactory = authenticationFactory; + _tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider)); } public override Tool ProtocolTool => new() @@ -74,10 +75,18 @@ public override async ValueTask InvokeAsync( var request = BuildHttpRequest(argsDict); logger?.LogInformation("Invoking {Method} {Url}", request.Method, request.RequestUri); - if (_authenticationFactory != null) + var authToken = await _tokenProvider.GetTokenAsync(token); + if (!string.IsNullOrEmpty(authToken)) { - var authentication = _authenticationFactory.Invoke(context.Services!); - await authentication.ApplyAsync(request, token); + var parts = authToken.Split(' ', 2); + if (parts.Length == 2) + { + request.Headers.Authorization = new AuthenticationHeaderValue(parts[0], parts[1]); + } + else + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + } } var response = await _http.SendAsync(request, token); diff --git a/Tests/MCPify.Tests/OpenApiProxyToolTests.cs b/Tests/MCPify.Tests/OpenApiProxyToolTests.cs index 68fc321..d69f97d 100644 --- a/Tests/MCPify.Tests/OpenApiProxyToolTests.cs +++ b/Tests/MCPify.Tests/OpenApiProxyToolTests.cs @@ -4,9 +4,11 @@ using System.Text.Json; using MCPify.Core; using MCPify.Core.Auth; +using MCPify.Core.Auth.TokenProviders; using MCPify.Schema; using MCPify.Tests.Integration; using MCPify.Tools; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; namespace MCPify.Tests; @@ -31,13 +33,18 @@ public async Task InvokeAsync_AppliesAuthentication() ); var auth = new TrackingAuthProvider(); + var serviceProvider = new ServiceCollection() + .AddScoped() + .BuildServiceProvider(); + + var tokenProvider = new AuthenticationFactoryTokenProvider(_ => auth, serviceProvider); var tool = new OpenApiProxyTool( descriptor, _apiServer.BaseUrl, _apiServer.CreateClient(), _schema, new McpifyOptions(), - _ => auth + tokenProvider ); var request = BuildRequest(tool, null); @@ -72,7 +79,8 @@ public async Task InvokeAsync_CallsCorrectUrl() _apiServer.BaseUrl, _apiServer.CreateClient(), _schema, - new McpifyOptions() + new McpifyOptions(), + NoTokenProvider.Instance ); var request = BuildRequest(tool, new Dictionary { { "id", 123 } }); From 2a6efc5568c5f1d5d94af6f306c40d3746b0311b Mon Sep 17 00:00:00 2001 From: "Abdullah D." <48760103+abdebek@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:42:52 +0300 Subject: [PATCH 2/2] feat: enhance token management with flexible TokenSource options and update version to 0.0.13-preview --- MCPify/Core/McpifyOptions.cs | 8 +- MCPify/MCPify.csproj | 2 +- README.md | 121 ++++++++++++++++++++- Sample/Extensions/DemoServiceExtensions.cs | 7 +- Sample/README.md | 63 ++++++++++- 5 files changed, 189 insertions(+), 12 deletions(-) diff --git a/MCPify/Core/McpifyOptions.cs b/MCPify/Core/McpifyOptions.cs index 822692a..adf5329 100644 --- a/MCPify/Core/McpifyOptions.cs +++ b/MCPify/Core/McpifyOptions.cs @@ -174,9 +174,9 @@ public class LocalEndpointsOptions /// /// Specifies where authentication tokens should be sourced from. - /// Defaults to Server for backward compatibility. + /// Defaults to Both (hybrid) - tries client token first, then server authentication. /// - public TokenSource TokenSource { get; set; } = TokenSource.Server; + public TokenSource TokenSource { get; set; } = TokenSource.Both; } /// @@ -221,7 +221,7 @@ public class ExternalApiOptions /// /// Specifies where authentication tokens should be sourced from. - /// Defaults to Server for backward compatibility. + /// Defaults to Both (hybrid) - tries client token first, then server authentication. /// - public TokenSource TokenSource { get; set; } = TokenSource.Server; + public TokenSource TokenSource { get; set; } = TokenSource.Both; } diff --git a/MCPify/MCPify.csproj b/MCPify/MCPify.csproj index e802386..500d042 100644 --- a/MCPify/MCPify.csproj +++ b/MCPify/MCPify.csproj @@ -8,7 +8,7 @@ true true MCPify - 1.0.0-preview.1 + 0.0.13-preview Abdullah D. Turn any ASP.NET Core API or OpenAPI spec into a Model Context Protocol (MCP) server. Seamlessly integrates with Claude Desktop and other MCP clients. mcp;openapi;swagger;api;tools diff --git a/README.md b/README.md index 8e5cda7..4897b84 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,19 @@ **MCPify** is a .NET library that bridges the gap between your existing ASP.NET Core APIs (or external OpenAPI/Swagger specs) and the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). It allows you to expose API operations as MCP tools that can be consumed by AI assistants like Claude Desktop, ensuring seamless integration with your existing services. -> **Latest Release:** v0.0.12 - Now with enhanced OAuth middleware and improved testing infrastructure! +> **Latest Release:** v0.0.13-preview - Now with flexible token provider architecture and client-managed authentication! ## What's New -### v0.0.12 (Latest - Jan 23, 2026) +### v0.0.13-preview (Latest - Jan 27, 2026) +- **Flexible Token Provider Architecture**: New `ITokenProvider` abstraction separates token acquisition from token attachment +- **Client-Managed Authentication**: Native support for MCP clients providing tokens via `TokenSource.Client` +- **TokenSource Configuration**: Explicit control over token sourcing with `Server`, `Client`, `Both`, or `None` options +- **No More Placeholder Auth**: Eliminates the need for placeholder authentication configurations when clients handle OAuth +- **Factory Pattern**: Consolidated token provider creation via `TokenProviderFactory` +- **Better Testability**: Simplified mocking and testing with clean `ITokenProvider` interface + +### v0.0.12 (Jan 23, 2026) - **Enhanced OAuth Middleware**: Improved OAuth authentication middleware with better error handling and token management (#17) - **JWT Token Validation**: Full support for JWT access token validation including expiration, audience, and scope verification (#15) - **Per-Tool Scope Requirements**: Define granular scope requirements for specific tools using pattern matching (#15) @@ -72,6 +80,7 @@ builder.Services.AddMcpify(options => ToolPrefix = "myapp_", BaseUrlOverride = "https://localhost:5001", // Optional: override base URL Filter = op => op.Route.StartsWith("/api"), // Optional: filter endpoints + TokenSource = TokenSource.Both, // Optional: defaults to Both (hybrid) AuthenticationFactory = sp => sp.GetRequiredService() // Optional }; @@ -131,7 +140,113 @@ To use your MCPify app with [Claude Desktop](https://claude.ai/download), edit y ## Authentication -MCPify provides comprehensive OAuth 2.0 authentication support with automatic token management, validation, and scope enforcement. +MCPify provides comprehensive OAuth 2.0 authentication support with automatic token management, validation, and scope enforcement. You can choose whether MCPify manages authentication (server-side) or your MCP client provides tokens directly (client-side). + +### Token Source Configuration + +MCPify supports flexible token sourcing through the `TokenSource` configuration option. This allows you to control where authentication tokens come from: + +#### Hybrid Approach (Default - Recommended) + +Try client token first, fallback to server authentication - provides maximum flexibility: + +```csharp +builder.Services.AddMcpify(options => +{ + options.ExternalApis.Add(new ExternalApiOptions + { + ApiBaseUrl = "https://api.example.com", + OpenApiUrl = "https://api.example.com/swagger.json", + TokenSource = TokenSource.Both, // Default - best of both worlds + AuthenticationFactory = sp => new OAuthAuthorizationCodeAuthentication(...) + }); +}); +``` + +This is the recommended default because: +- If the MCP client provides a token → uses it automatically +- If no client token → falls back to server authentication +- Backward compatible with existing configurations + +#### Server-Managed Authentication + +MCPify exclusively handles OAuth flows, token storage, and refresh: + +```csharp +builder.Services.AddMcpify(options => +{ + options.ExternalApis.Add(new ExternalApiOptions + { + ApiBaseUrl = "https://api.example.com", + OpenApiUrl = "https://api.example.com/swagger.json", + TokenSource = TokenSource.Server, // Default - MCPify manages auth + AuthenticationFactory = sp => new OAuthAuthorizationCodeAuthentication(...) + }); +}); +``` + +#### Client-Managed Authentication + +Your MCP client (e.g., Claude Desktop) handles authentication and provides tokens: + +```csharp +builder.Services.AddMcpify(options => +{ + options.ExternalApis.Add(new ExternalApiOptions + { + ApiBaseUrl = "https://api.github.com", + OpenApiUrl = "https://raw.githubusercontent.com/.../api.github.com.json", + TokenSource = TokenSource.Client // Client provides tokens + }); +}); +``` + +This is useful when: +- Your MCP client handles OAuth flows +- You want to avoid duplicate authentication logic +- Tokens are managed outside MCPify + +#### Hybrid Approach + +Try client token first, fallback to server authentication: + +```csharp +builder.Services.AddMcpify(options => +{ + options.ExternalApis.Add(new ExternalApiOptions + { + ApiBaseUrl = "https://api.example.com", + OpenApiUrl = "https://api.example.com/swagger.json", + TokenSource = TokenSource.Both, // Try client first, fallback to server + AuthenticationFactory = sp => new BearerAuthentication("fallback-token") + }); +}); +``` + +#### No Authentication + +For public APIs that don't require authentication: + +```csharp +builder.Services.AddMcpify(options => +{ + options.ExternalApis.Add(new ExternalApiOptions + { + ApiBaseUrl = "https://api.publicapis.org", + OpenApiUrl = "https://api.publicapis.org/swagger.json", + TokenSource = TokenSource.None // No auth required + }); +}); +``` + +**Available TokenSource Values:** + +| Value | Description | +|-------|-------------| +| `Both` (default) | Try client token first, fallback to server authentication - provides maximum flexibility | +| `Server` | MCPify manages authentication (OAuth flows, API keys, etc.) - explicit server-only auth | +| `Client` | MCP client provides tokens directly via the protocol - explicit client-only auth | +| `None` | No authentication required | ### Enabling OAuth diff --git a/Sample/Extensions/DemoServiceExtensions.cs b/Sample/Extensions/DemoServiceExtensions.cs index 8f53b4d..e108fa9 100644 --- a/Sample/Extensions/DemoServiceExtensions.cs +++ b/Sample/Extensions/DemoServiceExtensions.cs @@ -149,18 +149,19 @@ public static IServiceCollection AddDemoMcpify(this IServiceCollection services, Enabled = true, ToolPrefix = "api_", BaseUrlOverride = baseUrl, - Filter = descriptor => + Filter = descriptor => !descriptor.Route.StartsWith("/connect") && // Hide auth endpoints !descriptor.Route.StartsWith("/auth"), // Hide callback AuthenticationFactory = sp => sp.GetRequiredService() }; - // External APIs (Petstore) + // External APIs (Petstore) - Public API, no authentication options.ExternalApis.Add(new ExternalApiOptions { ApiBaseUrl = "https://petstore.swagger.io/v2", OpenApiUrl = "https://petstore.swagger.io/v2/swagger.json", - ToolPrefix = "petstore_" + ToolPrefix = "petstore_", + TokenSource = TokenSource.None // Public API, no auth required }); // External APIs (Local File Demo) diff --git a/Sample/README.md b/Sample/README.md index 91cc0f5..dd159e4 100644 --- a/Sample/README.md +++ b/Sample/README.md @@ -127,8 +127,69 @@ AuthenticationFactory = sp => new BasicAuthentication("user", "password"); ``` Attach these either to `options.LocalEndpoints.AuthenticationFactory` or to a specific `ExternalApiOptions.AuthenticationFactory`. +### Token Source Configuration + +MCPify now supports flexible token sourcing via the `TokenSource` configuration option. This allows you to control where authentication tokens come from: + +#### Server-Managed Authentication (Default) +MCPify handles authentication flows: +```csharp +options.ExternalApis.Add(new ExternalApiOptions +{ + ApiBaseUrl = "https://api.example.com", + OpenApiUrl = "https://api.example.com/swagger.json", + TokenSource = TokenSource.Server, // Default + AuthenticationFactory = sp => new OAuthAuthorizationCodeAuthentication(...) +}); +``` + +#### Client-Managed Authentication +Let the MCP client provide tokens directly: +```csharp +options.ExternalApis.Add(new ExternalApiOptions +{ + ApiBaseUrl = "https://api.github.com", + OpenApiUrl = "https://raw.githubusercontent.com/.../api.github.com.json", + TokenSource = TokenSource.Client // Client provides tokens +}); +``` + +This is useful when: +- Your MCP client (e.g., Claude Desktop) handles OAuth +- You want to avoid duplicate authentication logic +- Tokens are managed externally + +#### Hybrid Approach +Try client token first, fallback to server authentication: +```csharp +options.ExternalApis.Add(new ExternalApiOptions +{ + ApiBaseUrl = "https://api.example.com", + OpenApiUrl = "https://api.example.com/swagger.json", + TokenSource = TokenSource.Both, // Try client, fallback to server + AuthenticationFactory = sp => new BearerAuthentication("fallback-token") +}); +``` + +#### No Authentication +For public APIs: +```csharp +options.ExternalApis.Add(new ExternalApiOptions +{ + ApiBaseUrl = "https://api.publicapis.org", + OpenApiUrl = "https://api.publicapis.org/swagger.json", + TokenSource = TokenSource.None // No auth +}); +``` + +**Available Values:** +- `TokenSource.Both` (default): Try client first, fallback to server - maximum flexibility +- `TokenSource.Server`: MCPify exclusively manages authentication +- `TokenSource.Client`: MCP client exclusively provides tokens +- `TokenSource.None`: No authentication required + ### Pass-through Bearer Tokens -If your MCP client already sends `Authorization: Bearer ` to the sample, MCPify will forward that token via `IMcpContextAccessor.AccessToken` instead of using stored OAuth/client-credentials tokens. +If your MCP client already sends `Authorization: Bearer ` to the sample, MCPify will forward that token via `IMcpContextAccessor.AccessToken` when using `TokenSource.Client` or `TokenSource.Both`. ### Protected Resource Metadata