Skip to content

docs: add pending_verification state and initiate-verification / verify-headers MCP client endpoints to OpenAPI spec - #4858

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
07-02-docs_mcp_per_user_auth_completions_openapi_additions
Aug 8, 2026
Merged

Pratham-Mishra04 merged 1 commit into
devfrom
07-02-docs_mcp_per_user_auth_completions_openapi_additions

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

This PR extends the MCP client API to support a pending_verification lifecycle state for clients declared via config.json that 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

  • Added pending_verification as a valid MCPConnectionState enum value, representing clients that have been declared (typically via config.json) but whose admin verification has not yet been completed.
  • Added POST /api/mcp/client/{id}/initiate-verification — starts the one-time admin OAuth flow for clients with auth_type oauth or per_user_oauth in pending_verification state. Performs OAuth metadata discovery (RFC 8414) and dynamic client registration (RFC 7591) when the declared oauth_config omits those fields, then returns an OAuthFlowInitiation response. Safe to call repeatedly if a previous attempt expired.
  • Added POST /api/mcp/client/{id}/verify-headers — completes admin verification for clients with auth_type per_user_headers in pending_verification state. The admin supplies sample header values; Bifrost opens an upstream connection, discovers tools, persists them, and transitions the client to connected. Sample values are discarded after use and never persisted.
  • Updated the POST /api/mcp/client (create) response schema to oneOf SuccessResponse | OAuthFlowInitiation, reflecting that OAuth-based client creation returns a pending OAuth flow rather than immediate success.
  • Updated oauth_config field description to clarify it is required (not optional) for auth_type oauth or per_user_oauth.
  • Updated the reconnect endpoint description to note it returns 400 for per-user auth clients and clients in pending_verification state.
  • Added complete_url, status_url, and next_steps fields to the OAuthFlowInitiation schema to guide callers through completing the OAuth flow.
  • Added a Conflict (409) reusable response component.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

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.yaml
  • Confirm pending_verification appears in the MCPConnectionState enum.
  • Confirm POST /api/mcp/client/{id}/initiate-verification and POST /api/mcp/client/{id}/verify-headers appear with correct request/response schemas.
  • Confirm the create client 200 response shows oneOf: [SuccessResponse, OAuthFlowInitiation].
  • Confirm OAuthFlowInitiation includes complete_url, status_url, and next_steps.

Breaking changes

  • Yes
  • No

The POST /api/mcp/client 200 response schema has changed from a single SuccessResponse to oneOf [SuccessResponse, OAuthFlowInitiation]. Clients creating OAuth-based MCP clients must handle the OAuthFlowInitiation response variant and complete the OAuth flow via complete_url before the client is fully created.

Related issues

Security considerations

The initiate-verification and verify-headers endpoints are protected by ManagementBearerAuth. Sample header values supplied to verify-headers are explicitly never persisted, limiting exposure of admin-supplied credentials.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added OAuth verification flows for MCP clients, including authorization, status polling, and completion guidance.
    • Added header-based verification for pending per-user clients, with discovered tool counts.
    • Added a pending_verification connection state.
    • Expanded client creation responses to support pending OAuth flows.
  • Documentation

    • Clarified OAuth configuration requirements and reconnect limitations.
    • Added standardized conflict response documentation.

Walkthrough

The OpenAPI documentation adds pending MCP client verification states, deferred OAuth creation responses, OAuth completion guidance, and endpoints for OAuth and per-user-header verification.

Changes

MCP verification API

Layer / File(s) Summary
Verification states and response contracts
docs/openapi/schemas/management/mcp.yaml, docs/openapi/schemas/management/oauth.yaml, docs/openapi/paths/management/mcp.yaml, docs/openapi/openapi.json
The MCP state enum includes pending_verification. OAuth and per-user OAuth creation can return OAuthFlowInitiation. The response now documents completion URLs, status polling, and next steps.
MCP verification endpoints
docs/openapi/paths/management/mcp.yaml, docs/openapi/openapi.yaml, docs/openapi/openapi.json
The API documents endpoints to initiate OAuth verification and verify per-user headers. It adds validation, conflict, upstream, and server-error responses. Reconnect documentation excludes unsupported pending clients.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • maximhq/bifrost#3871: Documents the same pending_verification state and OAuth verification initiation endpoint.
  • maximhq/bifrost#3874: Documents the verify-headers endpoint and pending verification state.
  • maximhq/bifrost#5709: Relates to OAuth flow initiation and pending-verification lifecycle changes.

Suggested reviewers: akshaydeo, danpiths, impoiler

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main OpenAPI changes: the new pending_verification state and two MCP client verification endpoints.
Description check ✅ Passed The description covers the required sections and clearly explains the API changes, testing approach, breaking change, and security considerations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-02-docs_mcp_per_user_auth_completions_openapi_additions

Comment @coderabbitai help to get the list of available commands.

Pratham-Mishra04 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Documentation-only change; no runtime code is modified. The new endpoints and state are clearly specified, and the bundled JSON matches the YAML source.

The OAuthFlowInitiation schema has no required fields, meaning authorize_url, complete_url, and status_url will be generated as optional/nullable by SDK generators. Both new endpoint 400 descriptions also omit 'client not in pending_verification state' as an explicit condition, leaving callers unable to distinguish a state-machine rejection from a misconfiguration.

docs/openapi/schemas/management/oauth.yaml (missing required fields on OAuthFlowInitiation) and docs/openapi/paths/management/mcp.yaml (incomplete 400 error condition descriptions for both new endpoints)

Important Files Changed

Filename Overview
docs/openapi/schemas/management/oauth.yaml Adds complete_url, status_url, and next_steps to OAuthFlowInitiation; none of the existing or new fields are marked required, which will cause SDK generators to treat all response fields as optional even when they are always populated
docs/openapi/paths/management/mcp.yaml Adds client-initiate-verification and client-verify-headers endpoints; 400 error descriptions for both endpoints omit the 'client not in pending_verification state' condition
docs/openapi/schemas/management/mcp.yaml Adds pending_verification to MCPConnectionState enum with clear description linking to both verification endpoints; oauth_config description corrected to reflect required status for oauth/per_user_oauth
docs/openapi/openapi.yaml Wires new endpoints into the path registry and adds Conflict (409) as a reusable response component; changes are clean and consistent with existing structure
docs/openapi/openapi.json Bundled JSON mirrors all YAML changes accurately; no drift detected between the two representations

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Admin
    participant Bifrost
    participant OAuthProvider

    Note over Bifrost: Client declared in config.json<br/>State: pending_verification

    alt auth_type: oauth / per_user_oauth
        Admin->>Bifrost: "POST /api/mcp/client/{id}/initiate-verification"
        Bifrost->>OAuthProvider: OAuth metadata discovery (RFC 8414)
        Bifrost->>OAuthProvider: Dynamic client registration (RFC 7591)
        Bifrost-->>Admin: "OAuthFlowInitiation {authorize_url, complete_url, status_url, next_steps}"
        Admin->>OAuthProvider: Open authorize_url in browser
        OAuthProvider-->>Admin: Redirect with auth code
        loop Poll status_url
            Admin->>Bifrost: GET status_url
            Bifrost-->>Admin: "{status: pending/authorized}"
        end
        Admin->>Bifrost: POST complete_url
        Bifrost-->>Admin: "{status: success}"
        Note over Bifrost: State: connected
    else auth_type: per_user_headers
        Admin->>Bifrost: "POST /api/mcp/client/{id}/verify-headers {user_headers}"
        Bifrost->>OAuthProvider: Open upstream connection with sample headers
        Bifrost->>OAuthProvider: Discover tools
        Bifrost-->>Admin: "{status: success, tools_count: N}"
        Note over Bifrost: State: connected, sample headers discarded
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Admin
    participant Bifrost
    participant OAuthProvider

    Note over Bifrost: Client declared in config.json<br/>State: pending_verification

    alt auth_type: oauth / per_user_oauth
        Admin->>Bifrost: "POST /api/mcp/client/{id}/initiate-verification"
        Bifrost->>OAuthProvider: OAuth metadata discovery (RFC 8414)
        Bifrost->>OAuthProvider: Dynamic client registration (RFC 7591)
        Bifrost-->>Admin: "OAuthFlowInitiation {authorize_url, complete_url, status_url, next_steps}"
        Admin->>OAuthProvider: Open authorize_url in browser
        OAuthProvider-->>Admin: Redirect with auth code
        loop Poll status_url
            Admin->>Bifrost: GET status_url
            Bifrost-->>Admin: "{status: pending/authorized}"
        end
        Admin->>Bifrost: POST complete_url
        Bifrost-->>Admin: "{status: success}"
        Note over Bifrost: State: connected
    else auth_type: per_user_headers
        Admin->>Bifrost: "POST /api/mcp/client/{id}/verify-headers {user_headers}"
        Bifrost->>OAuthProvider: Open upstream connection with sample headers
        Bifrost->>OAuthProvider: Discover tools
        Bifrost-->>Admin: "{status: success, tools_count: N}"
        Note over Bifrost: State: connected, sample headers discarded
    end
Loading

Comments Outside Diff (1)

  1. docs/openapi/schemas/management/oauth.yaml, line 47-50 (link)

    P2 The three new fields (complete_url, status_url, next_steps) are described as the primary mechanism for completing an OAuth flow, yet OAuthFlowInitiation has no required array at all — not even for authorize_url. SDK generators (openapi-generator, oapi-codegen, etc.) will emit nullable/optional types for all of them, forcing every caller to add defensive null-checks for fields that are always populated on a successful initiation response.

Reviews (1): Last reviewed commit: "docs: mcp per user auth completions open..." | Re-trigger Greptile

Comment thread docs/openapi/paths/management/mcp.yaml
Comment thread docs/openapi/paths/management/mcp.yaml
@Pratham-Mishra04
Pratham-Mishra04 marked this pull request as draft July 3, 2026 05:19
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-02-docs_mcp_per_user_auth_completions_openapi_additions branch from 8f4139b to e152314 Compare July 3, 2026 06:17
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 05-29-docs_mcp_oauth_and_per_user_types_config_json_support_docs_update branch from cec0f92 to 00483f9 Compare July 3, 2026 06:17
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 05-29-docs_mcp_oauth_and_per_user_types_config_json_support_docs_update branch from 00483f9 to 5c8eae4 Compare July 21, 2026 09:16
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-02-docs_mcp_per_user_auth_completions_openapi_additions branch from e152314 to 23cf80b Compare July 21, 2026 09:16
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 05-29-docs_mcp_oauth_and_per_user_types_config_json_support_docs_update branch from 5c8eae4 to c75d5ea Compare July 30, 2026 23:38
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-02-docs_mcp_per_user_auth_completions_openapi_additions branch from 23cf80b to 65b9670 Compare July 30, 2026 23:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
docs/openapi/openapi.json (2)

43944-43961: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Declare the required properties of the 200 response.

The 200 schema lists status, message, and tools_count but declares no required array. Code generators then emit all three fields as optional. Callers must add null checks that the server never triggers. Add required for 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 value

The new reusable Conflict response is not referenced by the only new 409. This PR adds a Conflict response component and a 409 response on verify-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 references Conflict, remove the component; otherwise confirm the referencing operations in the source YAML.

Note that a $ref to a response component replaces the whole response object, including description. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 465aeaa and 7d7f5a7.

📒 Files selected for processing (5)
  • docs/openapi/openapi.json
  • docs/openapi/openapi.yaml
  • docs/openapi/paths/management/mcp.yaml
  • docs/openapi/schemas/management/mcp.yaml
  • docs/openapi/schemas/management/oauth.yaml

Comment thread docs/openapi/openapi.json
"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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 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 the OAuthFlowInitiation response 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-L43080
  • docs/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.

Comment thread docs/openapi/openapi.json
Comment on lines +43352 to +43363
"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"
}
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 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.yaml

Repository: 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)
PY

Repository: 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.

Comment thread docs/openapi/openapi.json
Comment on lines +43912 to +43932
"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"
}
}
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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' || true

Repository: 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/**' . || true

Repository: 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 -500

Repository: 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 -700

Repository: 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 -1200

Repository: 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' | sort

Repository: 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()}")
PY

Repository: 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 -1000

Repository: 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()}")
PY

Repository: 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 -700

Repository: 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]}")
PY

Repository: 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.

Comment on lines +69 to +85
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ 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 -500

Repository: 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 -700

Repository: 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 -700

Repository: 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

Pratham-Mishra04 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Aug 8, 8:47 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 8, 9:48 AM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 8, 9:49 AM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from 05-29-docs_mcp_oauth_and_per_user_types_config_json_support_docs_update to graphite-base/4858 August 8, 2026 09:44
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/4858 to dev August 8, 2026 09:47
@Pratham-Mishra04
Pratham-Mishra04 requested a review from a team as a code owner August 8, 2026 09:47
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-02-docs_mcp_per_user_auth_completions_openapi_additions branch from f467c2e to 9cbca49 Compare August 8, 2026 09:47
@Pratham-Mishra04
Pratham-Mishra04 merged commit e8c1c4b into dev Aug 8, 2026
14 of 15 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 07-02-docs_mcp_per_user_auth_completions_openapi_additions branch August 8, 2026 09:49
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.

2 participants