edda-framework 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {edda_framework-0.2.0 → edda_framework-0.3.1}/PKG-INFO +47 -1
  2. {edda_framework-0.2.0 → edda_framework-0.3.1}/README.md +44 -0
  3. edda_framework-0.3.1/docs/integrations/mcp.md +230 -0
  4. edda_framework-0.3.1/edda/integrations/__init__.py +1 -0
  5. edda_framework-0.3.1/edda/integrations/mcp/__init__.py +40 -0
  6. edda_framework-0.3.1/edda/integrations/mcp/decorators.py +188 -0
  7. edda_framework-0.3.1/edda/integrations/mcp/server.py +261 -0
  8. edda_framework-0.3.1/examples/mcp/README.md +301 -0
  9. edda_framework-0.3.1/examples/mcp/order_processing_mcp.py +189 -0
  10. edda_framework-0.3.1/examples/mcp/remote_server_example.py +122 -0
  11. edda_framework-0.3.1/examples/mcp/simple_mcp_server.py +82 -0
  12. {edda_framework-0.2.0 → edda_framework-0.3.1}/pyproject.toml +4 -1
  13. edda_framework-0.3.1/tests/integrations/__init__.py +1 -0
  14. edda_framework-0.3.1/tests/integrations/mcp/__init__.py +1 -0
  15. edda_framework-0.3.1/tests/integrations/mcp/test_integration.py +183 -0
  16. edda_framework-0.3.1/tests/integrations/mcp/test_jsonrpc.py +139 -0
  17. edda_framework-0.3.1/tests/integrations/mcp/test_server.py +104 -0
  18. {edda_framework-0.2.0 → edda_framework-0.3.1}/uv.lock +229 -2
  19. {edda_framework-0.2.0 → edda_framework-0.3.1}/zensical.toml +3 -0
  20. {edda_framework-0.2.0 → edda_framework-0.3.1}/.github/workflows/ci.yml +0 -0
  21. {edda_framework-0.2.0 → edda_framework-0.3.1}/.github/workflows/docs.yml +0 -0
  22. {edda_framework-0.2.0 → edda_framework-0.3.1}/.github/workflows/release.yml +0 -0
  23. {edda_framework-0.2.0 → edda_framework-0.3.1}/.gitignore +0 -0
  24. {edda_framework-0.2.0 → edda_framework-0.3.1}/.python-version +0 -0
  25. {edda_framework-0.2.0 → edda_framework-0.3.1}/Justfile +0 -0
  26. {edda_framework-0.2.0 → edda_framework-0.3.1}/LICENSE +0 -0
  27. {edda_framework-0.2.0 → edda_framework-0.3.1}/demo_app.py +0 -0
  28. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/durable-execution/replay.md +0 -0
  29. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  30. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/events/wait-event.md +0 -0
  31. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/hooks.md +0 -0
  32. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/retry.md +0 -0
  33. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/saga-compensation.md +0 -0
  34. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/transactional-outbox.md +0 -0
  35. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/core-features/workflows-activities.md +0 -0
  36. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/examples/ecommerce.md +0 -0
  37. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/examples/events.md +0 -0
  38. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/examples/fastapi-integration.md +0 -0
  39. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/examples/saga.md +0 -0
  40. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/examples/simple.md +0 -0
  41. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/getting-started/concepts.md +0 -0
  42. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/getting-started/first-workflow.md +0 -0
  43. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/getting-started/installation.md +0 -0
  44. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/getting-started/quick-start.md +0 -0
  45. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/index.md +0 -0
  46. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/markdown.md +0 -0
  47. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  48. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/compensation-execution.png +0 -0
  49. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/conditional-branching-diagram.png +0 -0
  50. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/detail-overview-panel.png +0 -0
  51. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/execution-history-panel.png +0 -0
  52. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/form-generation-example.png +0 -0
  53. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/hybrid-diagram-example.png +0 -0
  54. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  55. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/start-workflow-dialog.png +0 -0
  56. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/status-badges-example.png +0 -0
  57. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  58. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  59. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/setup.md +0 -0
  60. {edda_framework-0.2.0 → edda_framework-0.3.1}/docs/viewer-ui/visualization.md +0 -0
  61. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/__init__.py +0 -0
  62. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/activity.py +0 -0
  63. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/app.py +0 -0
  64. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/compensation.py +0 -0
  65. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/context.py +0 -0
  66. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/events.py +0 -0
  67. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/exceptions.py +0 -0
  68. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/hooks.py +0 -0
  69. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/locking.py +0 -0
  70. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/outbox/__init__.py +0 -0
  71. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/outbox/relayer.py +0 -0
  72. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/outbox/transactional.py +0 -0
  73. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/pydantic_utils.py +0 -0
  74. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/replay.py +0 -0
  75. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/retry.py +0 -0
  76. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/serialization/__init__.py +0 -0
  77. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/serialization/base.py +0 -0
  78. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/serialization/json.py +0 -0
  79. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/storage/__init__.py +0 -0
  80. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/storage/models.py +0 -0
  81. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/storage/protocol.py +0 -0
  82. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/storage/sqlalchemy_storage.py +0 -0
  83. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/viewer_ui/__init__.py +0 -0
  84. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/viewer_ui/app.py +0 -0
  85. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/viewer_ui/components.py +0 -0
  86. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/viewer_ui/data_service.py +0 -0
  87. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/visualizer/__init__.py +0 -0
  88. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/visualizer/ast_analyzer.py +0 -0
  89. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/visualizer/mermaid_generator.py +0 -0
  90. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/workflow.py +0 -0
  91. {edda_framework-0.2.0 → edda_framework-0.3.1}/edda/wsgi.py +0 -0
  92. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/__init__.py +0 -0
  93. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/cancellable_workflow.py +0 -0
  94. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/compensation_workflow.py +0 -0
  95. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/event_waiting_app.py +0 -0
  96. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/event_waiting_workflow.py +0 -0
  97. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/event_waiting_workflow_complete.py +0 -0
  98. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/observability_with_logfire.py +0 -0
  99. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/pydantic_saga.py +0 -0
  100. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/retry_example.py +0 -0
  101. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/retry_with_compensation.py +0 -0
  102. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/simple_workflow.py +0 -0
  103. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/typeddict_example.py +0 -0
  104. {edda_framework-0.2.0 → edda_framework-0.3.1}/examples/with_outbox.py +0 -0
  105. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/__init__.py +0 -0
  106. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/conftest.py +0 -0
  107. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_activity.py +0 -0
  108. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_activity_retry.py +0 -0
  109. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_activity_sync.py +0 -0
  110. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_app.py +0 -0
  111. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_ast_analyzer.py +0 -0
  112. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_atomic_wait_event.py +0 -0
  113. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_binary_data.py +0 -0
  114. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_cloudevents_http_binding.py +0 -0
  115. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_compensation.py +0 -0
  116. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_compensation_crash_recovery.py.wip +0 -0
  117. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_concurrent_outbox.py +0 -0
  118. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_context.py +0 -0
  119. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_ctx_session.py +0 -0
  120. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_distributed_event_delivery.py +0 -0
  121. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_events.py +0 -0
  122. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_lock_race_condition.py +0 -0
  123. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_lock_timeout_customization.py +0 -0
  124. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_locking.py +0 -0
  125. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_multidb_storage.py +0 -0
  126. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_outbox.py +0 -0
  127. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_pydantic_activity.py +0 -0
  128. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_pydantic_enum.py +0 -0
  129. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_pydantic_events.py +0 -0
  130. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_pydantic_saga.py +0 -0
  131. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_pydantic_utils.py +0 -0
  132. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_received_event.py +0 -0
  133. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_replay.py +0 -0
  134. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_retry_policy.py +0 -0
  135. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_saga_parameter_extraction.py +0 -0
  136. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_serialization.py +0 -0
  137. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_skip_locked.py +0 -0
  138. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_stale_workflow_recovery.py +0 -0
  139. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_storage.py +0 -0
  140. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_storage_mysql.py +0 -0
  141. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_storage_postgresql.py +0 -0
  142. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_transactions.py +0 -0
  143. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_viewer_pydantic_form.py +0 -0
  144. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_viewer_start_saga.py +0 -0
  145. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_wait_timer.py +0 -0
  146. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_workflow.py +0 -0
  147. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_workflow_auto_register.py +0 -0
  148. {edda_framework-0.2.0 → edda_framework-0.3.1}/tests/test_workflow_cancellation.py +0 -0
  149. {edda_framework-0.2.0 → edda_framework-0.3.1}/viewer_app.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Lightweight Durable Execution Framework
5
5
  Project-URL: Homepage, https://github.com/i2y/edda
6
6
  Project-URL: Documentation, https://github.com/i2y/edda#readme
@@ -38,6 +38,8 @@ Requires-Dist: ruff>=0.14.2; extra == 'dev'
38
38
  Requires-Dist: testcontainers[mysql]>=4.0.0; extra == 'dev'
39
39
  Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
40
40
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
41
+ Provides-Extra: mcp
42
+ Requires-Dist: mcp>=1.22.0; extra == 'mcp'
41
43
  Provides-Extra: mysql
42
44
  Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
43
45
  Provides-Extra: postgresql
@@ -77,6 +79,7 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
77
79
  - 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
78
80
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
79
81
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
82
+ - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
80
83
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
81
84
 
82
85
  ## Use Cases
@@ -686,6 +689,49 @@ async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
686
689
 
687
690
  **Performance note**: ASGI servers (uvicorn, hypercorn) are recommended for better performance with Edda's async architecture. WSGI support is provided for compatibility with existing infrastructure and users who prefer synchronous programming.
688
691
 
692
+ ## MCP Integration
693
+
694
+ Edda integrates with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), allowing AI assistants like Claude to interact with your durable workflows as long-running tools.
695
+
696
+ ### Quick Example
697
+
698
+ ```python
699
+ from edda.integrations.mcp import EddaMCPServer
700
+ from edda import WorkflowContext, activity
701
+
702
+ # Create MCP server
703
+ server = EddaMCPServer(
704
+ name="Order Service",
705
+ db_url="postgresql://user:pass@localhost/orders",
706
+ )
707
+
708
+ @activity
709
+ async def process_payment(ctx: WorkflowContext, amount: float):
710
+ return {"status": "paid", "amount": amount}
711
+
712
+ @server.durable_tool(description="Process customer order")
713
+ async def process_order(ctx: WorkflowContext, order_id: str):
714
+ await process_payment(ctx, 99.99)
715
+ return {"status": "completed", "order_id": order_id}
716
+
717
+ # Deploy with uvicorn
718
+ if __name__ == "__main__":
719
+ import uvicorn
720
+ uvicorn.run(server.asgi_app(), host="0.0.0.0", port=8000)
721
+ ```
722
+
723
+ ### Auto-Generated Tools
724
+
725
+ Each `@durable_tool` automatically generates **three MCP tools**:
726
+
727
+ 1. **Main tool** (`process_order`): Starts the workflow, returns instance ID
728
+ 2. **Status tool** (`process_order_status`): Checks workflow progress
729
+ 3. **Result tool** (`process_order_result`): Gets final result when completed
730
+
731
+ This enables AI assistants to work with workflows that take minutes, hours, or even days to complete.
732
+
733
+ **For detailed documentation**, see [MCP Integration Guide](docs/integrations/mcp.md).
734
+
689
735
  ## Observability Hooks
690
736
 
691
737
  Extend Edda with custom observability without coupling to specific tools:
@@ -27,6 +27,7 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
27
27
  - 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
28
28
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
29
29
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
30
+ - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
30
31
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
31
32
 
32
33
  ## Use Cases
@@ -636,6 +637,49 @@ async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
636
637
 
637
638
  **Performance note**: ASGI servers (uvicorn, hypercorn) are recommended for better performance with Edda's async architecture. WSGI support is provided for compatibility with existing infrastructure and users who prefer synchronous programming.
638
639
 
640
+ ## MCP Integration
641
+
642
+ Edda integrates with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), allowing AI assistants like Claude to interact with your durable workflows as long-running tools.
643
+
644
+ ### Quick Example
645
+
646
+ ```python
647
+ from edda.integrations.mcp import EddaMCPServer
648
+ from edda import WorkflowContext, activity
649
+
650
+ # Create MCP server
651
+ server = EddaMCPServer(
652
+ name="Order Service",
653
+ db_url="postgresql://user:pass@localhost/orders",
654
+ )
655
+
656
+ @activity
657
+ async def process_payment(ctx: WorkflowContext, amount: float):
658
+ return {"status": "paid", "amount": amount}
659
+
660
+ @server.durable_tool(description="Process customer order")
661
+ async def process_order(ctx: WorkflowContext, order_id: str):
662
+ await process_payment(ctx, 99.99)
663
+ return {"status": "completed", "order_id": order_id}
664
+
665
+ # Deploy with uvicorn
666
+ if __name__ == "__main__":
667
+ import uvicorn
668
+ uvicorn.run(server.asgi_app(), host="0.0.0.0", port=8000)
669
+ ```
670
+
671
+ ### Auto-Generated Tools
672
+
673
+ Each `@durable_tool` automatically generates **three MCP tools**:
674
+
675
+ 1. **Main tool** (`process_order`): Starts the workflow, returns instance ID
676
+ 2. **Status tool** (`process_order_status`): Checks workflow progress
677
+ 3. **Result tool** (`process_order_result`): Gets final result when completed
678
+
679
+ This enables AI assistants to work with workflows that take minutes, hours, or even days to complete.
680
+
681
+ **For detailed documentation**, see [MCP Integration Guide](docs/integrations/mcp.md).
682
+
639
683
  ## Observability Hooks
640
684
 
641
685
  Extend Edda with custom observability without coupling to specific tools:
@@ -0,0 +1,230 @@
1
+ # MCP (Model Context Protocol) Integration
2
+
3
+ Edda provides seamless integration with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), allowing AI assistants like Claude to interact with your durable workflows as long-running tools.
4
+
5
+ ## Overview
6
+
7
+ MCP is a standardized protocol for AI tool integration. Edda's MCP integration automatically converts your durable workflows into MCP-compliant tools that:
8
+
9
+ - **Start workflows** and return instance IDs immediately
10
+ - **Check workflow status** to monitor progress
11
+ - **Retrieve results** when workflows complete
12
+
13
+ This enables AI assistants to work with long-running processes that may take minutes, hours, or even days to complete.
14
+
15
+ ## Installation
16
+
17
+ Install Edda with MCP support:
18
+
19
+ ```bash
20
+ pip install edda-framework[mcp]
21
+
22
+ # Or using uv
23
+ uv add edda-framework --extra mcp
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### 1. Create an MCP Server
29
+
30
+ ```python
31
+ from edda.integrations.mcp import EddaMCPServer
32
+ from edda import WorkflowContext, activity
33
+
34
+ # Create MCP server
35
+ server = EddaMCPServer(
36
+ name="Order Service",
37
+ db_url="postgresql://user:pass@localhost/orders",
38
+ )
39
+
40
+ @activity
41
+ async def reserve_inventory(ctx: WorkflowContext, items: list[str]):
42
+ # Your business logic here
43
+ return {"reserved": True}
44
+
45
+ @activity
46
+ async def process_payment(ctx: WorkflowContext, amount: float):
47
+ # Payment processing logic
48
+ return {"transaction_id": "txn_123"}
49
+
50
+ @server.durable_tool(description="Process customer order workflow")
51
+ async def process_order(ctx: WorkflowContext, order_id: str, items: list[str]):
52
+ """
53
+ Long-running order processing workflow.
54
+
55
+ This workflow reserves inventory, processes payment, and ships the order.
56
+ """
57
+ # Reserve inventory
58
+ await reserve_inventory(ctx, items)
59
+
60
+ # Process payment
61
+ await process_payment(ctx, 99.99)
62
+
63
+ return {"status": "completed", "order_id": order_id}
64
+ ```
65
+
66
+ ### 2. Deploy the Server
67
+
68
+ ```python
69
+ # Deploy with uvicorn (production)
70
+ if __name__ == "__main__":
71
+ import uvicorn
72
+ uvicorn.run(server.asgi_app(), host="0.0.0.0", port=8000)
73
+ ```
74
+
75
+ ```bash
76
+ # Run the server
77
+ uvicorn your_app:server.asgi_app --host 0.0.0.0 --port 8000
78
+ ```
79
+
80
+ ### 3. Use from MCP Clients (e.g., Claude Desktop)
81
+
82
+ Add to your MCP client configuration (e.g., Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "order-service": {
88
+ "command": "uvicorn",
89
+ "args": ["your_app:server.asgi_app", "--host", "127.0.0.1", "--port", "8000"],
90
+ "env": {
91
+ "DATABASE_URL": "postgresql://user:pass@localhost/orders"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Auto-Generated Tools
99
+
100
+ Each `@durable_tool` automatically generates **three MCP tools**:
101
+
102
+ ### 1. Main Tool: Start Workflow
103
+
104
+ ```
105
+ Tool Name: process_order
106
+ Description: Process customer order workflow
107
+
108
+ Input: {"order_id": "ORD-123", "items": ["item1", "item2"]}
109
+ Output: {
110
+ "content": [{
111
+ "type": "text",
112
+ "text": "Workflow 'process_order' started successfully.\nInstance ID: abc123...\n\nUse 'process_order_status' tool to check progress."
113
+ }],
114
+ "isError": false
115
+ }
116
+ ```
117
+
118
+ ### 2. Status Tool: Check Progress
119
+
120
+ ```
121
+ Tool Name: process_order_status
122
+ Description: Check status of process_order workflow
123
+
124
+ Input: {"instance_id": "abc123..."}
125
+ Output: {
126
+ "content": [{
127
+ "type": "text",
128
+ "text": "Workflow Status: running\nCurrent Activity: payment:1\nInstance ID: abc123..."
129
+ }],
130
+ "isError": false
131
+ }
132
+ ```
133
+
134
+ ### 3. Result Tool: Get Final Result
135
+
136
+ ```
137
+ Tool Name: process_order_result
138
+ Description: Get result of process_order workflow (if completed)
139
+
140
+ Input: {"instance_id": "abc123..."}
141
+ Output: {
142
+ "content": [{
143
+ "type": "text",
144
+ "text": "Workflow Result:\n{'status': 'completed', 'order_id': 'ORD-123'}"
145
+ }],
146
+ "isError": false
147
+ }
148
+ ```
149
+
150
+ ## Advanced Configuration
151
+
152
+ ### Authentication
153
+
154
+ Protect your MCP server with token-based authentication:
155
+
156
+ ```python
157
+ def verify_token(token: str) -> bool:
158
+ # Your token verification logic
159
+ return token == "secret-token-123"
160
+
161
+ server = EddaMCPServer(
162
+ name="Order Service",
163
+ db_url="postgresql://user:pass@localhost/orders",
164
+ token_verifier=verify_token,
165
+ )
166
+ ```
167
+
168
+ Clients must include the token in the Authorization header:
169
+
170
+ ```http
171
+ Authorization: Bearer secret-token-123
172
+ ```
173
+
174
+ ### Transactional Outbox Pattern
175
+
176
+ Enable event-driven architecture with outbox pattern:
177
+
178
+ ```python
179
+ server = EddaMCPServer(
180
+ name="Order Service",
181
+ db_url="postgresql://user:pass@localhost/orders",
182
+ outbox_enabled=True,
183
+ broker_url="nats://localhost:4222",
184
+ )
185
+ ```
186
+
187
+ ## MCP Protocol Compliance
188
+
189
+ Edda's MCP integration follows the [MCP Tools specification](https://modelcontextprotocol.io/docs/concepts/tools):
190
+
191
+ - **JSON-RPC 2.0**: All communication uses JSON-RPC 2.0 protocol
192
+ - **Content Arrays**: Responses include `content` array with text/image/resource items
193
+ - **Error Handling**: Errors are reported with `isError: true` flag
194
+ - **Stateless HTTP**: Uses MCP's streamable HTTP transport for production deployments
195
+
196
+ ## Architecture
197
+
198
+ ```
199
+ ┌─────────────────┐
200
+ │ MCP Client │
201
+ └────────┬────────┘
202
+ │ JSON-RPC 2.0
203
+ │ (HTTP Transport)
204
+
205
+ ┌─────────────────┐
206
+ │ EddaMCPServer │
207
+ │ ┌─────────┐ │
208
+ │ │ FastMCP │ │ ← Official MCP SDK
209
+ │ └─────────┘ │
210
+ │ ┌─────────┐ │
211
+ │ │ EddaApp │ │ ← Durable Execution
212
+ │ └─────────┘ │
213
+ └────────┬────────┘
214
+
215
+
216
+ ┌─────────────────┐
217
+ │ Database │
218
+ └─────────────────┘
219
+ ```
220
+
221
+
222
+ ## Related Documentation
223
+
224
+ - [Edda Workflows and Activities](../core-features/workflows-activities.md)
225
+ - [Transactional Outbox Pattern](../core-features/transactional-outbox.md)
226
+ - [MCP Protocol Specification](https://modelcontextprotocol.io/)
227
+
228
+ ## Examples
229
+
230
+ See the [examples/mcp/](../../examples/mcp/) directory for complete working examples.
@@ -0,0 +1 @@
1
+ """Edda integrations package."""
@@ -0,0 +1,40 @@
1
+ """
2
+ Edda MCP (Model Context Protocol) Integration.
3
+
4
+ Provides MCP server functionality for Edda durable workflows,
5
+ enabling long-running workflow tools via the MCP protocol.
6
+
7
+ Example:
8
+ ```python
9
+ from edda.integrations.mcp import EddaMCPServer
10
+ from edda import WorkflowContext, activity
11
+
12
+ server = EddaMCPServer(
13
+ name="Order Service",
14
+ db_url="postgresql://user:pass@localhost/orders",
15
+ )
16
+
17
+ @activity
18
+ async def reserve_inventory(ctx, items):
19
+ return {"reserved": True}
20
+
21
+ @server.durable_tool(description="Process order workflow")
22
+ async def process_order(ctx: WorkflowContext, order_id: str):
23
+ await reserve_inventory(ctx, [order_id], activity_id="reserve:1")
24
+ return {"status": "completed"}
25
+
26
+ # Deploy with uvicorn
27
+ if __name__ == "__main__":
28
+ import uvicorn
29
+ uvicorn.run(server.asgi_app(), host="0.0.0.0", port=8000)
30
+ ```
31
+
32
+ The server automatically generates three MCP tools for each @durable_tool:
33
+ - `tool_name`: Start the workflow, returns instance_id
34
+ - `tool_name_status`: Check workflow status
35
+ - `tool_name_result`: Get workflow result (if completed)
36
+ """
37
+
38
+ from edda.integrations.mcp.server import EddaMCPServer
39
+
40
+ __all__ = ["EddaMCPServer"]
@@ -0,0 +1,188 @@
1
+ """Decorators for MCP durable tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable
7
+ from typing import TYPE_CHECKING, Any, cast
8
+
9
+ from edda.workflow import workflow
10
+
11
+ if TYPE_CHECKING:
12
+ from edda.integrations.mcp.server import EddaMCPServer
13
+ from edda.workflow import Workflow
14
+
15
+
16
+ def create_durable_tool(
17
+ server: EddaMCPServer,
18
+ func: Callable[..., Any],
19
+ *,
20
+ description: str = "",
21
+ ) -> Workflow:
22
+ """
23
+ Create a durable workflow tool with auto-generated status/result tools.
24
+
25
+ This function:
26
+ 1. Wraps the function as an Edda @workflow
27
+ 2. Registers three MCP tools:
28
+ - {name}: Start workflow, return instance_id
29
+ - {name}_status: Check workflow status
30
+ - {name}_result: Get workflow result
31
+
32
+ Args:
33
+ server: EddaMCPServer instance
34
+ func: Async workflow function
35
+ description: Tool description
36
+
37
+ Returns:
38
+ Workflow instance
39
+ """
40
+ # 1. Create Edda workflow
41
+ workflow_instance = cast(Workflow, workflow(func, event_handler=False))
42
+ workflow_name = func.__name__
43
+
44
+ # Register in server's workflow registry
45
+ server._workflows[workflow_name] = workflow_instance
46
+
47
+ # 2. Generate main tool (start workflow)
48
+ tool_description = description or func.__doc__ or f"Start {workflow_name} workflow"
49
+
50
+ # Extract parameters from workflow function (excluding ctx)
51
+ sig = inspect.signature(func)
52
+ params = [
53
+ param
54
+ for name, param in sig.parameters.items()
55
+ if name != "ctx" # Exclude WorkflowContext parameter
56
+ ]
57
+
58
+ # Create the tool function
59
+ async def start_tool(**kwargs: Any) -> dict[str, Any]:
60
+ """
61
+ Start workflow and return instance_id.
62
+
63
+ This is the main entry point for the durable tool.
64
+ """
65
+ # Remove 'ctx' if provided by client (workflow will inject it)
66
+ kwargs.pop("ctx", None)
67
+
68
+ # Start Edda workflow
69
+ instance_id = await workflow_instance.start(**kwargs)
70
+
71
+ # Return MCP-compliant response
72
+ return {
73
+ "content": [
74
+ {
75
+ "type": "text",
76
+ "text": (
77
+ f"Workflow '{workflow_name}' started successfully.\n"
78
+ f"Instance ID: {instance_id}\n\n"
79
+ f"Use '{workflow_name}_status' tool with instance_id='{instance_id}' to check progress.\n"
80
+ f"Use '{workflow_name}_result' tool to get the final result once completed."
81
+ ),
82
+ }
83
+ ],
84
+ "isError": False,
85
+ }
86
+
87
+ # Override the function's signature for introspection (FastMCP uses this for schema generation)
88
+ start_tool.__signature__ = inspect.Signature(parameters=params) # type: ignore[attr-defined]
89
+
90
+ # Register with FastMCP (call as function, not decorator syntax)
91
+ server._mcp.tool(name=workflow_name, description=tool_description)(start_tool)
92
+
93
+ # 3. Generate status tool
94
+ status_tool_name = f"{workflow_name}_status"
95
+ status_tool_description = f"Check status of {workflow_name} workflow"
96
+
97
+ @server._mcp.tool(name=status_tool_name, description=status_tool_description) # type: ignore[misc]
98
+ async def status_tool(instance_id: str) -> dict[str, Any]:
99
+ """Check workflow status."""
100
+ try:
101
+ instance = await server._edda_app.storage.get_instance(instance_id)
102
+ if instance is None:
103
+ return {
104
+ "content": [
105
+ {
106
+ "type": "text",
107
+ "text": f"Workflow instance not found: {instance_id}",
108
+ }
109
+ ],
110
+ "isError": True,
111
+ }
112
+
113
+ status = instance["status"]
114
+ current_activity_id = instance.get("current_activity_id", "N/A")
115
+
116
+ status_text = (
117
+ f"Workflow Status: {status}\n"
118
+ f"Current Activity: {current_activity_id}\n"
119
+ f"Instance ID: {instance_id}"
120
+ )
121
+
122
+ return {
123
+ "content": [{"type": "text", "text": status_text}],
124
+ "isError": False,
125
+ }
126
+ except Exception as e:
127
+ return {
128
+ "content": [
129
+ {
130
+ "type": "text",
131
+ "text": f"Error checking status: {str(e)}",
132
+ }
133
+ ],
134
+ "isError": True,
135
+ }
136
+
137
+ # 4. Generate result tool
138
+ result_tool_name = f"{workflow_name}_result"
139
+ result_tool_description = f"Get result of {workflow_name} workflow (if completed)"
140
+
141
+ @server._mcp.tool(name=result_tool_name, description=result_tool_description) # type: ignore[misc]
142
+ async def result_tool(instance_id: str) -> dict[str, Any]:
143
+ """Get workflow result (if completed)."""
144
+ try:
145
+ instance = await server._edda_app.storage.get_instance(instance_id)
146
+ if instance is None:
147
+ return {
148
+ "content": [
149
+ {
150
+ "type": "text",
151
+ "text": f"Workflow instance not found: {instance_id}",
152
+ }
153
+ ],
154
+ "isError": True,
155
+ }
156
+
157
+ status = instance["status"]
158
+
159
+ if status != "completed":
160
+ return {
161
+ "content": [
162
+ {
163
+ "type": "text",
164
+ "text": f"Workflow not completed yet. Current status: {status}",
165
+ }
166
+ ],
167
+ "isError": True,
168
+ }
169
+
170
+ output_data = instance.get("output_data")
171
+ result_text = f"Workflow Result:\n{output_data}"
172
+
173
+ return {
174
+ "content": [{"type": "text", "text": result_text}],
175
+ "isError": False,
176
+ }
177
+ except Exception as e:
178
+ return {
179
+ "content": [
180
+ {
181
+ "type": "text",
182
+ "text": f"Error getting result: {str(e)}",
183
+ }
184
+ ],
185
+ "isError": True,
186
+ }
187
+
188
+ return workflow_instance