edda-framework 0.1.0__tar.gz → 0.3.0__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.1.0 → edda_framework-0.3.0}/PKG-INFO +95 -1
  2. {edda_framework-0.1.0 → edda_framework-0.3.0}/README.md +90 -0
  3. {edda_framework-0.1.0 → edda_framework-0.3.0}/demo_app.py +6 -0
  4. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/workflows-activities.md +64 -9
  5. edda_framework-0.3.0/docs/integrations/mcp.md +230 -0
  6. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/__init__.py +2 -0
  7. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/activity.py +32 -19
  8. edda_framework-0.3.0/edda/integrations/__init__.py +1 -0
  9. edda_framework-0.3.0/edda/integrations/mcp/__init__.py +40 -0
  10. edda_framework-0.3.0/edda/integrations/mcp/decorators.py +168 -0
  11. edda_framework-0.3.0/edda/integrations/mcp/server.py +257 -0
  12. edda_framework-0.3.0/edda/wsgi.py +77 -0
  13. edda_framework-0.3.0/examples/mcp/README.md +301 -0
  14. edda_framework-0.3.0/examples/mcp/order_processing_mcp.py +189 -0
  15. edda_framework-0.3.0/examples/mcp/remote_server_example.py +122 -0
  16. edda_framework-0.3.0/examples/mcp/simple_mcp_server.py +82 -0
  17. {edda_framework-0.1.0 → edda_framework-0.3.0}/pyproject.toml +6 -1
  18. edda_framework-0.3.0/tests/integrations/__init__.py +1 -0
  19. edda_framework-0.3.0/tests/integrations/mcp/__init__.py +1 -0
  20. edda_framework-0.3.0/tests/integrations/mcp/test_integration.py +183 -0
  21. edda_framework-0.3.0/tests/integrations/mcp/test_jsonrpc.py +139 -0
  22. edda_framework-0.3.0/tests/integrations/mcp/test_server.py +104 -0
  23. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_activity.py +12 -6
  24. edda_framework-0.3.0/tests/test_activity_sync.py +218 -0
  25. {edda_framework-0.1.0 → edda_framework-0.3.0}/uv.lock +242 -2
  26. {edda_framework-0.1.0 → edda_framework-0.3.0}/zensical.toml +3 -0
  27. {edda_framework-0.1.0 → edda_framework-0.3.0}/.github/workflows/ci.yml +0 -0
  28. {edda_framework-0.1.0 → edda_framework-0.3.0}/.github/workflows/docs.yml +0 -0
  29. {edda_framework-0.1.0 → edda_framework-0.3.0}/.github/workflows/release.yml +0 -0
  30. {edda_framework-0.1.0 → edda_framework-0.3.0}/.gitignore +0 -0
  31. {edda_framework-0.1.0 → edda_framework-0.3.0}/.python-version +0 -0
  32. {edda_framework-0.1.0 → edda_framework-0.3.0}/Justfile +0 -0
  33. {edda_framework-0.1.0 → edda_framework-0.3.0}/LICENSE +0 -0
  34. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/durable-execution/replay.md +0 -0
  35. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  36. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/events/wait-event.md +0 -0
  37. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/hooks.md +0 -0
  38. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/retry.md +0 -0
  39. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/saga-compensation.md +0 -0
  40. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/core-features/transactional-outbox.md +0 -0
  41. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/examples/ecommerce.md +0 -0
  42. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/examples/events.md +0 -0
  43. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/examples/fastapi-integration.md +0 -0
  44. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/examples/saga.md +0 -0
  45. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/examples/simple.md +0 -0
  46. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/getting-started/concepts.md +0 -0
  47. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/getting-started/first-workflow.md +0 -0
  48. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/getting-started/installation.md +0 -0
  49. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/getting-started/quick-start.md +0 -0
  50. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/index.md +0 -0
  51. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/markdown.md +0 -0
  52. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  53. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/compensation-execution.png +0 -0
  54. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/conditional-branching-diagram.png +0 -0
  55. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/detail-overview-panel.png +0 -0
  56. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/execution-history-panel.png +0 -0
  57. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/form-generation-example.png +0 -0
  58. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/hybrid-diagram-example.png +0 -0
  59. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  60. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/start-workflow-dialog.png +0 -0
  61. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/status-badges-example.png +0 -0
  62. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  63. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  64. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/setup.md +0 -0
  65. {edda_framework-0.1.0 → edda_framework-0.3.0}/docs/viewer-ui/visualization.md +0 -0
  66. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/app.py +0 -0
  67. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/compensation.py +0 -0
  68. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/context.py +0 -0
  69. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/events.py +0 -0
  70. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/exceptions.py +0 -0
  71. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/hooks.py +0 -0
  72. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/locking.py +0 -0
  73. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/outbox/__init__.py +0 -0
  74. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/outbox/relayer.py +0 -0
  75. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/outbox/transactional.py +0 -0
  76. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/pydantic_utils.py +0 -0
  77. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/replay.py +0 -0
  78. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/retry.py +0 -0
  79. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/serialization/__init__.py +0 -0
  80. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/serialization/base.py +0 -0
  81. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/serialization/json.py +0 -0
  82. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/storage/__init__.py +0 -0
  83. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/storage/models.py +0 -0
  84. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/storage/protocol.py +0 -0
  85. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/storage/sqlalchemy_storage.py +0 -0
  86. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/viewer_ui/__init__.py +0 -0
  87. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/viewer_ui/app.py +0 -0
  88. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/viewer_ui/components.py +0 -0
  89. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/viewer_ui/data_service.py +0 -0
  90. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/visualizer/__init__.py +0 -0
  91. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/visualizer/ast_analyzer.py +0 -0
  92. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/visualizer/mermaid_generator.py +0 -0
  93. {edda_framework-0.1.0 → edda_framework-0.3.0}/edda/workflow.py +0 -0
  94. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/__init__.py +0 -0
  95. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/cancellable_workflow.py +0 -0
  96. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/compensation_workflow.py +0 -0
  97. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/event_waiting_app.py +0 -0
  98. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/event_waiting_workflow.py +0 -0
  99. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/event_waiting_workflow_complete.py +0 -0
  100. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/observability_with_logfire.py +0 -0
  101. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/pydantic_saga.py +0 -0
  102. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/retry_example.py +0 -0
  103. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/retry_with_compensation.py +0 -0
  104. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/simple_workflow.py +0 -0
  105. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/typeddict_example.py +0 -0
  106. {edda_framework-0.1.0 → edda_framework-0.3.0}/examples/with_outbox.py +0 -0
  107. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/__init__.py +0 -0
  108. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/conftest.py +0 -0
  109. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_activity_retry.py +0 -0
  110. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_app.py +0 -0
  111. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_ast_analyzer.py +0 -0
  112. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_atomic_wait_event.py +0 -0
  113. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_binary_data.py +0 -0
  114. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_cloudevents_http_binding.py +0 -0
  115. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_compensation.py +0 -0
  116. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_compensation_crash_recovery.py.wip +0 -0
  117. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_concurrent_outbox.py +0 -0
  118. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_context.py +0 -0
  119. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_ctx_session.py +0 -0
  120. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_distributed_event_delivery.py +0 -0
  121. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_events.py +0 -0
  122. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_lock_race_condition.py +0 -0
  123. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_lock_timeout_customization.py +0 -0
  124. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_locking.py +0 -0
  125. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_multidb_storage.py +0 -0
  126. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_outbox.py +0 -0
  127. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_pydantic_activity.py +0 -0
  128. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_pydantic_enum.py +0 -0
  129. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_pydantic_events.py +0 -0
  130. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_pydantic_saga.py +0 -0
  131. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_pydantic_utils.py +0 -0
  132. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_received_event.py +0 -0
  133. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_replay.py +0 -0
  134. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_retry_policy.py +0 -0
  135. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_saga_parameter_extraction.py +0 -0
  136. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_serialization.py +0 -0
  137. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_skip_locked.py +0 -0
  138. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_stale_workflow_recovery.py +0 -0
  139. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_storage.py +0 -0
  140. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_storage_mysql.py +0 -0
  141. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_storage_postgresql.py +0 -0
  142. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_transactions.py +0 -0
  143. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_viewer_pydantic_form.py +0 -0
  144. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_viewer_start_saga.py +0 -0
  145. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_wait_timer.py +0 -0
  146. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_workflow.py +0 -0
  147. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_workflow_auto_register.py +0 -0
  148. {edda_framework-0.1.0 → edda_framework-0.3.0}/tests/test_workflow_cancellation.py +0 -0
  149. {edda_framework-0.1.0 → edda_framework-0.3.0}/viewer_app.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.1.0
3
+ Version: 0.3.0
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
@@ -20,7 +20,9 @@ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
21
  Classifier: Topic :: System :: Distributed Computing
22
22
  Requires-Python: >=3.11
23
+ Requires-Dist: a2wsgi>=1.10.0
23
24
  Requires-Dist: aiosqlite>=0.21.0
25
+ Requires-Dist: anyio>=4.0.0
24
26
  Requires-Dist: cloudevents>=1.12.0
25
27
  Requires-Dist: httpx>=0.28.1
26
28
  Requires-Dist: pydantic>=2.0.0
@@ -36,6 +38,8 @@ Requires-Dist: ruff>=0.14.2; extra == 'dev'
36
38
  Requires-Dist: testcontainers[mysql]>=4.0.0; extra == 'dev'
37
39
  Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
38
40
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
41
+ Provides-Extra: mcp
42
+ Requires-Dist: mcp>=1.22.0; extra == 'mcp'
39
43
  Provides-Extra: mysql
40
44
  Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
41
45
  Provides-Extra: postgresql
@@ -75,6 +79,8 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
75
79
  - 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
76
80
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
77
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
83
+ - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
78
84
 
79
85
  ## Use Cases
80
86
 
@@ -638,6 +644,94 @@ api.mount("/workflows", edda_app)
638
644
 
639
645
  This works with any ASGI framework (Starlette, FastAPI, Quart, etc.)
640
646
 
647
+ ### WSGI Integration
648
+
649
+ For WSGI environments (gunicorn, uWSGI, Flask, Django), use the WSGI adapter:
650
+
651
+ ```python
652
+ from edda import EddaApp
653
+ from edda.wsgi import create_wsgi_app
654
+
655
+ # Create Edda app
656
+ edda_app = EddaApp(db_url="sqlite:///workflow.db")
657
+
658
+ # Convert to WSGI
659
+ wsgi_application = create_wsgi_app(edda_app)
660
+ ```
661
+
662
+ **Running with WSGI servers:**
663
+
664
+ ```bash
665
+ # With Gunicorn
666
+ gunicorn demo_app:wsgi_application --workers 4
667
+
668
+ # With uWSGI
669
+ uwsgi --http :8000 --wsgi-file demo_app.py --callable wsgi_application
670
+ ```
671
+
672
+ **Sync Activities**: For WSGI environments or legacy codebases, you can write synchronous activities:
673
+
674
+ ```python
675
+ from edda import activity, WorkflowContext
676
+
677
+ @activity
678
+ def process_payment(ctx: WorkflowContext, amount: float) -> dict:
679
+ # Sync function - automatically executed in thread pool
680
+ # No async/await needed!
681
+ return {"status": "paid", "amount": amount}
682
+
683
+ @workflow
684
+ async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
685
+ # Workflows still use async (for deterministic replay)
686
+ result = await process_payment(ctx, 99.99, activity_id="pay:1")
687
+ return result
688
+ ```
689
+
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.
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
+
641
735
  ## Observability Hooks
642
736
 
643
737
  Extend Edda with custom observability without coupling to specific tools:
@@ -27,6 +27,8 @@ 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
31
+ - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
30
32
 
31
33
  ## Use Cases
32
34
 
@@ -590,6 +592,94 @@ api.mount("/workflows", edda_app)
590
592
 
591
593
  This works with any ASGI framework (Starlette, FastAPI, Quart, etc.)
592
594
 
595
+ ### WSGI Integration
596
+
597
+ For WSGI environments (gunicorn, uWSGI, Flask, Django), use the WSGI adapter:
598
+
599
+ ```python
600
+ from edda import EddaApp
601
+ from edda.wsgi import create_wsgi_app
602
+
603
+ # Create Edda app
604
+ edda_app = EddaApp(db_url="sqlite:///workflow.db")
605
+
606
+ # Convert to WSGI
607
+ wsgi_application = create_wsgi_app(edda_app)
608
+ ```
609
+
610
+ **Running with WSGI servers:**
611
+
612
+ ```bash
613
+ # With Gunicorn
614
+ gunicorn demo_app:wsgi_application --workers 4
615
+
616
+ # With uWSGI
617
+ uwsgi --http :8000 --wsgi-file demo_app.py --callable wsgi_application
618
+ ```
619
+
620
+ **Sync Activities**: For WSGI environments or legacy codebases, you can write synchronous activities:
621
+
622
+ ```python
623
+ from edda import activity, WorkflowContext
624
+
625
+ @activity
626
+ def process_payment(ctx: WorkflowContext, amount: float) -> dict:
627
+ # Sync function - automatically executed in thread pool
628
+ # No async/await needed!
629
+ return {"status": "paid", "amount": amount}
630
+
631
+ @workflow
632
+ async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
633
+ # Workflows still use async (for deterministic replay)
634
+ result = await process_payment(ctx, 99.99, activity_id="pay:1")
635
+ return result
636
+ ```
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.
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
+
593
683
  ## Observability Hooks
594
684
 
595
685
  Extend Edda with custom observability without coupling to specific tools:
@@ -1817,3 +1817,9 @@ async def scheduled_order_shipment_workflow(
1817
1817
  # Export as ASGI application
1818
1818
  # No need to manually register event handlers!
1819
1819
  application = app
1820
+
1821
+ # Export as WSGI application (for gunicorn, uWSGI, etc.)
1822
+ # Usage: gunicorn demo_app:wsgi_application --workers 4
1823
+ from edda.wsgi import create_wsgi_app
1824
+
1825
+ wsgi_application = create_wsgi_app(app)
@@ -121,6 +121,46 @@ async def create_order_with_db(ctx: WorkflowContext, order_id: str):
121
121
  return {"order_id": order_id}
122
122
  ```
123
123
 
124
+ ### Sync Activities (WSGI Compatibility)
125
+
126
+ For WSGI environments (gunicorn, uWSGI) or legacy codebases, Edda supports synchronous activities:
127
+
128
+ ```python
129
+ from edda import activity, WorkflowContext
130
+
131
+ @activity
132
+ def create_user_record(ctx: WorkflowContext, user_id: str, email: str) -> dict:
133
+ """Sync activity - executed in thread pool"""
134
+ # Traditional sync code - no async/await needed!
135
+ user = User(user_id=user_id, email=email)
136
+ db.session.add(user)
137
+ db.session.commit()
138
+ return {"user_id": user.id}
139
+
140
+ @activity
141
+ async def async_activity(ctx: WorkflowContext, data: str) -> dict:
142
+ """Async activity - recommended for I/O operations"""
143
+ result = await httpx.get(f"https://api.example.com/{data}")
144
+ return result.json()
145
+
146
+ @workflow
147
+ async def mixed_workflow(ctx: WorkflowContext, user_id: str) -> dict:
148
+ # Workflows are always async (for deterministic replay)
149
+ # But can call both sync and async activities
150
+ user = await create_user_record(ctx, user_id, "user@example.com", activity_id="create:1")
151
+ data = await async_activity(ctx, user_id, activity_id="fetch:1")
152
+ return {"user": user, "data": data}
153
+ ```
154
+
155
+ **When to use sync activities:**
156
+
157
+ - ✅ Existing sync codebases (Flask, Django)
158
+ - ✅ WSGI deployments (gunicorn, uWSGI)
159
+ - ✅ Libraries without async support
160
+ - ✅ Simple CPU-bound operations
161
+
162
+ **Performance note:** Async activities are recommended for I/O-bound operations (database queries, HTTP requests, file I/O) for better performance. Sync activities are executed in a thread pool to avoid blocking the event loop.
163
+
124
164
  ## Retry Policies
125
165
 
126
166
  Activities automatically retry on failure with exponential backoff. This provides resilience against transient failures like network timeouts or temporary service unavailability.
@@ -755,26 +795,41 @@ async def order_workflow(ctx: WorkflowContext, order_id: str, amount: float):
755
795
  pass
756
796
  ```
757
797
 
758
- ### 4. Don't Mix Async/Sync
798
+ ### 4. Choose Async or Sync Appropriately
759
799
 
760
- **Bad:**
800
+ **Preferred: Async activities** (better performance for I/O)
761
801
 
762
802
  ```python
763
803
  @activity
764
- def sync_activity(ctx: WorkflowContext, param: str): # Not async!
765
- # This won't work!
766
- pass
804
+ async def fetch_user_data(ctx: WorkflowContext, user_id: str) -> dict:
805
+ # Async I/O operations (recommended)
806
+ result = await httpx.get(f"https://api.example.com/users/{user_id}")
807
+ return result.json()
767
808
  ```
768
809
 
769
- ✅ **Good:**
810
+ ✅ **Valid: Sync activities** (WSGI compatibility, legacy code)
770
811
 
771
812
  ```python
772
813
  @activity
773
- async def async_activity(ctx: WorkflowContext, param: str): # Async
774
- # Correct!
775
- pass
814
+ def process_legacy_data(ctx: WorkflowContext, data: str) -> dict:
815
+ # Sync operations (executed in thread pool)
816
+ result = legacy_library.process(data) # No async support
817
+ return {"processed": result}
776
818
  ```
777
819
 
820
+ ✅ **Good: Mix sync and async in same workflow**
821
+
822
+ ```python
823
+ @workflow
824
+ async def order_workflow(ctx: WorkflowContext, order_id: str) -> dict:
825
+ # Both sync and async activities work fine
826
+ user = await create_user_record(ctx, order_id, activity_id="user:1") # Sync
827
+ payment = await process_payment(ctx, 99.99, activity_id="pay:1") # Async
828
+ return {"user": user, "payment": payment}
829
+ ```
830
+
831
+ **Performance tip**: Prefer async activities for I/O-bound operations (database queries, HTTP requests, file I/O). Use sync activities when integrating with legacy code or libraries without async support.
832
+
778
833
  ## Next Steps
779
834
 
780
835
  - **[Durable Execution](durable-execution/replay.md)**: Learn how Edda ensures workflows never lose progress
@@ -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.
@@ -30,6 +30,7 @@ from edda.hooks import HooksBase, WorkflowHooks
30
30
  from edda.outbox import OutboxRelayer, send_event_transactional
31
31
  from edda.retry import RetryPolicy
32
32
  from edda.workflow import workflow
33
+ from edda.wsgi import create_wsgi_app
33
34
 
34
35
  __version__ = "0.1.0"
35
36
 
@@ -53,4 +54,5 @@ __all__ = [
53
54
  "RetryPolicy",
54
55
  "RetryExhaustedError",
55
56
  "TerminalError",
57
+ "create_wsgi_app",
56
58
  ]