From c060bfcf86e5b2143e1b8830a05295d277d645d7 Mon Sep 17 00:00:00 2001 From: "tibian.zaman" Date: Mon, 27 Jul 2026 13:41:45 -0700 Subject: [PATCH 1/4] feat: add optional run_id to get_workflow_history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the run_id pattern already established in get_workflow_event (#39). Allows callers to target a specific run's history when a workflow has been retried; previously only the latest run was reachable. Additive — response now always includes a run_id key (matching get_workflow_event's shape). Closes #50. Co-Authored-By: Claude Opus 4.7 (1M context) --- temporal_mcp/handlers/workflow_handlers.py | 7 +++-- temporal_mcp/tools/tool_definitions.py | 1 + tests/test_workflow_handlers.py | 32 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/temporal_mcp/handlers/workflow_handlers.py b/temporal_mcp/handlers/workflow_handlers.py index 49383aa..9544558 100644 --- a/temporal_mcp/handlers/workflow_handlers.py +++ b/temporal_mcp/handlers/workflow_handlers.py @@ -215,15 +215,16 @@ async def get_workflow_history(client: Client, args: dict) -> list[TextContent]: Args: client: Connected Temporal client - args: Arguments containing workflow_id and optional limit + args: Arguments containing workflow_id and optional limit, run_id Returns: Workflow history events """ workflow_id = args["workflow_id"] limit = args.get("limit", 1000) + run_id = args.get("run_id") - handle = client.get_workflow_handle(workflow_id) + handle = client.get_workflow_handle(workflow_id, run_id=run_id) events = [] scheduled_activities: dict[int, dict[str, Any]] = {} @@ -249,7 +250,7 @@ async def get_workflow_history(client: Client, args: dict) -> list[TextContent]: if count >= limit: break - return [TextContent(type="text", text=json.dumps({"workflow_id": workflow_id, "events": events, "count": len(events)}, indent=2))] + return [TextContent(type="text", text=json.dumps({"workflow_id": workflow_id, "run_id": run_id, "events": events, "count": len(events)}, indent=2))] def _workflow_history_event_to_dict(event: Any, scheduled_activities: dict[int, dict[str, Any]], initiated_child_workflows: dict[int, dict[str, Any]]) -> dict[str, Any]: diff --git a/temporal_mcp/tools/tool_definitions.py b/temporal_mcp/tools/tool_definitions.py index dc46e02..8408144 100644 --- a/temporal_mcp/tools/tool_definitions.py +++ b/temporal_mcp/tools/tool_definitions.py @@ -93,6 +93,7 @@ def get_all_tools() -> list[Tool]: "type": "object", "properties": { "workflow_id": {"type": "string", "description": "The workflow execution ID"}, + "run_id": {"type": "string", "description": "Optional run ID for the workflow execution"}, "limit": {"type": "number", "description": "Maximum number of history events to return (default: 1000)"}, }, "required": ["workflow_id"], diff --git a/tests/test_workflow_handlers.py b/tests/test_workflow_handlers.py index 4b76c37..27721e5 100644 --- a/tests/test_workflow_handlers.py +++ b/tests/test_workflow_handlers.py @@ -282,6 +282,38 @@ async def mock_fetch_history_events(): assert "__raw__" not in failed_attrs assert "result" not in failed_attrs + @pytest.mark.asyncio + async def test_get_workflow_history_passes_run_id_when_provided(self, mock_client): + async def mock_fetch_history_events(): + if False: + yield None + + mock_handle = AsyncMock() + mock_handle.fetch_history_events = mock_fetch_history_events + mock_client.get_workflow_handle = MagicMock(return_value=mock_handle) + + result = await workflow_handlers.get_workflow_history(mock_client, {"workflow_id": "test-workflow-123", "run_id": "run-xyz"}) + + response = json.loads(result[0].text) + assert response["run_id"] == "run-xyz" + mock_client.get_workflow_handle.assert_called_once_with("test-workflow-123", run_id="run-xyz") + + @pytest.mark.asyncio + async def test_get_workflow_history_defaults_run_id_to_none(self, mock_client): + async def mock_fetch_history_events(): + if False: + yield None + + mock_handle = AsyncMock() + mock_handle.fetch_history_events = mock_fetch_history_events + mock_client.get_workflow_handle = MagicMock(return_value=mock_handle) + + result = await workflow_handlers.get_workflow_history(mock_client, {"workflow_id": "test-workflow-123"}) + + response = json.loads(result[0].text) + assert response["run_id"] is None + mock_client.get_workflow_handle.assert_called_once_with("test-workflow-123", run_id=None) + class TestGetWorkflowEvent: @pytest.mark.asyncio From 486980241913ddba37156f4b9b7b3e4a5cc439d1 Mon Sep 17 00:00:00 2001 From: Mike Toscano Date: Tue, 28 Jul 2026 20:58:45 -0400 Subject: [PATCH 2/4] chore: rerun checks From 102dd994701f849b5bf0203e341dc64c56281c95 Mon Sep 17 00:00:00 2001 From: Mike Toscano Date: Tue, 28 Jul 2026 22:06:00 -0400 Subject: [PATCH 3/4] chore: trigger updated security workflows From 62941d56a44a6af8a0a14d13c692b411ad1fe06d Mon Sep 17 00:00:00 2001 From: "tibian.zaman" Date: Tue, 28 Jul 2026 23:35:48 -0700 Subject: [PATCH 4/4] review: address run_id feedback Clarify tool schema that omitted run_id targets the latest run, and extend the new tests to assert the full response shape (workflow_id, events, count) alongside run_id. Co-Authored-By: Claude Opus 4.7 (1M context) --- temporal_mcp/tools/tool_definitions.py | 2 +- tests/test_workflow_handlers.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/temporal_mcp/tools/tool_definitions.py b/temporal_mcp/tools/tool_definitions.py index 8408144..d99815b 100644 --- a/temporal_mcp/tools/tool_definitions.py +++ b/temporal_mcp/tools/tool_definitions.py @@ -93,7 +93,7 @@ def get_all_tools() -> list[Tool]: "type": "object", "properties": { "workflow_id": {"type": "string", "description": "The workflow execution ID"}, - "run_id": {"type": "string", "description": "Optional run ID for the workflow execution"}, + "run_id": {"type": "string", "description": "Optional run ID for the workflow execution; omit to target the latest run"}, "limit": {"type": "number", "description": "Maximum number of history events to return (default: 1000)"}, }, "required": ["workflow_id"], diff --git a/tests/test_workflow_handlers.py b/tests/test_workflow_handlers.py index 27721e5..a66e979 100644 --- a/tests/test_workflow_handlers.py +++ b/tests/test_workflow_handlers.py @@ -295,7 +295,10 @@ async def mock_fetch_history_events(): result = await workflow_handlers.get_workflow_history(mock_client, {"workflow_id": "test-workflow-123", "run_id": "run-xyz"}) response = json.loads(result[0].text) + assert response["workflow_id"] == "test-workflow-123" assert response["run_id"] == "run-xyz" + assert response["events"] == [] + assert response["count"] == 0 mock_client.get_workflow_handle.assert_called_once_with("test-workflow-123", run_id="run-xyz") @pytest.mark.asyncio @@ -311,7 +314,10 @@ async def mock_fetch_history_events(): result = await workflow_handlers.get_workflow_history(mock_client, {"workflow_id": "test-workflow-123"}) response = json.loads(result[0].text) + assert response["workflow_id"] == "test-workflow-123" assert response["run_id"] is None + assert response["events"] == [] + assert response["count"] == 0 mock_client.get_workflow_handle.assert_called_once_with("test-workflow-123", run_id=None)