Platform bridge: unify APIs, description enrichment, auth scoping, policy webhook#11
Conversation
…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>
PR SummaryMedium Risk Overview Runtime security/policy controls are added. Reviewed by Cursor Bugbot for commit 840877c. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
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; |
There was a problem hiding this comment.
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)
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`] ?? |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 840877c. Configure here.
| ? { description: (p.schema as Record<string, unknown>).description as string } | ||
| : {}), | ||
| schema: p.schema ?? { type: "string" } | ||
| })); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 840877c. Configure here.


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.
index.tsrewritten as thin adapter overcompiler.ts, eliminating 20+ duplicated normalization functions (-200 lines). Public API surface unchanged.x-mcp-descriptionandx-mcp-hiddenOpenAPI extensions, external--descriptionsfile, andhumanizeOperationId()for ConnectRPC patterns (GovernanceService_EvaluateAction→ "Evaluate action. Service: Governance")tools/listnow applies the sameisToolAllowed()check astools/call. Agents no longer see tools they can't use.{service}template placeholder and--tool-name-separatorfor structured names likegovernance.evaluate_action--auth-scope governance=GOV,meter=METERmaps tags to credential env var prefixes instead of globalMCP_OPENAPI_*--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
--policy-webhookagainst gate--auth-scopeacross multiple servicesx-mcp-hiddenexcludes operations from tools/list🤖 Generated with Claude Code