Skip to content

Support URL elicitation on MCP tool calls#450

Open
artjen wants to merge 3 commits into
databricks:mainfrom
artjen:url-elicitation
Open

Support URL elicitation on MCP tool calls#450
artjen wants to merge 3 commits into
databricks:mainfrom
artjen:url-elicitation

Conversation

@artjen

@artjen artjen commented Jun 30, 2026

Copy link
Copy Markdown

Summary

Adds URL-mode elicitation to DatabricksMCPClient in an embedding-safe way. The MCP SDK ships no default way to surface elicitation to users, so this makes the client's elicitation callback injectable.

Because databricks-mcp is frequently embedded in custom agents — often headless (Model Serving) or async — an interactive input()/webbrowser callback can't be the imposed default: it silently cancels where there's no TTY and blocks the event loop from acall_tool. So:

  • DatabricksMCPClient(..., elicitation_callback=None) — new optional param. Default None means the ClientSession advertises no elicitation capability (safe for headless/async). Agents inject their own callback to surface the URL through their own channel (UI, response stream, approval card).
  • interactive_url_elicitation_callback (exported) — a ready-made opt-in handler for local/CLI use: scheme-validates the server URL (http/https only), shows a security warning + confirmation prompt, and opens the approved URL. Its blocking prompt/browser runs off the event loop via run_in_executor, so it's safe from async code. URL-mode only; form-mode is rejected with INVALID_REQUEST.

Scope is the core client only; threading the callback through the OpenAI/LangChain wrappers is a follow-up.

The URL-elicitation handling draws on the MCP Python SDK's reference example: https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/url_elicitation_client.py

How do you know it works?

TestUrlElicitation covers: accept/decline/cancel, no-stdin (EOFError), disallowed-scheme rejection, form-mode rejection, elicitation off by default, an injected callback being stored, and both session paths forwarding either None (default) or the injected callback. Full unit suite passes under both uv resolutions (lowest-direct and highest) — 133 passed — and ruff check/format are clean.

Also bumps the mcp floor to >=1.23.0 (the release that introduced URL elicitation), so the lowest-direct CI resolution has the required symbols.

This pull request and its description were written by Isaac.

artjen added 2 commits June 30, 2026 11:29
Wire a URL-only elicitation callback into DatabricksMCPClient's ClientSession
for both list_tools and call_tool. The MCP SDK ships no default way to surface
elicitation to users (its default callback just replies "Elicitation not
supported" and advertises no capability), so define one internally:

- _handle_url_elicitation: validates the server-supplied URL's scheme against an
  http(s) allowlist, surfaces a security warning + confirmation prompt, opens the
  browser on accept, and returns accept/decline/cancel. Cancels when there is no
  interactive stdin.
- _url_only_elicitation_callback: routes URL-mode requests to the handler and
  rejects form-mode with INVALID_REQUEST so servers can fall back.

The callback is module-private; the public DatabricksMCPClient constructor is
unchanged.

Co-authored-by: Isaac
URL elicitation (ElicitRequestURLParams / ElicitRequestFormParams and the
mode-tagged ElicitRequestParams union) was introduced in mcp 1.23.0. The prior
floor mcp>=1.13.0 let the lowest-direct CI resolution install a release without
those symbols, breaking import of the new tests and the params.mode dispatch.

Co-authored-by: Isaac
@artjen

artjen commented Jun 30, 2026

Copy link
Copy Markdown
Author

Pre-existing CI failures (unrelated to this PR)

This PR only touches databricks_mcp/. The four red checks below fail independently of this change — they reproduce on main at this branch's base commit 59f216a:

  • openai_test (3.10, v0.3.0 / v0.4.0 / v0.5.0)integrations/openai/tests/unit_tests/test_vector_search_retriever_tool.py fails with MlflowException: Reading Databricks credential configuration failed ... tracking URI 'None'. This is an environment/credential issue in the OpenAI vector-search tests, in a package this PR does not modify. (The openai_test lowest-direct and highest jobs pass.)
  • typechecking for integrations/langchainty reports unresolved-attribute on timedelta.seconds at tests/unit_tests/test_multi_server_mcp_client.py:54, a file this PR does not touch.

All checks that exercise this change are green: mcp_test (lowest-direct), mcp_test (highest), ruff check for databricks_mcp, and typechecking for databricks_mcp.

For reference, the one regression this PR did introduce — mcp_test (lowest-direct) failing because URL elicitation requires mcp>=1.23.0 — was fixed by bumping the dependency floor.

@artjen artjen marked this pull request as ready for review June 30, 2026 18:54
databricks-mcp is frequently embedded in custom agents (often headless in
Model Serving, or async). Hardcoding an interactive input()/webbrowser
elicitation callback was wrong for that: it silently cancels in headless
contexts and blocks the event loop from acall_tool.

- Add an optional `elicitation_callback` to DatabricksMCPClient; default None
  means the ClientSession advertises no elicitation capability (safe default).
  Agents inject their own callback to surface the URL through their channel.
- Provide `interactive_url_elicitation_callback` (now exported) for local/CLI
  use, and run its blocking prompt/browser off the event loop via
  run_in_executor so it is safe from async code.
- Rename the internal callback accordingly; document the model in the README.

Co-authored-by: Isaac
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