-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
MCP Server Part 9: Background callbacks #3766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mcp
Are you sure you want to change the base?
Changes from all commits
493a9dc
62bcde8
806d4cd
311a593
77d3d66
b54c7ce
3ef3409
3004db8
eae9504
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||
| """Description for background (long-running) callbacks. | ||||||||||
|
|
||||||||||
| Informs the LLM that the tool returns a taskId immediately | ||||||||||
| and must be polled via the background task result tool. | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| from __future__ import annotations | ||||||||||
|
|
||||||||||
| from typing import TYPE_CHECKING | ||||||||||
|
|
||||||||||
| from ..tool_background_tasks import GET_RESULT_TOOL_NAME | ||||||||||
| from .base import ToolDescriptionSource | ||||||||||
|
|
||||||||||
| if TYPE_CHECKING: | ||||||||||
| from dash.mcp.primitives.tools.callback_adapter import CallbackAdapter | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class BackgroundCallbackDescription(ToolDescriptionSource): | ||||||||||
| """Add async polling instructions for background callbacks.""" | ||||||||||
|
|
||||||||||
| @classmethod | ||||||||||
| def describe(cls, callback: CallbackAdapter) -> list[str]: | ||||||||||
| # pylint: disable-next=protected-access | ||||||||||
| if not callback._cb_info.get("background"): | ||||||||||
| return [] | ||||||||||
|
|
||||||||||
| return [ | ||||||||||
| "", | ||||||||||
| "This is a long-running background operation. " | ||||||||||
| "It returns a taskId immediately. " | ||||||||||
|
Comment on lines
+29
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These get joined with newlines, so you could probably skip the ending spaces.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually implicit string concatenation (no commas), so they form a single string (without newlines). |
||||||||||
| f"Call tool `{GET_RESULT_TOOL_NAME}` with the taskId to poll for the result.", | ||||||||||
| ] | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,17 +7,19 @@ | |
| from __future__ import annotations | ||
|
|
||
| import json | ||
| from typing import Any | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| from mcp.types import CallToolResult, TextContent | ||
| from mcp.types import CallToolResult, CreateTaskResult, TextContent | ||
|
|
||
| from dash.types import CallbackExecutionResponse | ||
| from dash.mcp.primitives.tools.callback_adapter import CallbackAdapter | ||
|
|
||
| from .base import ResultFormatter | ||
| from .result_dataframe import DataFrameResult | ||
| from .result_plotly_figure import PlotlyFigureResult | ||
|
|
||
| if TYPE_CHECKING: | ||
| from dash.mcp.primitives.tools.callback_adapter import CallbackAdapter | ||
|
Comment on lines
+20
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this necessary?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
and the TYPE_CHECKING constant just prevents circular import errors. |
||
|
|
||
| _RESULT_FORMATTERS: list[type[ResultFormatter]] = [ | ||
| PlotlyFigureResult, | ||
| DataFrameResult, | ||
|
|
@@ -50,3 +52,33 @@ def format_callback_response( | |
| content=content, | ||
| structuredContent=dict(response), | ||
| ) | ||
|
|
||
|
|
||
| def task_result_to_tool_result(create_task_result: CreateTaskResult) -> CallToolResult: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is minor, but
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, we are creating a task in MCP parlance, which is just some long-running operation that agents need to wait for. |
||
| """ | ||
| Wrap a CreateTaskResult as a CallToolResult with polling instructions. | ||
|
|
||
| MCP Tasks are not yet supported by LLM clients, so this converts the | ||
| task metadata into a tool response that guides the LLM to poll via | ||
| the get_background_task_result tool. | ||
| """ | ||
| task = create_task_result.task | ||
| return CallToolResult( | ||
| content=[ | ||
| TextContent( | ||
| type="text", | ||
| text=json.dumps( | ||
| { | ||
| "taskId": task.taskId, | ||
| "status": task.status, | ||
| "pollInterval": task.pollInterval, | ||
| "message": ( | ||
| "This is a long-running background callback. " | ||
| "Call the get_background_task_result tool with this taskId " | ||
| "to poll for the result." | ||
| ), | ||
| } | ||
| ), | ||
| ) | ||
| ], | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would
CreateTaskResultget returned? Intask_result_to_tool_resultthe result is wrapped inCallToolResult.