docs: add pending_verification state and initiate-verification / verify-headers MCP client endpoints to OpenAPI spec - #4858
Conversation
|
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe OpenAPI documentation adds pending MCP client verification states, deferred OAuth creation responses, OAuth completion guidance, and endpoints for OAuth and per-user-header verification. ChangesMCP verification API
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
8f4139b to
e152314
Compare
cec0f92 to
00483f9
Compare
00483f9 to
5c8eae4
Compare
e152314 to
23cf80b
Compare
5c8eae4 to
c75d5ea
Compare
23cf80b to
65b9670
Compare
249bcee to
7d7f5a7
Compare
a66d991 to
465aeaa
Compare
7d7f5a7 to
f467c2e
Compare
465aeaa to
d4aaf25
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
docs/openapi/openapi.json (2)
43944-43961: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winDeclare the required properties of the 200 response.
The 200 schema lists
status,message, andtools_countbut declares norequiredarray. Code generators then emit all three fields as optional. Callers must add null checks that the server never triggers. Addrequiredfor the fields the server always returns.♻️ Proposed change
"schema": { "type": "object", + "required": [ + "status", + "message", + "tools_count" + ], "properties": {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/openapi/openapi.json` around lines 43944 - 43961, Add a required array to the 200 response schema containing status, message, and tools_count, preserving their existing property definitions so generated clients treat all server-guaranteed fields as required.
43985-43993: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe new reusable
Conflictresponse is not referenced by the only new 409. This PR adds aConflictresponse component and a 409 response onverify-headers, but the 409 inlines its own schema. The component stays unused, and the two definitions can drift.
docs/openapi/openapi.json#L43985-L43993: replace the inline 409 content with"$ref": "#/components/responses/Conflict", or keep the inline form and state why the specific description is required.docs/openapi/openapi.json#L67636-L67645: if no operation referencesConflict, remove the component; otherwise confirm the referencing operations in the source YAML.Note that a
$refto a response component replaces the whole response object, includingdescription. If the specific text "Client has already been verified..." must stay, keep the inline form and drop the component.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/openapi/openapi.json` around lines 43985 - 43993, The new verify-headers 409 response must either reuse the reusable Conflict response or remove that unused component. At docs/openapi/openapi.json lines 43985-43993, replace the inline response with a reference to components/responses/Conflict only if its description is acceptable; otherwise retain the specific description and remove the Conflict component at lines 67636-67645. Confirm the corresponding source YAML reflects the chosen definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/openapi/openapi.json`:
- Line 42920: Replace the unresolved “pending_oauth” wording in all three
descriptions at docs/openapi/openapi.json:42920-42920,
docs/openapi/openapi.json:43080-43080, and docs/openapi/openapi.json:43240-43240
with a reference to the OAuthFlowInitiation response schema. Update the
corresponding source YAML under docs/openapi/ so regeneration preserves the
corrected wording.
- Around line 43352-43363: Update the MCP client creation response schema in the
source OpenAPI definition to use anyOf, or otherwise enforce mutually exclusive
SuccessResponse and OAuthFlowInitiation constraints, so valid OAuth responses
pass strict validation. Apply the corresponding change in the bundled
openapi.json by regenerating it from docs/openapi/paths/management/mcp.yaml.
- Around line 43912-43932: Update verifyMCPClientHeaders and both verification
error paths to prevent user_headers values from being retained or exposed:
either stop storing canonUserHeaders as an admin credential or revise the
OpenAPI contract to document retention, and redact header values from 422
responses, request logs, and audit logs. Keep the user_headers schema accurate
after the persistence decision.
In `@docs/openapi/schemas/management/oauth.yaml`:
- Around line 69-85: Update OAuthFlowInitiation in oauth.yaml to require status,
oauth_config_id, authorize_url, expires_at, mcp_client_id, complete_url,
status_url, and next_steps, and constrain SuccessResponse to require status with
the value success so the create-client oneOf branches are mutually exclusive.
Apply the identical schema constraints in openapi.json.
---
Nitpick comments:
In `@docs/openapi/openapi.json`:
- Around line 43944-43961: Add a required array to the 200 response schema
containing status, message, and tools_count, preserving their existing property
definitions so generated clients treat all server-guaranteed fields as required.
- Around line 43985-43993: The new verify-headers 409 response must either reuse
the reusable Conflict response or remove that unused component. At
docs/openapi/openapi.json lines 43985-43993, replace the inline response with a
reference to components/responses/Conflict only if its description is
acceptable; otherwise retain the specific description and remove the Conflict
component at lines 67636-67645. Confirm the corresponding source YAML reflects
the chosen definition.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0245744c-2e93-49ac-bc6b-43106bebd89c
📒 Files selected for processing (5)
docs/openapi/openapi.jsondocs/openapi/openapi.yamldocs/openapi/paths/management/mcp.yamldocs/openapi/schemas/management/mcp.yamldocs/openapi/schemas/management/oauth.yaml
| "oauth_config": { | ||
| "$ref": "#/components/schemas/OAuthConfigRequest", | ||
| "description": "OAuth configuration for initiating OAuth flow.\nOnly include this when creating a client with auth_type \"oauth\".\nThis will trigger the OAuth flow and return an authorization URL.\n" | ||
| "description": "OAuth configuration for initiating OAuth flow.\nRequired when creating a client with auth_type \"oauth\" or \"per_user_oauth\".\nThis will trigger the OAuth flow and return an authorization URL\n(see the pending_oauth response variant).\n" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Three copies of the oauth_config description point to a pending_oauth variant that does not exist. The create-client 200 response uses an unnamed oneOf of SuccessResponse and OAuthFlowInitiation. No schema, component, or discriminator mapping key named pending_oauth exists in this spec, so the cross-reference does not resolve for readers or documentation tooling.
docs/openapi/openapi.json#L42920-L42920: replace "see the pending_oauth response variant" with a reference to theOAuthFlowInitiationresponse schema.docs/openapi/openapi.json#L43080-L43080: apply the same wording change to this duplicated description.docs/openapi/openapi.json#L43240-L43240: apply the same wording change to this duplicated description.
Update the source YAML under docs/openapi/ as well, so a regeneration does not restore the old text.
📍 Affects 1 file
docs/openapi/openapi.json#L42920-L42920(this comment)docs/openapi/openapi.json#L43080-L43080docs/openapi/openapi.json#L43240-L43240
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/openapi/openapi.json` at line 42920, Replace the unresolved
“pending_oauth” wording in all three descriptions at
docs/openapi/openapi.json:42920-42920, docs/openapi/openapi.json:43080-43080,
and docs/openapi/openapi.json:43240-43240 with a reference to the
OAuthFlowInitiation response schema. Update the corresponding source YAML under
docs/openapi/ so regeneration preserves the corrected wording.
| "description": "MCP client added successfully. For auth_type \"oauth\" and\n\"per_user_oauth\" the client is not created yet — the response is the\npending_oauth variant carrying authorize_url plus status_url /\ncomplete_url / next_steps hints; the client is created when the flow\nis completed via complete_url.\n", | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "$ref": "#/components/schemas/SuccessResponse" | ||
| "oneOf": [ | ||
| { | ||
| "$ref": "#/components/schemas/SuccessResponse" | ||
| }, | ||
| { | ||
| "$ref": "#/components/schemas/OAuthFlowInitiation" | ||
| } | ||
| ] |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect SuccessResponse and OAuthFlowInitiation strictness in the bundled spec.
fd -t f 'openapi.json' docs | while read -r f; do
jq '{
SuccessResponse: .components.schemas.SuccessResponse,
OAuthFlowInitiation: .components.schemas.OAuthFlowInitiation
}' "$f"
done
# Check bundle/source parity for the create-client 200 response and the new paths.
fd -t f -e yaml -e yml . docs/openapi | xargs rg -n -C6 'OAuthFlowInitiation|initiate-verification|verify-headers|pending_verification'Repository: maximhq/bifrost
Length of output: 13588
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- bundled response ---'
sed -n '43340,43370p' docs/openapi/openapi.json
printf '%s\n' '--- source response ---'
sed -n '175,200p' docs/openapi/paths/management/mcp.yaml
printf '%s\n' '--- source schemas ---'
sed -n '1,85p' docs/openapi/schemas/management/common.yaml
sed -n '47,85p' docs/openapi/schemas/management/oauth.yamlRepository: maximhq/bifrost
Length of output: 4599
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
import json
with open("docs/openapi/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
success = spec["components"]["schemas"]["SuccessResponse"]
oauth = spec["components"]["schemas"]["OAuthFlowInitiation"]
payload = {
"status": "pending_oauth",
"message": "Authorize the MCP server",
"oauth_config_id": "cfg-1",
"authorize_url": "https://example.test/authorize",
"expires_at": "2026-08-01T00:00:00Z",
"mcp_client_id": "client-1",
"complete_url": "/complete",
"status_url": "/status",
"next_steps": ["authorize"],
}
def matches(schema, obj):
if schema.get("type") != "object" or not isinstance(obj, dict):
return False
properties = schema.get("properties", {})
for key, value in obj.items():
if key not in properties:
if schema.get("additionalProperties", True) is False:
return False
continue
prop = properties[key]
if prop.get("type") == "string" and not isinstance(value, str):
return False
if "enum" in prop and value not in prop["enum"]:
return False
return True
print("matches SuccessResponse:", matches(success, payload))
print("matches OAuthFlowInitiation:", matches(oauth, payload))
print("oneOf match count:", sum(matches(schema, payload) for schema in (success, oauth)))
print("source refs in create response:")
with open("docs/openapi/paths/management/mcp.yaml", encoding="utf-8") as f:
text = f.read()
start = text.index("client-create:")
end = text.index("\nclient-by-id:", start)
section = text[start:end]
print("oneOf present:", "oneOf:" in section)
print("SuccessResponse ref present:", "SuccessResponse" in section)
print("OAuthFlowInitiation ref present:", "OAuthFlowInitiation" in section)
PYRepository: maximhq/bifrost
Length of output: 376
Make the OAuth response variants mutually exclusive. OAuthFlowInitiation matches both branches of oneOf, so strict validators reject valid OAuth responses. Use anyOf or add mutually exclusive constraints, then update docs/openapi/paths/management/mcp.yaml and regenerate the bundle.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/openapi/openapi.json` around lines 43352 - 43363, Update the MCP client
creation response schema in the source OpenAPI definition to use anyOf, or
otherwise enforce mutually exclusive SuccessResponse and OAuthFlowInitiation
constraints, so valid OAuth responses pass strict validation. Apply the
corresponding change in the bundled openapi.json by regenerating it from
docs/openapi/paths/management/mcp.yaml.
| "requestBody": { | ||
| "required": true, | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "type": "object", | ||
| "required": [ | ||
| "user_headers" | ||
| ], | ||
| "properties": { | ||
| "user_headers": { | ||
| "type": "object", | ||
| "additionalProperties": { | ||
| "type": "string" | ||
| }, | ||
| "description": "Sample value for every header name declared in the client's\nper_user_header_keys. Used once for the verification\nconnection, then discarded — never persisted.\n" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for logging of user headers in the verification path.
rg -n -C6 --iglob '!docs/**' 'user_headers|UserHeaders|PerUserHeader' | rg -n -C6 -i 'log|zap|logger|printf|audit' || trueRepository: maximhq/bifrost
Length of output: 153
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- status ---'
git status --short
printf '%s\n' '--- diff stat ---'
git diff --stat
printf '%s\n' '--- recent commits ---'
git log --oneline -8
printf '%s\n' '--- tracked candidate files ---'
git ls-files | rg -i '(^|/)(docs/openapi/openapi\.json|.*(mcp|oauth|header|verif|audit|log|error).*)$' | head -200
printf '%s\n' '--- exact symbols ---'
rg -n -S -i -C4 'user_headers|userHeaders|per_user_header_keys|per-user-header|per user header|pending_verification' --glob '!docs/openapi/openapi.json' --glob '!vendor/**' --glob '!node_modules/**' . || trueRepository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -u
LOG=/tmp/coderabbit-shell-logs/shell-output-nHxI5C
printf '%s\n' '--- previous command metadata ---'
sed -n '1,120p' "$LOG"
printf '%s\n' '--- source files containing exact user_headers symbols ---'
rg -l -S 'user_headers|UserHeaders|PerUserHeader' --glob '!docs/openapi/openapi.json' --glob '!ui/**' --glob '!vendor/**' --glob '!node_modules/**' . | sort
printf '%s\n' '--- candidate handler files ---'
git ls-files | rg '(^|/)(.*mcp.*|.*oauth.*)\.(go|yaml|json)$' | rg 'handler|handlers|route|server|mcp' | head -150
printf '%s\n' '--- relevant logging/error calls in core handlers ---'
rg -n -S -i -C5 'user_headers|PerUserHeader|per-user-headers|verify-headers|upstream|status.?422|Unprocessable|logger\.|log\.|audit|Error\(' core framework --glob '*.go' | head -500Repository: maximhq/bifrost
Length of output: 47311
🏁 Script executed:
#!/bin/bash
set -u
for f in transports/bifrost-http/handlers/mcp.go transports/bifrost-http/handlers/mcpheaders.go transports/bifrost-http/handlers/mcpsessions.go transports/bifrost-http/server/server.go; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
wc -l "$f"
ast-grep outline "$f" 2>/dev/null | head -200 || true
fi
done
printf '\n--- mcp.go user_headers contexts ---\n'
rg -n -C15 -S 'user_headers|UserHeaders|verify-headers|VerifyHeaders|createMCPClient|updateMCPClient' transports/bifrost-http/handlers/mcp.go
printf '\n--- mcpheaders.go structure and contexts ---\n'
rg -n -C12 -S -i 'func |user_headers|headers|verify|upstream|status|error|logger|audit|json\.NewEncoder|c\.JSON|ctx\.JSON|response' transports/bifrost-http/handlers/mcpheaders.go | head -700
printf '\n--- request/audit/error helpers ---\n'
rg -n -S -i -C5 'request body|requestbody|body.*log|log.*body|audit|auditlog|c\.JSON|ctx\.JSON|Error\(|Errorf\(|http\.Error|WriteHeader|Unprocessable|422' transports/bifrost-http/handlers --glob '*.go' | head -700Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- verify/create handler slices ---'
sed -n '327,545p' transports/bifrost-http/handlers/mcp.go
sed -n '1160,1320p' transports/bifrost-http/handlers/mcp.go
printf '%s\n' '--- verification implementation references ---'
rg -n -S -C10 'VerifyHeadersConnection|verifyHeaders|Verify.*Header|Connect.*Header|userHeaders|UserHeaders' core transports/bifrost-http/server framework --glob '*.go' | head -1000
printf '%s\n' '--- error helpers ---'
rg -n -S -C8 'func (SendError|SendJSON)|SendError\(' transports/bifrost-http/handlers transports/bifrost-http/lib --glob '*.go' | head -800
printf '%s\n' '--- middleware and request logging ---'
rg -n -S -i -C8 'BifrostHTTPMiddleware|request.*log|log.*request|PostBody\(\)|RequestURI|audit|mcp.*log|MCPLog' transports/bifrost-http core framework --glob '*.go' | head -1200
printf '%s\n' '--- redaction tests and header-value assertions ---'
rg -n -S -i -C8 'redact|redaction|secret|token|api.?key|user_headers|Verification failed|UnprocessableEntity|422' transports/bifrost-http core/mcp framework/mcp_headers --glob '*_test.go' | head -1200Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- server verification wrapper ---'
sed -n '370,395p' transports/bifrost-http/server/server.go
printf '%s\n' '--- exact VerifyHeadersConnection definitions ---'
rg -n -S 'func .*VerifyHeadersConnection|VerifyHeadersConnection\(' core transports/bifrost-http framework --glob '*.go'
printf '%s\n' '--- exact SendError definition ---'
rg -n -S 'func SendError|func SendJSON' transports/bifrost-http/handlers --glob '*.go'
sed -n '1,180p' transports/bifrost-http/handlers/utils.go 2>/dev/null || true
printf '%s\n' '--- API route registration and middleware arguments ---'
sed -n '1920,2072p' transports/bifrost-http/server/server.go
sed -n '2120,2175p' transports/bifrost-http/server/server.go
printf '%s\n' '--- targeted handler tests ---'
rg -l -S -i 'verify-headers|VerifyMCPClientHeaders|user_headers|Verification failed' transports/bifrost-http --glob '*_test.go' | sortRepository: maximhq/bifrost
Length of output: 21217
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
targets = [
Path("transports/bifrost-http/handlers/mcp.go"),
Path("transports/bifrost-http/server/server.go"),
]
needles = ("req.UserHeaders", "canonUserHeaders", "VerifyHeadersConnection",
"UpsertCredential", "SendError", "logger.")
for path in targets:
lines = path.read_text().splitlines()
print(f"--- {path} ---")
for i, line in enumerate(lines, 1):
if any(n in line for n in needles):
print(f"{i}: {line.strip()}")
PYRepository: maximhq/bifrost
Length of output: 32243
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- MCP manager verification ---'
sed -n '640,735p' core/mcp/clientmanager.go
sed -n '790,835p' core/mcp/clientmanager.go
printf '%s\n' '--- connection/error construction in MCP package ---'
rg -n -S -C8 -i 'userHeaders|user_headers|http\.Header|Dial|Connect|tools/list|failed.*connect|connection.*failed|status.?code|response.*body|body.*response' core/mcp --glob '*.go' | head -1200
printf '%s\n' '--- HTTP logging entry points ---'
rg -n -S 'LogHTTPRequest|HTTPMiddleware|RequestCtx|PostBody\(\)' transports/bifrost-http core framework plugins --glob '*.go' | head -1000
printf '%s\n' '--- audit/log persistence entry points ---'
rg -n -S -i -C5 'audit|mcp.?logs|Create.*Log|Insert.*Log|LogHTTPRequest|request.*body|response.*body|raw.*request|raw.*response' transports/bifrost-http/handlers plugins framework --glob '*.go' | head -1400
printf '%s\n' '--- relevant tests by exact names ---'
rg -n -S -i -C5 'VerifyHeadersConnection|Verification failed|UpsertCredential|MCP.*Headers' core/mcp transports/bifrost-http/handlers framework/mcp_headers --glob '*_test.go' | head -1000Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
files = list(Path("core/mcp").rglob("*.go")) + list(Path("transports/bifrost-http/handlers").rglob("*.go"))
for p in files:
text = p.read_text(errors="replace")
if "userHeaders" in text or "UserHeaders" in text or "user_headers" in text:
print(f"\n--- {p} ---")
for i, line in enumerate(text.splitlines(), 1):
if any(x in line for x in ("userHeaders", "UserHeaders", "user_headers")):
print(f"{i}: {line.strip()}")
PYRepository: maximhq/bifrost
Length of output: 9767
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- exact manager implementation ---'
sed -n '650,745p' core/mcp/clientmanager.go
sed -n '1,150p' core/mcp/credstore/per_user_headers.go
sed -n '1,180p' core/mcp/interface.go
printf '%s\n' '--- MCP connection construction and errors ---'
rg -n -S 'func .*Connect|func .*connect|New.*Client|New.*Transport|CallTool|ListTools|ListTools\(' core/mcp core --glob '*.go' | rg -v '_test\.go' | head -300
rg -n -S -i -C10 'fmt\.Errorf|errors\.New|return nil,.*err|return .*err' core/mcp/clientmanager.go | sed -n '1,500p'
printf '%s\n' '--- logger calls in relevant implementation ---'
rg -n -S 'logger\.|LogHTTPRequest|fmt\.Sprintf' core/mcp/clientmanager.go core/mcp/credstore/per_user_headers.go transports/bifrost-http/handlers/mcp.go
printf '%s\n' '--- middleware definitions and request logging calls ---'
rg -n -S 'func .*Middleware|LogHTTPRequest|PostBody\(\)|Request\.Header|ctx\.Request\.Header|Header\.VisitAll' transports/bifrost-http --glob '*.go' | rg -v '_test\.go' | head -700Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
for p in [Path("core/mcp/clientmanager.go"), Path("transports/bifrost-http/handlers/mcp.go")]:
lines = p.read_text().splitlines()
print(f"--- {p} ---")
for start, end in ((650, 745), (400, 415), (1285, 1297), (510, 533)):
if start <= len(lines):
print(f"[{start}:{min(end,len(lines))}]")
for n in range(start, min(end, len(lines)) + 1):
print(f"{n}: {lines[n-1]}")Repository: maximhq/bifrost
Length of output: 14942
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
for p in [Path("core/mcp/clientmanager.go"), Path("transports/bifrost-http/handlers/mcp.go")]:
lines = p.read_text().splitlines()
print(f"--- {p} ---")
for start, end in ((650, 745), (400, 415), (1285, 1297), (510, 533)):
if start <= len(lines):
print(f"[{start}:{min(end,len(lines))}]")
for n in range(start, min(end, len(lines)) + 1):
print(f"{n}: {lines[n-1]}")
PYRepository: maximhq/bifrost
Length of output: 14855
Redact verification credentials and align the persistence contract.
verifyMCPClientHeaders stores canonUserHeaders as an admin credential, so user_headers is not discarded. Both verification paths also return raw verifyErr in the 422 body. Remove this retention or update the API description, and sanitize errors and request/audit logs so header values cannot leak.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/openapi/openapi.json` around lines 43912 - 43932, Update
verifyMCPClientHeaders and both verification error paths to prevent user_headers
values from being retained or exposed: either stop storing canonUserHeaders as
an admin credential or revise the OpenAPI contract to document retention, and
redact header values from 422 responses, request logs, and audit logs. Keep the
user_headers schema accurate after the persistence decision.
| complete_url: | ||
| type: string | ||
| description: | | ||
| Relative URL to POST once the flow is authorized | ||
| (/api/mcp/client/{oauth_config_id}/complete-oauth). Note the path | ||
| parameter is the oauth_config_id, not the MCP client ID. | ||
| status_url: | ||
| type: string | ||
| description: | | ||
| Relative URL to poll for the flow status | ||
| (/api/oauth/config/{oauth_config_id}/status). Wait for | ||
| status "authorized" before calling complete_url. | ||
| next_steps: | ||
| type: array | ||
| items: | ||
| type: string | ||
| description: Human-readable steps to complete the flow (authorize, poll, complete) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 10 \
'SuccessResponse|OAuthFlowInitiation|pending_oauth|complete_url|status_url|next_steps|complete-oauth|/api/oauth/config' .Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- relevant files ---'
git ls-files | rg '(^|/)(oauth\.yaml|openapi\.json|.*mcp.*\.(go|yaml|json|ts))$|mcp.*oauth|oauth.*mcp' | head -200
printf '%s\n' '--- stack/change context ---'
git status --short
git branch --show-current
git diff --stat
git diff --name-only
printf '%s\n' '--- schema definitions ---'
rg -n -C 18 \
'OAuthFlowInitiation|SuccessResponse|pending_oauth|oneOf:|complete_url:|status_url:|next_steps:' \
docs/openapi/schemas docs/openapi/openapi.json transports docs ui/lib/types/mcp.ts \
--glob '*.yaml' --glob '*.json' --glob '*.go' --glob '*.ts' \
| head -500Repository: maximhq/bifrost
Length of output: 43112
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- source schema ---'
cat -n docs/openapi/schemas/management/oauth.yaml | sed -n '1,130p'
printf '%s\n' '--- create-client path schema ---'
rg -n -C 35 \
'oneOf:|OAuthFlowInitiation|SuccessResponse|pending_oauth' \
docs/openapi/paths/management/mcp.yaml
printf '%s\n' '--- bundled component schemas ---'
python3 - <<'PY'
import json
with open("docs/openapi/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
for name in ("SuccessResponse", "OAuthFlowInitiation"):
print(f"\n--- {name} ---")
print(json.dumps(spec.get("components", {}).get("schemas", {}).get(name), indent=2))
print("\n--- create-client 200 response ---")
paths = spec.get("paths", {})
for path, methods in paths.items():
for method, operation in methods.items():
if not isinstance(operation, dict):
continue
text = json.dumps(operation)
if "OAuthFlowInitiation" in text and "SuccessResponse" in text:
print(path, method)
print(json.dumps(operation.get("responses", {}).get("200"), indent=2))
PY
printf '%s\n' '--- backend OAuth response construction and endpoint handlers ---'
rg -n -C 12 \
'pending_oauth|complete_url|status_url|next_steps|OAuthFlowInitiation|authorize_url|oauth_config_id' \
--glob '*.go' \
transports core framework | head -700Repository: maximhq/bifrost
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- MCP handler response construction ---'
rg -n -C 20 \
'InitiateOAuthFlow|OAuth2FlowInitiation|pending_oauth|complete_url|status_url|next_steps|StorePendingMCPClient|addMCPClient|AddMCPClient' \
transports/bifrost-http/handlers/mcp.go \
core/mcp framework/oauth2 \
--glob '*.go' | head -900
printf '%s\n' '--- response schema types ---'
rg -n -C 15 \
'type OAuth2FlowInitiation|type SuccessResponse|json:"(status|message|oauth_config_id|authorize_url|expires_at|mcp_client_id|complete_url|status_url|next_steps)' \
core framework transports \
--glob '*.go' | head -500
printf '%s\n' '--- all direct OAuth-flow response literals ---'
rg -n -C 12 \
'OAuth2FlowInitiation\{|map\[string\].*(pending_oauth|complete_url|status_url|next_steps)|"pending_oauth"' \
--glob '*.go' . | head -700Repository: maximhq/bifrost
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
with open("docs/openapi/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
schemas = spec["components"]["schemas"]
success = schemas["SuccessResponse"]
oauth = schemas["OAuthFlowInitiation"]
print("--- schema constraints ---")
for name, schema in (("SuccessResponse", success), ("OAuthFlowInitiation", oauth)):
print(name)
print(" required:", schema.get("required", []))
print(" status:", schema.get("properties", {}).get("status"))
print(" additionalProperties:", schema.get("additionalProperties", True))
def accepts(schema, value):
if schema.get("type") == "object" and not isinstance(value, dict):
return False
required = schema.get("required", [])
if any(key not in value for key in required):
return False
properties = schema.get("properties", {})
for key, rule in properties.items():
if key not in value:
continue
if "enum" in rule and value[key] not in rule["enum"]:
return False
if rule.get("type") == "string" and not isinstance(value[key], str):
return False
if rule.get("type") == "array" and not isinstance(value[key], list):
return False
if schema.get("additionalProperties", True) is False:
if any(key not in properties for key in value):
return False
return True
samples = {
"empty object": {},
"success response": {"status": "success", "message": "ok"},
"runtime OAuth response": {
"status": "pending_oauth",
"oauth_config_id": "oauth-1",
"authorize_url": "https://provider.example/authorize",
"expires_at": "2026-08-01T00:00:00Z",
"mcp_client_id": "mcp-1",
"complete_url": "/api/mcp/client/oauth-1/complete-oauth",
"status_url": "/api/oauth/config/oauth-1/status",
"next_steps": ["authorize", "poll", "complete"],
},
}
print("\n--- oneOf overlap probe ---")
for label, value in samples.items():
matches = [name for name, schema in (
("SuccessResponse", success),
("OAuthFlowInitiation", oauth),
) if accepts(schema, value)]
print(f"{label}: {matches}")
PY
printf '%s\n' '--- SuccessResponse source schema ---'
cat -n docs/openapi/schemas/management/common.yaml | sed -n '1,80p'Repository: maximhq/bifrost
Length of output: 1597
Make the create-client oneOf branches mutually exclusive.
Require status, oauth_config_id, authorize_url, expires_at, mcp_client_id, complete_url, status_url, and next_steps in OAuthFlowInitiation. Constrain the SuccessResponse branch to require status: success; it currently also matches every OAuth response because its fields are optional and extra properties are allowed. Update docs/openapi/openapi.json with the same constraints.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/openapi/schemas/management/oauth.yaml` around lines 69 - 85, Update
OAuthFlowInitiation in oauth.yaml to require status, oauth_config_id,
authorize_url, expires_at, mcp_client_id, complete_url, status_url, and
next_steps, and constrain SuccessResponse to require status with the value
success so the create-client oneOf branches are mutually exclusive. Apply the
identical schema constraints in openapi.json.
Source: Path instructions
Merge activity
|
f467c2e to
9cbca49
Compare

Summary
This PR extends the MCP client API to support a
pending_verificationlifecycle state for clients declared viaconfig.jsonthat require a one-time admin verification step before becoming active. It introduces two new endpoints to complete that verification and clarifies the OAuth flow response shape for client creation.Changes
pending_verificationas a validMCPConnectionStateenum value, representing clients that have been declared (typically viaconfig.json) but whose admin verification has not yet been completed.POST /api/mcp/client/{id}/initiate-verification— starts the one-time admin OAuth flow for clients withauth_typeoauthorper_user_oauthinpending_verificationstate. Performs OAuth metadata discovery (RFC 8414) and dynamic client registration (RFC 7591) when the declaredoauth_configomits those fields, then returns anOAuthFlowInitiationresponse. Safe to call repeatedly if a previous attempt expired.POST /api/mcp/client/{id}/verify-headers— completes admin verification for clients withauth_typeper_user_headersinpending_verificationstate. The admin supplies sample header values; Bifrost opens an upstream connection, discovers tools, persists them, and transitions the client toconnected. Sample values are discarded after use and never persisted.POST /api/mcp/client(create) response schema tooneOfSuccessResponse|OAuthFlowInitiation, reflecting that OAuth-based client creation returns a pending OAuth flow rather than immediate success.oauth_configfield description to clarify it is required (not optional) forauth_typeoauthorper_user_oauth.400for per-user auth clients and clients inpending_verificationstate.complete_url,status_url, andnext_stepsfields to theOAuthFlowInitiationschema to guide callers through completing the OAuth flow.Conflict(409) reusable response component.Type of change
Affected areas
How to test
Verify the updated OpenAPI spec is valid and that the new endpoints, schemas, and state descriptions render correctly in your API documentation tooling.
# Validate the OpenAPI spec npx @redocly/cli lint docs/openapi/openapi.yamlpending_verificationappears in theMCPConnectionStateenum.POST /api/mcp/client/{id}/initiate-verificationandPOST /api/mcp/client/{id}/verify-headersappear with correct request/response schemas.200response showsoneOf: [SuccessResponse, OAuthFlowInitiation].OAuthFlowInitiationincludescomplete_url,status_url, andnext_steps.Breaking changes
The
POST /api/mcp/client200response schema has changed from a singleSuccessResponsetooneOf [SuccessResponse, OAuthFlowInitiation]. Clients creating OAuth-based MCP clients must handle theOAuthFlowInitiationresponse variant and complete the OAuth flow viacomplete_urlbefore the client is fully created.Related issues
Security considerations
The
initiate-verificationandverify-headersendpoints are protected byManagementBearerAuth. Sample header values supplied toverify-headersare explicitly never persisted, limiting exposure of admin-supplied credentials.Checklist
docs/contributing/README.mdand followed the guidelines