Skip to content

Add hosted MCP server#2320

Draft
ejsmith wants to merge 12 commits into
mainfrom
add-oauth-mcp-api-access
Draft

Add hosted MCP server#2320
ejsmith wants to merge 12 commits into
mainfrom
add-oauth-mcp-api-access

Conversation

@ejsmith

@ejsmith ejsmith commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

  • add a hosted MCP server endpoint at /mcp inside the existing Exceptionless app so AI tools can query a user's Exceptionless projects directly
  • expose an initial read-only MCP tool set for projects, stacks, stack events, and individual events
  • add OAuth-based authentication for MCP clients, including authorization-code + PKCE, refresh/revoke, protected-resource metadata, and CIMD client discovery
  • store MCP OAuth client applications in Elasticsearch and add management in the new Svelte system settings UI
  • keep /mcp from falling through to the web app shell by returning the correct OAuth challenge or method response for MCP clients

Testing

  • dotnet build tests\Exceptionless.Tests\Exceptionless.Tests.csproj -v:minimal -m:1
  • dotnet test tests\Exceptionless.Tests\Exceptionless.Tests.csproj -- --filter-class Exceptionless.Tests.Controllers.ControllerManifestTests
  • dotnet test tests\Exceptionless.Tests\Exceptionless.Tests.csproj -- --filter-class Exceptionless.Tests.Controllers.OAuthApplicationControllerTests --filter-class Exceptionless.Tests.Controllers.OAuthControllerTests --filter-class Exceptionless.Tests.Controllers.OpenApiControllerTests
  • dotnet test tests\Exceptionless.Tests\Exceptionless.Tests.csproj --no-restore -- --filter-class Exceptionless.Tests.Controllers.OAuthControllerTests
  • npm run lint in src/Exceptionless.Web/ClientApp
  • npm run check in src/Exceptionless.Web/ClientApp
  • npm run build in src/Exceptionless.Web/ClientApp
  • npm run openspec:validate -- add-oauth-api-access in src/Exceptionless.Web/ClientApp
  • local Aspire smoke test for OAuth + MCP tool calls against /mcp
  • local Aspire smoke test for unauthenticated/authenticated GET /mcp behavior
  • dev preview smoke test for /mcp OAuth protected-resource discovery metadata
  • git diff --check
  • rg "<<<<<<<|=======|>>>>>>>"

Notes

  • OAuth is included to let external MCP clients authenticate to the hosted Exceptionless MCP server; it is not the primary feature by itself.
  • The full CIMD discovery flow needs a public HTTPS metadata document, so the preview environment should be the practical place to test that path end-to-end.
  • Breaking changes: none.

@ejsmith

ejsmith commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

/preview

@github-actions github-actions Bot added the dev-preview Deploy this pull request to the shared dev environment. label Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Preview deployed


public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (string redirectUri in RedirectUris.Where(String.IsNullOrWhiteSpace))
yield return new ValidationResult($"'{redirectUri}' is not a valid absolute redirect URI without a fragment.", [nameof(RedirectUris)]);
}

foreach (string scope in Scopes.Where(String.IsNullOrWhiteSpace))
Comment on lines +252 to +257
var refreshResponse = await client.PostAsync("oauth/token", new FormUrlEncodedContent(new Dictionary<string, string?>
{
["grant_type"] = OAuthGrantTypes.RefreshToken,
["client_id"] = ClientId,
["refresh_token"] = token.RefreshToken
}), TestContext.Current.CancellationToken);
Comment on lines +265 to +270
var reusedRefreshResponse = await client.PostAsync("oauth/token", new FormUrlEncodedContent(new Dictionary<string, string?>
{
["grant_type"] = OAuthGrantTypes.RefreshToken,
["client_id"] = ClientId,
["refresh_token"] = token.RefreshToken
}), TestContext.Current.CancellationToken);
Comment on lines +281 to +284
var response = await client.PostAsync("oauth/revoke", new FormUrlEncodedContent(new Dictionary<string, string?>
{
["token"] = token.AccessToken
}), TestContext.Current.CancellationToken);
Comment on lines +50 to +54
foreach (string redirectUri in RedirectUris.Where(uri => !String.IsNullOrWhiteSpace(uri)))
{
if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !String.IsNullOrEmpty(uri.Fragment))
yield return new ValidationResult($"'{redirectUri}' is not a valid absolute redirect URI without a fragment.", [nameof(RedirectUris)]);
}
Comment on lines +59 to +63
foreach (string scope in Scopes.Where(s => !String.IsNullOrWhiteSpace(s)))
{
if (!AllScopes.Contains(scope, StringComparer.Ordinal))
yield return new ValidationResult($"'{scope}' is not a supported OAuth scope.", [nameof(Scopes)]);
}
Comment on lines +62 to +66
catch (Exception ex)
{
logger.LogWarning(ex, "Unable to fetch OAuth client metadata document for {ClientId}.", clientId);
return null;
}
Comment on lines +65 to +68
catch (Exception)
{
return McpListResult<McpProjectResult>.Failed("Unable to list projects. Check the filter, sort, and limit values.");
}
Comment on lines +119 to +122
catch (Exception)
{
return McpListResult<McpStackResult>.Failed("Unable to search stacks. Check the project id, filter, sort, and limit values.");
}
@ejsmith ejsmith changed the title Add OAuth MCP API access Add hosted MCP server Jun 22, 2026
@github-actions

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Exceptionless.AppHost 39% 40% 134
Exceptionless.Core 71% 63% 8345
Exceptionless.Insulation 24% 23% 203
Exceptionless.Web 71% 61% 4344
Summary 69% (14822 / 21521) 61% (7690 / 12536) 13026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-preview Deploy this pull request to the shared dev environment.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant