MCP Server Part 9: Background callbacks#3766
Conversation
3c39eac to
a1ca057
Compare
3e146f8 to
0a82ca0
Compare
a1ca057 to
409be55
Compare
fd13290 to
5c45baf
Compare
409be55 to
b788678
Compare
01e8744 to
1366bd4
Compare
…move Flask dependency in MCP implementation
1366bd4 to
f4254e2
Compare
| if TYPE_CHECKING: | ||
| from dash.mcp.primitives.tools.callback_adapter import CallbackAdapter |
There was a problem hiding this comment.
Why was this necessary?
|
|
||
|
|
||
| def task_result_to_tool_result(create_task_result: CreateTaskResult) -> CallToolResult: | ||
| """Wrap a CreateTaskResult as a CallToolResult with polling instructions. |
There was a problem hiding this comment.
Ahem...
| """Wrap a CreateTaskResult as a CallToolResult with polling instructions. | |
| """ | |
| Wrap a CreateTaskResult as a CallToolResult with polling instructions. |
| "This is a long-running background operation. " | ||
| "It returns a taskId immediately. " |
There was a problem hiding this comment.
These get joined with newlines, so you could probably skip the ending spaces.
| "This is a long-running background operation. " | |
| "It returns a taskId immediately. " | |
| "This is a long-running background operation." | |
| "It returns a taskId immediately." |
| ) | ||
|
|
||
|
|
||
| def task_result_to_tool_result(create_task_result: CreateTaskResult) -> CallToolResult: |
There was a problem hiding this comment.
This is minor, but CreateTaskResult makes me think that you're creating something. That's not the case though. Is there another name option?
| def call_tool(cls, tool_name: str, arguments: dict[str, Any]) -> CallToolResult: | ||
| def call_tool( | ||
| cls, tool_name: str, arguments: dict[str, Any], task: dict | None = None | ||
| ) -> CallToolResult | CreateTaskResult: |
There was a problem hiding this comment.
Would CreateTaskResult get returned? In task_result_to_tool_result the result is wrapped in CallToolResult.
| tool_name: str, | ||
| arguments: dict[str, Any], | ||
| task: dict | None = None, | ||
| ) -> CallToolResult: |
There was a problem hiding this comment.
Could the result here be CreateTaskResult?
| if adapter is None: | ||
| return None |
There was a problem hiding this comment.
Does an error need to be returned here?
| from dash.mcp.types import MCPError | ||
|
|
||
|
|
||
| def parse_task_id(task_id: str) -> tuple[str, str, str, datetime]: |
There was a problem hiding this comment.
Could you add error handling here in the event that task_id is malformed?
| raise MCPError("No background callback manager configured.") | ||
|
|
||
| app = get_app() | ||
| adapter = app.mcp_callback_map.find_by_tool_name(tool_name) |
There was a problem hiding this comment.
Could this ever be None? If so, then 117 would throw an error.
| task_status = get_task(task_id) | ||
| if task_status.status == "completed": | ||
| return get_task_result(task_id) |
There was a problem hiding this comment.
Is the task guaranteed to still be available between these two calls? For example, if you have two clients polling for the same task, could one of them grab the result before the other and cause an error?
Summary
Adds support for MCP Tasks — when an LLM calls a tool backed by a Dash background callback, the tool returns a
taskIdimmediately and the LLM polls for results.dash/mcp/tasks/module that lists, starts, and cancels background callbacks per the MCP spec SEP-1686