|
102 | 102 |
|
103 | 103 | logger = get_logger(__name__) |
104 | 104 |
|
| 105 | + |
| 106 | +def _create_named_fn_wrapper(fn: Callable[..., Any], name: str) -> Callable[..., Any]: |
| 107 | + """Create a wrapper function with a custom __name__ for Docket registration. |
| 108 | +
|
| 109 | + Docket uses fn.__name__ as the key for function registration and lookup. |
| 110 | + When mounting servers, we need unique names to avoid collisions between |
| 111 | + mounted servers that have identically-named functions. |
| 112 | + """ |
| 113 | + import functools |
| 114 | + |
| 115 | + @functools.wraps(fn) |
| 116 | + async def wrapper(*args: Any, **kwargs: Any) -> Any: |
| 117 | + return await fn(*args, **kwargs) |
| 118 | + |
| 119 | + wrapper.__name__ = name |
| 120 | + return wrapper |
| 121 | + |
| 122 | + |
105 | 123 | DuplicateBehavior = Literal["warn", "error", "replace", "ignore"] |
106 | 124 | Transport = Literal["stdio", "http", "sse", "streamable-http"] |
107 | 125 |
|
@@ -449,7 +467,7 @@ async def _docket_lifespan(self) -> AsyncIterator[None]: |
449 | 467 | # execute in the parent's Docket context |
450 | 468 | for mounted in self._mounted_servers: |
451 | 469 | await self._register_mounted_server_functions( |
452 | | - mounted.server, docket |
| 470 | + mounted.server, docket, mounted.prefix |
453 | 471 | ) |
454 | 472 |
|
455 | 473 | # Set Docket in ContextVar so CurrentDocket can access it |
@@ -491,45 +509,69 @@ async def _docket_lifespan(self) -> AsyncIterator[None]: |
491 | 509 | _current_server.reset(server_token) |
492 | 510 |
|
493 | 511 | async def _register_mounted_server_functions( |
494 | | - self, server: FastMCP, docket: Docket |
| 512 | + self, server: FastMCP, docket: Docket, prefix: str | None |
495 | 513 | ) -> None: |
496 | 514 | """Register task-enabled functions from a mounted server with Docket. |
497 | 515 |
|
498 | 516 | This enables background task execution for mounted server components |
499 | 517 | through the parent server's Docket context. |
| 518 | +
|
| 519 | + Args: |
| 520 | + server: The mounted server whose functions to register |
| 521 | + docket: The Docket instance to register with |
| 522 | + prefix: The mount prefix to prepend to function names (matches |
| 523 | + client-facing tool/prompt names) |
500 | 524 | """ |
501 | | - # Register tools |
| 525 | + # Register tools with prefixed names to avoid collisions |
502 | 526 | for tool in server._tool_manager._tools.values(): |
503 | 527 | if isinstance(tool, FunctionTool) and tool.task_config.mode != "forbidden": |
504 | | - docket.register(tool.fn) |
| 528 | + # Use same naming as client-facing tool keys |
| 529 | + fn_name = f"{prefix}_{tool.key}" if prefix else tool.key |
| 530 | + named_fn = _create_named_fn_wrapper(tool.fn, fn_name) |
| 531 | + docket.register(named_fn) |
505 | 532 |
|
506 | | - # Register prompts |
| 533 | + # Register prompts with prefixed names |
507 | 534 | for prompt in server._prompt_manager._prompts.values(): |
508 | 535 | if ( |
509 | 536 | isinstance(prompt, FunctionPrompt) |
510 | 537 | and prompt.task_config.mode != "forbidden" |
511 | 538 | ): |
512 | | - docket.register(cast(Callable[..., Awaitable[Any]], prompt.fn)) |
| 539 | + fn_name = f"{prefix}_{prompt.key}" if prefix else prompt.key |
| 540 | + named_fn = _create_named_fn_wrapper( |
| 541 | + cast(Callable[..., Awaitable[Any]], prompt.fn), fn_name |
| 542 | + ) |
| 543 | + docket.register(named_fn) |
513 | 544 |
|
514 | | - # Register resources |
| 545 | + # Register resources with prefixed names (use name, not key/URI) |
515 | 546 | for resource in server._resource_manager._resources.values(): |
516 | 547 | if ( |
517 | 548 | isinstance(resource, FunctionResource) |
518 | 549 | and resource.task_config.mode != "forbidden" |
519 | 550 | ): |
520 | | - docket.register(resource.fn) |
| 551 | + fn_name = f"{prefix}_{resource.name}" if prefix else resource.name |
| 552 | + named_fn = _create_named_fn_wrapper(resource.fn, fn_name) |
| 553 | + docket.register(named_fn) |
521 | 554 |
|
522 | | - # Register resource templates |
| 555 | + # Register resource templates with prefixed names (use name, not key/URI) |
523 | 556 | for template in server._resource_manager._templates.values(): |
524 | 557 | if ( |
525 | 558 | isinstance(template, FunctionResourceTemplate) |
526 | 559 | and template.task_config.mode != "forbidden" |
527 | 560 | ): |
528 | | - docket.register(template.fn) |
| 561 | + fn_name = f"{prefix}_{template.name}" if prefix else template.name |
| 562 | + named_fn = _create_named_fn_wrapper(template.fn, fn_name) |
| 563 | + docket.register(named_fn) |
529 | 564 |
|
530 | | - # Recursively register from nested mounted servers |
| 565 | + # Recursively register from nested mounted servers with accumulated prefix |
531 | 566 | for nested in server._mounted_servers: |
532 | | - await self._register_mounted_server_functions(nested.server, docket) |
| 567 | + nested_prefix = ( |
| 568 | + f"{prefix}_{nested.prefix}" |
| 569 | + if prefix and nested.prefix |
| 570 | + else (prefix or nested.prefix) |
| 571 | + ) |
| 572 | + await self._register_mounted_server_functions( |
| 573 | + nested.server, docket, nested_prefix |
| 574 | + ) |
533 | 575 |
|
534 | 576 | @asynccontextmanager |
535 | 577 | async def _lifespan_manager(self) -> AsyncIterator[None]: |
|
0 commit comments