Skip to content

Restrict interactive MCP picker to MCP services for consumer access - #474

Draft
sunishsheth2009 wants to merge 1 commit into
databricks:mainfrom
sunishsheth2009:consumer-access-v2-mcp
Draft

Restrict interactive MCP picker to MCP services for consumer access#474
sunishsheth2009 wants to merge 1 commit into
databricks:mainfrom
sunishsheth2009:consumer-access-v2-mcp

Conversation

@sunishsheth2009

Copy link
Copy Markdown
Collaborator

What did you change, and why?

Change: The interactive MCP picker (ucode configure mcp / ucode mcp add) no longer offers V2 AI Gateway sources — Vector Search, UC Functions, external connections, Genie spaces, and Databricks apps (everything under /api/2.0/mcp/*). It now offers only MCP services (the /ai-gateway/mcp-services/ path), and skips the source-selection step since that's the sole source. Workspace users can still add a V2 server on request, non-interactively, by naming it in --services with a typed selector:

  • vector-search:<catalog>.<schema>
  • uc-functions:<catalog>.<schema>
  • external:<connection>
  • genie-space:<space-id>
  • app:<name>

Why: Consumer-only identities lack the workspace-access entitlement and can't reach the V2 AI Gateway, so offering those sources in the default flow leads to failures. MCP services is the consumer-safe path. This implements the team "Option 4" decision (and the AIGTWY-4471 graceful-skip pattern).

Consumer detection: The AI Gateway's WorkspaceAccessGuard returns an HTTP 403 whose message names the workspace-access entitlement — the only signal that distinguishes a consumer-only identity from a workspace user who merely lacks a grant on a specific resource (both are otherwise the same 403 / JsonRpc FORBIDDEN). PermissionDeniedError now carries consumer_only set from that marker; discovery skips it gracefully, and a consumer vs. a missing-grant get different, accurate error messages. The server-side guard is gated by the blockInferenceWithoutWorkspaceAccess SAFE flag (default off), so the preemptive block is best-effort and deliberately conservative (never blocks a workspace user on a non-marker 403).

How do you know it works?

Testing: uv run ruff check ., uv run ruff format --check, and the test_mcp.py / test_databricks.py suites all pass. Added tests cover: the picker offering only MCP services and skipping the source prompt (allow_back=False); the typed V2 --services selectors registering the right server URL; a consumer identity being blocked before any config write; V2 selectors rejected when combined with --location/plain names; PermissionDeniedError.consumer_only classification from the workspace-access marker (consumer) vs a resource 403 (not consumer); consumer_access_reason firing only on the marker; and discovery skipping PermissionDeniedError quietly while still warning on other errors.

Notes

  • Follow-up (separate PR): remove the now-dead V2 discovery functions in mcp.py and the underlying list_* helpers in databricks.py. It's interwoven — the VS/UC-Fn walk shares helpers with the retained mcp-services walk, and ~40 test monkeypatch stubs reference the discover_* names — so it needs its own diff + test refactor.

The interactive picker previously offered V2 AI Gateway sources (Vector
Search, UC Functions, external connections, Genie, Databricks apps, all
served under /api/2.0/mcp/*). Consumer-only identities lack the
workspace-access entitlement and can't reach those, so the picker now
offers only MCP services (the consumer-safe /ai-gateway/mcp-services path)
and skips the source-selection step when it's the sole source.

Workspace users still add a V2 server on request non-interactively by
naming it in --services with a typed selector (vector-search:cat.schema,
uc-functions:cat.schema, external:conn, genie-space:id, app:name).

Consumer detection uses the authoritative signal: the AI Gateway's
WorkspaceAccessGuard returns a 403 whose message names the
`workspace-access` entitlement, which is the only thing distinguishing a
consumer-only identity from a workspace user missing a specific grant.
PermissionDeniedError carries consumer_only accordingly, discovery skips
it gracefully, and the two cases get different, accurate error messages.

Co-authored-by: Isaac <no-reply@databricks.com>
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