@@ -2439,7 +2439,7 @@ def resolve_provider_launch_model(model: str | None, provider_models: dict[str,
24392439
24402440_UC_LIST_PAGE_SIZE = 200
24412441_UC_LIST_MAX_PAGES = 50
2442- _UC_FUNCTION_PROBE_WORKERS = 16
2442+ _SCHEMA_PROBE_WORKERS = 16
24432443_UC_LIST_HTTP_TIMEOUT = 10
24442444# Most MCP services live outside `system.ai`, so this workspace-wide walk needs
24452445# enough time to enumerate them; a slow workspace still degrades to partial
@@ -2513,30 +2513,30 @@ def _paginated_json_items(
25132513 return items , last_reason
25142514
25152515
2516- def list_all_mcp_services (
2516+ def walk_catalog_schemas [ T ] (
25172517 workspace : str ,
25182518 token : str ,
25192519 * ,
2520- deadline_seconds : float = _MCP_SERVICES_WALK_DEADLINE_SECONDS ,
2521- on_progress : Callable [[int , int , int ], None ] | None = None ,
2522- on_services : Callable [[list [str ]], None ] | None = None ,
2523- ) -> tuple [list [str ], str | None ]:
2524- """Return sorted unique MCP-service full names across every `<catalog>.<schema>`
2525- in the workspace. The mcp-services API is one-schema-per-call, so this walks
2526- catalogs -> schemas -> mcp-services in parallel under a wall-clock budget,
2527- returning partial results once `deadline_seconds` is exceeded.
2528-
2529- `on_progress`, if given, is called as each schema's listing completes with
2530- `(schemas_done, schemas_total, services_found)` so callers can render a live
2531- count. `on_services`, if given, is called with each schema's newly-found service
2532- names (deduped against everything emitted so far) so callers can stream results
2533- into a picker as the walk progresses instead of waiting for the full result. Both
2534- are invoked serially from the draining thread (not the workers).
2535-
2536- This walk is the slow, workspace-wide counterpart to `list_mcp_services`
2537- (single schema)."""
2520+ deadline : float ,
2521+ probe : Callable [[str , str ], T ],
2522+ collect : Callable [[T , int , int ], None ],
2523+ skip_catalogs : frozenset [str ] = _UC_FUNCTIONS_SKIP_CATALOGS ,
2524+ max_workers : int = _SCHEMA_PROBE_WORKERS ,
2525+ ) -> str | None :
2526+ """Discover every user `<catalog>.<schema>` in the workspace and probe each one in parallel.
2527+
2528+ Catalogs and their schemas are listed (skipping `skip_catalogs` and `information_schema`), then
2529+ each schema is probed concurrently until `deadline` (an absolute `time.monotonic()` value)
2530+ passes, so a slow workspace returns partial results instead of hanging. The caller supplies two
2531+ callables and owns whatever they accumulate:
2532+
2533+ - `probe(catalog, schema) -> result`: fetch one schema's data (e.g. its MCP services).
2534+ - `collect(result, done, total)`: handle each probe result as it lands — accumulating,
2535+ de-duping, streaming — where `done`/`total` are the completed and total schema counts.
2536+
2537+ Returns None once the probes run, or a short reason string if there are no catalogs or schemas
2538+ to probe."""
25382539 hostname = workspace_hostname (workspace )
2539- deadline = time .monotonic () + deadline_seconds
25402540
25412541 catalogs , catalogs_reason = _paginated_json_items (
25422542 f"https://{ hostname } /api/2.1/unity-catalog/catalogs" ,
@@ -2545,23 +2545,20 @@ def list_all_mcp_services(
25452545 timeout = _UC_LIST_HTTP_TIMEOUT ,
25462546 )
25472547 if not catalogs :
2548- return [], catalogs_reason or "no UC catalogs found"
2548+ return catalogs_reason or "no UC catalogs found"
25492549
25502550 catalog_names = [
25512551 c ["name" ]
25522552 for c in catalogs
2553- if isinstance (c .get ("name" ), str )
2554- and c ["name" ]
2555- and c ["name" ] not in _UC_FUNCTIONS_SKIP_CATALOGS
2553+ if isinstance (c .get ("name" ), str ) and c ["name" ] and c ["name" ] not in skip_catalogs
25562554 ]
25572555 if not catalog_names :
2558- return [], "no user UC catalogs found"
2556+ return "no user UC catalogs found"
25592557 if time .monotonic () > deadline :
2560- return [], "deadline exceeded while listing UC catalogs"
2558+ return "deadline exceeded while listing UC catalogs"
25612559
2562- # Parallel per-catalog schema listing.
2563- schema_refs : list [str ] = []
2564- schema_workers = max (1 , min (_UC_FUNCTION_PROBE_WORKERS , len (catalog_names )))
2560+ schema_refs : list [tuple [str , str ]] = []
2561+ schema_workers = max (1 , min (max_workers , len (catalog_names )))
25652562 with ThreadPoolExecutor (max_workers = schema_workers ) as pool :
25662563 schema_futures = {
25672564 pool .submit (
@@ -2584,40 +2581,76 @@ def collect_schemas(result, catalog):
25842581 and schema_name
25852582 and schema_name != "information_schema"
25862583 ):
2587- schema_refs .append (f" { catalog } . { schema_name } " )
2584+ schema_refs .append (( catalog , schema_name ) )
25882585
25892586 _drain_with_deadline (schema_futures , deadline , collect_schemas )
25902587 pool .shutdown (wait = False , cancel_futures = True )
25912588
25922589 if not schema_refs :
25932590 if time .monotonic () > deadline :
2594- return [], "deadline exceeded while listing UC schemas"
2595- return [], "no UC schemas found"
2591+ return "deadline exceeded while listing UC schemas"
2592+ return "no UC schemas found"
25962593
2597- # Parallel per-schema mcp-services listing.
2598- names : set [str ] = set ()
25992594 schemas_total = len (schema_refs )
26002595 schemas_done = 0
2601- probe_workers = max (1 , min (_UC_FUNCTION_PROBE_WORKERS , schemas_total ))
2596+ probe_workers = max (1 , min (max_workers , schemas_total ))
26022597 with ThreadPoolExecutor (max_workers = probe_workers ) as pool :
2603- service_futures = {
2604- pool .submit (list_mcp_services , workspace , token , ref ): ref for ref in schema_refs
2598+ probe_futures = {
2599+ pool .submit (probe , catalog , schema ): (catalog , schema )
2600+ for catalog , schema in schema_refs
26052601 }
26062602
2607- def collect_services (result , _ref ):
2603+ def collect_probe (result , _ref ):
26082604 nonlocal schemas_done
2609- found , _ = result
2610- new = [n for n in found if n not in names ]
2611- names .update (found )
26122605 schemas_done += 1
2613- if on_progress is not None :
2614- on_progress (schemas_done , schemas_total , len (names ))
2615- if on_services is not None and new :
2616- on_services (sorted (new ))
2606+ collect (result , schemas_done , schemas_total )
26172607
2618- _drain_with_deadline (service_futures , deadline , collect_services )
2608+ _drain_with_deadline (probe_futures , deadline , collect_probe )
26192609 pool .shutdown (wait = False , cancel_futures = True )
26202610
2611+ return None
2612+
2613+
2614+ def list_all_mcp_services (
2615+ workspace : str ,
2616+ token : str ,
2617+ * ,
2618+ deadline_seconds : float = _MCP_SERVICES_WALK_DEADLINE_SECONDS ,
2619+ on_progress : Callable [[int , int , int ], None ] | None = None ,
2620+ on_services : Callable [[list [str ]], None ] | None = None ,
2621+ ) -> tuple [list [str ], str | None ]:
2622+ """Return sorted unique MCP-service full names across every `<catalog>.<schema>`
2623+ in the workspace. The mcp-services API is one-schema-per-call, so this walks
2624+ catalogs -> schemas -> mcp-services in parallel under a wall-clock budget,
2625+ returning partial results once `deadline_seconds` is exceeded.
2626+
2627+ `on_progress`, if given, is called as each schema's listing completes with
2628+ `(schemas_done, schemas_total, services_found)` so callers can render a live
2629+ count. `on_services`, if given, is called with each schema's newly-found service
2630+ names (deduped against everything emitted so far) so callers can stream results
2631+ into a picker as the walk progresses instead of waiting for the full result. Both
2632+ are invoked serially from the draining thread (not the workers).
2633+
2634+ This walk is the slow, workspace-wide counterpart to `list_mcp_services`
2635+ (single schema)."""
2636+ deadline = time .monotonic () + deadline_seconds
2637+ names : set [str ] = set ()
2638+
2639+ def probe (catalog , schema ):
2640+ return list_mcp_services (workspace , token , f"{ catalog } .{ schema } " )
2641+
2642+ def collect (result , schemas_done , schemas_total ):
2643+ found , _ = result
2644+ new = [n for n in found if n not in names ]
2645+ names .update (found )
2646+ if on_progress is not None :
2647+ on_progress (schemas_done , schemas_total , len (names ))
2648+ if on_services is not None and new :
2649+ on_services (sorted (new ))
2650+
2651+ reason = walk_catalog_schemas (workspace , token , deadline = deadline , probe = probe , collect = collect )
2652+ if reason is not None :
2653+ return [], reason
26212654 if not names :
26222655 if time .monotonic () > deadline :
26232656 return [], "deadline exceeded while listing MCP services"
0 commit comments