Skip to content

Platform bridge: unify APIs, description enrichment, auth scoping, policy webhook#11

Merged
haasonsaas merged 1 commit intomainfrom
improve/mcp-openapi-platform-bridge
Apr 13, 2026
Merged

Platform bridge: unify APIs, description enrichment, auth scoping, policy webhook#11
haasonsaas merged 1 commit intomainfrom
improve/mcp-openapi-platform-bridge

Conversation

@haasonsaas
Copy link
Copy Markdown
Collaborator

Summary

Six improvements that transform mcp-openapi from a generic OpenAPI bridge into the connective tissue between EvalOps platform services and AI agents operating through gate.

  • Unify library and server code pathsindex.ts rewritten as thin adapter over compiler.ts, eliminating 20+ duplicated normalization functions (-200 lines). Public API surface unchanged.
  • Description enrichmentx-mcp-description and x-mcp-hidden OpenAPI extensions, external --descriptions file, and humanizeOperationId() for ConnectRPC patterns (GovernanceService_EvaluateAction → "Evaluate action. Service: Governance")
  • Filter denied tools from discoverytools/list now applies the same isToolAllowed() check as tools/call. Agents no longer see tools they can't use.
  • Service-aware tool naming{service} template placeholder and --tool-name-separator for structured names like governance.evaluate_action
  • Per-tag auth scoping--auth-scope governance=GOV,meter=METER maps tags to credential env var prefixes instead of global MCP_OPENAPI_*
  • Policy webhook--policy-webhook <url> defers allow/deny decisions to an external system (gate). POSTs tool/method/path/tags, expects {allow, reason}, fails closed, 30s cache.

Also fixes hardcoded version string and replaces CommonJS require("ajv") with ESM import.

Test plan

  • All 31 existing tests pass
  • TypeScript compiles cleanly
  • Manual test with a ConnectRPC-generated OpenAPI spec
  • Manual test with --policy-webhook against gate
  • Manual test with --auth-scope across multiple services
  • Verify x-mcp-hidden excludes operations from tools/list

🤖 Generated with Claude Code

…ebhook, and fix version/AJV

Six improvements to the MCP-OpenAPI bridge:

1. Rewrite index.ts as thin adapter over compiler.ts, eliminating all
   duplicated normalization logic (~200 lines removed) while preserving
   the identical public API surface (parseSpec, generateToolsWithTags, etc.)

2. Add description enrichment for ConnectRPC/generated specs:
   - x-mcp-description extension on operations
   - --descriptions CLI flag to load operationId->description mapping
   - humanizeOperationId() auto-generates descriptions from camelCase/PascalCase
   - x-mcp-hidden extension to skip operations entirely

3. Filter denied tools from tools/list handler (previously only filtered
   on tools/call, making denied tools visible but not callable)

4. Add service-aware tool naming: {service} template placeholder,
   --tool-name-separator CLI option for customizing separator character

5. Per-tag auth scoping: --auth-scope tag=PREFIX pairs allow different
   tags to use different credential env var prefixes instead of global
   MCP_OPENAPI_ prefix

6. Policy webhook: --policy-webhook URL for external authorization with
   fail-closed semantics and 30s in-memory cache per tool name

Additional fixes:
- Read version from package.json instead of hardcoded "0.1.0"
- Replace createRequire/require("ajv") with ESM import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 13, 2026

PR Summary

Medium Risk
Medium risk because this changes tool discovery/execution gating and authentication env var resolution (tag-scoped prefixes), which can deny/allow requests or pick different credentials at runtime if misconfigured.

Overview
Tool compilation is expanded and unified. index.ts now builds its normalized spec from compileOperations() (replacing local OpenAPI normalization), while compiler.ts adds x-mcp-hidden to drop operations, x-mcp-description/--descriptions support, a humanizeOperationId() fallback, and new tool naming controls ({service} placeholder + --tool-name-separator).

Runtime security/policy controls are added. http.ts now supports per-tag auth scoping (--auth-scope) by selecting credential env var prefixes (instead of only MCP_OPENAPI_*), and server.ts adds an optional fail-closed --policy-webhook check (cached) before tool execution plus filters denied tools out of tools/list. Also updates server versioning to read from package.json and switches Ajv loading to ESM-compatible import.

Reviewed by Cursor Bugbot for commit 840877c. Bugbot is set up for automated code reviews on this repo. Configure here.

@haasonsaas haasonsaas merged commit 0be3e31 into main Apr 13, 2026
5 checks passed
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the spend limit has been reached. To enable Bugbot Autofix, have a team admin raise the spend limit in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 840877c. Configure here.

const cacheKey = operation.operationId;
const cached = policyWebhookCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return cached.result;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Policy webhook cache ignores per-request input data

Medium Severity

The cacheKey for the policy webhook is only operation.operationId, but the webhook POST body includes input (the per-call arguments). This means a webhook decision for one set of arguments is cached and reused for all subsequent calls to the same tool for 30 seconds, regardless of different input. If the webhook makes input-sensitive authorization decisions, a call with benign arguments that gets allow: true would let through a subsequent call with different, potentially unauthorized arguments without consulting the webhook.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 840877c. Configure here.

process.env.MCP_OPENAPI_OAUTH2_ACCESS_TOKEN ??
process.env.MCP_OPENAPI_BEARER_TOKEN ??
process.env[`${envPrefix}OAUTH2_ACCESS_TOKEN`] ??
process.env[`${envPrefix}BEARER_TOKEN`] ??
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OAuth2 client credentials ignore auth scope prefix

Medium Severity

The new auth scoping feature correctly applies envPrefix for bearer tokens, API keys, and basic auth lookups in applyAuth. However, the OAuth2 client credentials fallback via getOAuth2AccessToken still uses hardcoded MCP_OPENAPI_ prefix for CLIENT_ID and CLIENT_SECRET env vars. When --auth-scope is configured, scoped OAuth2 client credentials (e.g. GOV_OAUTH2_CLIENT_ID) will silently fail to resolve.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 840877c. Configure here.

? { description: (p.schema as Record<string, unknown>).description as string }
: {}),
schema: p.schema ?? { type: "string" }
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Library adapter loses parameter-level descriptions from OpenAPI

Low Severity

The new operationToEndpoint adapter populates NormalizedParameter.description from p.schema.description instead of the parameter's own description field. Since ParameterSpec in the compiler doesn't carry the parameter-level description, this information is lost. The old code read entry.description directly from the OpenAPI parameter object, so the parseSpec() library API now silently drops parameter descriptions that differ from or don't exist on the schema.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 840877c. Configure here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant