universal-mcp 0.1.1__tar.gz → 0.1.2rc1__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 (74) hide show
  1. universal_mcp-0.1.2rc1/.github/workflows/publish-pypi.yml +48 -0
  2. universal_mcp-0.1.2rc1/.github/workflows/pull-request-checks.yml +8 -0
  3. universal_mcp-0.1.2rc1/.github/workflows/shared.yml +54 -0
  4. universal_mcp-0.1.2rc1/.gitignore +47 -0
  5. universal_mcp-0.1.2rc1/.pre-commit-config.yaml +24 -0
  6. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/PKG-INFO +22 -5
  7. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/README.md +5 -1
  8. universal_mcp-0.1.2rc1/local_config.json.example +11 -0
  9. universal_mcp-0.1.2rc1/pyproject.toml +149 -0
  10. universal_mcp-0.1.2rc1/src/playground/README.md +65 -0
  11. universal_mcp-0.1.2rc1/src/playground/__main__.py +37 -0
  12. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/agents/react.py +3 -2
  13. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/client.py +8 -8
  14. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/schema.py +2 -1
  15. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/service.py +9 -10
  16. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/settings.py +1 -0
  17. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/streamlit.py +151 -19
  18. universal_mcp-0.1.2rc1/src/tests/test_api_generator.py +267 -0
  19. universal_mcp-0.1.2rc1/src/tests/test_applications.py +34 -0
  20. universal_mcp-0.1.2rc1/src/tests/test_localserver.py +30 -0
  21. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/tests/test_zenquotes.py +13 -10
  22. universal_mcp-0.1.2rc1/src/universal_mcp/applications/__init__.py +26 -0
  23. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/application.py +13 -8
  24. universal_mcp-0.1.2rc1/src/universal_mcp/applications/e2b/app.py +74 -0
  25. universal_mcp-0.1.2rc1/src/universal_mcp/applications/firecrawl/app.py +381 -0
  26. universal_mcp-0.1.2rc1/src/universal_mcp/applications/github/README.md +35 -0
  27. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/github/app.py +133 -100
  28. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/google_calendar/app.py +170 -139
  29. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/google_mail/app.py +185 -160
  30. universal_mcp-0.1.2rc1/src/universal_mcp/applications/markitdown/app.py +32 -0
  31. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/reddit/app.py +112 -71
  32. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/resend/app.py +3 -8
  33. universal_mcp-0.1.2rc1/src/universal_mcp/applications/serp/app.py +84 -0
  34. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/tavily/app.py +11 -10
  35. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/applications/zenquotes/app.py +3 -3
  36. universal_mcp-0.1.2rc1/src/universal_mcp/cli.py +165 -0
  37. universal_mcp-0.1.2rc1/src/universal_mcp/config.py +32 -0
  38. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/exceptions.py +1 -3
  39. universal_mcp-0.1.2rc1/src/universal_mcp/integrations/__init__.py +8 -0
  40. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/integrations/agentr.py +26 -24
  41. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/integrations/integration.py +72 -35
  42. universal_mcp-0.1.2rc1/src/universal_mcp/servers/__init__.py +23 -0
  43. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/servers/server.py +77 -44
  44. universal_mcp-0.1.2rc1/src/universal_mcp/stores/__init__.py +16 -0
  45. universal_mcp-0.1.2rc1/src/universal_mcp/stores/store.py +181 -0
  46. universal_mcp-0.1.2rc1/src/universal_mcp/utils/__init__.py +1 -0
  47. universal_mcp-0.1.2rc1/src/universal_mcp/utils/api_generator.py +269 -0
  48. universal_mcp-0.1.2rc1/src/universal_mcp/utils/docgen.py +360 -0
  49. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/utils/installation.py +17 -2
  50. universal_mcp-0.1.2rc1/src/universal_mcp/utils/openapi.py +372 -0
  51. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/uv.lock +1463 -87
  52. universal_mcp-0.1.1/.gitignore +0 -25
  53. universal_mcp-0.1.1/pyproject.toml +0 -37
  54. universal_mcp-0.1.1/src/tests/test_applications.py +0 -25
  55. universal_mcp-0.1.1/src/universal_mcp/applications/__init__.py +0 -31
  56. universal_mcp-0.1.1/src/universal_mcp/cli.py +0 -83
  57. universal_mcp-0.1.1/src/universal_mcp/config.py +0 -15
  58. universal_mcp-0.1.1/src/universal_mcp/integrations/__init__.py +0 -4
  59. universal_mcp-0.1.1/src/universal_mcp/servers/__init__.py +0 -3
  60. universal_mcp-0.1.1/src/universal_mcp/stores/__init__.py +0 -3
  61. universal_mcp-0.1.1/src/universal_mcp/stores/store.py +0 -71
  62. universal_mcp-0.1.1/src/universal_mcp/utils/openapi.py +0 -274
  63. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/.python-version +0 -0
  64. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/CONTRIBUTING.md +0 -0
  65. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/__init__.py +0 -0
  66. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/memory/__init__.py +0 -0
  67. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/memory/sqlite.py +0 -0
  68. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/playground/utils.py +0 -0
  69. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/tests/__init__.py +0 -0
  70. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/tests/conftest.py +0 -0
  71. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/__init__.py +0 -0
  72. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/integrations/README.md +0 -0
  73. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/py.typed +0 -0
  74. {universal_mcp-0.1.1 → universal_mcp-0.1.2rc1}/src/universal_mcp/utils/bridge.py +0 -0
@@ -0,0 +1,48 @@
1
+ name: Publishing
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ release-build:
9
+ name: Build distribution
10
+ runs-on: ubuntu-latest
11
+ needs: [checks]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v3
17
+ with:
18
+ enable-cache: true
19
+
20
+ - name: Build
21
+ run: uv build
22
+
23
+ - name: Upload artifacts
24
+ uses: actions/upload-artifact@v4
25
+ with:
26
+ name: release-dists
27
+ path: dist/
28
+
29
+ checks:
30
+ uses: ./.github/workflows/shared.yml
31
+
32
+ pypi-publish:
33
+ name: Upload release to PyPI
34
+ runs-on: ubuntu-latest
35
+ needs:
36
+ - release-build
37
+ permissions:
38
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
39
+
40
+ steps:
41
+ - name: Retrieve release distributions
42
+ uses: actions/download-artifact@v4
43
+ with:
44
+ name: release-dists
45
+ path: dist/
46
+
47
+ - name: Publish package distributions to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ name: Pull request checks
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ checks:
8
+ uses: ./.github/workflows/shared.yml
@@ -0,0 +1,54 @@
1
+ name: Shared Checks
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ jobs:
7
+ format:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+
12
+ - name: Install uv
13
+ uses: astral-sh/setup-uv@v3
14
+ with:
15
+ enable-cache: true
16
+
17
+ - name: Install the project
18
+ run: uv sync --frozen --all-extras --dev
19
+
20
+ - name: Run ruff format check
21
+ run: uv run --no-sync ruff check .
22
+
23
+ typecheck:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v3
30
+ with:
31
+ enable-cache: true
32
+
33
+ - name: Install the project
34
+ run: uv sync --frozen --all-extras --dev
35
+
36
+ # - name: Run pyright
37
+ # run: uv run --no-sync pyright
38
+
39
+ test:
40
+ runs-on: ubuntu-latest
41
+
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - name: Install uv
46
+ uses: astral-sh/setup-uv@v3
47
+ with:
48
+ enable-cache: true
49
+
50
+ - name: Install the project
51
+ run: uv sync --frozen --all-extras --dev
52
+
53
+ - name: Run pytest
54
+ run: uv run --no-sync pytest
@@ -0,0 +1,47 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Environment variables
13
+ .env
14
+ .env.local
15
+
16
+
17
+ dump/
18
+
19
+
20
+ # SQLite database
21
+ *.db
22
+ *.sqlite
23
+ *.sqlite3
24
+ *.db-shm
25
+ *.db-wal
26
+
27
+
28
+ # Pyright
29
+ .pyright
30
+
31
+ # Ruff
32
+ .ruff_cache
33
+
34
+ # Pytest
35
+ .pytest_cache
36
+
37
+
38
+ # Local config
39
+ local_config.json
40
+
41
+
42
+ # Test Artifacts
43
+ test_artifacts/
44
+ src/universal_mcp/applications/complex/
45
+ src/universal_mcp/applications/test/
46
+ src/universal_mcp/applications/test_with_docs/
47
+ src/universal_mcp/applications/test_without_docs/
@@ -0,0 +1,24 @@
1
+ fail_fast: true
2
+
3
+ repos:
4
+ - repo: https://github.com/pre-commit/mirrors-prettier
5
+ rev: v3.1.0
6
+ hooks:
7
+ - id: prettier
8
+ types_or: [yaml, json5]
9
+
10
+ - repo: https://github.com/astral-sh/ruff-pre-commit
11
+ rev: v0.8.1
12
+ hooks:
13
+ - id: ruff-format
14
+ - id: ruff
15
+ args: [--fix, --exit-non-zero-on-fix]
16
+
17
+ - repo: local
18
+ hooks:
19
+ - id: uv-lock-check
20
+ name: Check uv.lock is up to date
21
+ entry: uv lock --check
22
+ language: system
23
+ files: ^(pyproject\.toml|uv\.lock)$
24
+ pass_filenames: false
@@ -1,25 +1,38 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.1
3
+ Version: 0.1.2rc1
4
4
  Summary: Universal MCP acts as a middle ware for your API applications. It can store your credentials, authorize, enable disable apps on the fly and much more.
5
5
  Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
6
6
  Requires-Python: >=3.11
7
+ Requires-Dist: keyring>=25.6.0
8
+ Requires-Dist: litellm>=1.30.7
7
9
  Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: markitdown[all]>=0.1.1
8
11
  Requires-Dist: mcp>=1.5.0
9
12
  Requires-Dist: pydantic-settings>=2.8.1
10
13
  Requires-Dist: pydantic>=2.11.1
11
14
  Requires-Dist: pyyaml>=6.0.2
12
15
  Requires-Dist: typer>=0.15.2
16
+ Provides-Extra: dev
17
+ Requires-Dist: litellm>=1.30.7; extra == 'dev'
18
+ Requires-Dist: pyright>=1.1.398; extra == 'dev'
19
+ Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
20
+ Requires-Dist: pytest>=8.3.5; extra == 'dev'
21
+ Requires-Dist: ruff>=0.11.4; extra == 'dev'
22
+ Provides-Extra: e2b
23
+ Requires-Dist: e2b-code-interpreter>=1.2.0; extra == 'e2b'
24
+ Provides-Extra: firecrawl
25
+ Requires-Dist: firecrawl-py>=1.15.0; extra == 'firecrawl'
13
26
  Provides-Extra: playground
14
27
  Requires-Dist: fastapi[standard]>=0.115.12; extra == 'playground'
15
28
  Requires-Dist: langchain-anthropic>=0.3.10; extra == 'playground'
16
29
  Requires-Dist: langchain-mcp-adapters>=0.0.3; extra == 'playground'
17
30
  Requires-Dist: langgraph-checkpoint-sqlite>=2.0.6; extra == 'playground'
18
31
  Requires-Dist: langgraph>=0.3.24; extra == 'playground'
32
+ Requires-Dist: python-dotenv>=1.0.1; extra == 'playground'
19
33
  Requires-Dist: streamlit>=1.44.1; extra == 'playground'
20
- Provides-Extra: test
21
- Requires-Dist: pytest-asyncio>=0.26.0; extra == 'test'
22
- Requires-Dist: pytest>=8.3.5; extra == 'test'
34
+ Provides-Extra: serpapi
35
+ Requires-Dist: google-search-results>=2.4.2; extra == 'serpapi'
23
36
  Description-Content-Type: text/markdown
24
37
 
25
38
  # Universal MCP
@@ -166,7 +179,11 @@ AgentR includes a command-line interface for common operations:
166
179
  agentr version
167
180
 
168
181
  # Generate API client from OpenAPI schema
169
- agentr generate --schema path/to/openapi.yaml
182
+
183
+ # Use the name of the API as the output filename (e.g., twitter, petstore, github)
184
+ universal_mcp generate --schema petstore.json --output outputfilename
185
+
186
+ # The tool will create src/universal_mcp/applications/petstore/ with app.py and README.md
170
187
 
171
188
  # Run the test server
172
189
  agentr run
@@ -142,7 +142,11 @@ AgentR includes a command-line interface for common operations:
142
142
  agentr version
143
143
 
144
144
  # Generate API client from OpenAPI schema
145
- agentr generate --schema path/to/openapi.yaml
145
+
146
+ # Use the name of the API as the output filename (e.g., twitter, petstore, github)
147
+ universal_mcp generate --schema petstore.json --output outputfilename
148
+
149
+ # The tool will create src/universal_mcp/applications/petstore/ with app.py and README.md
146
150
 
147
151
  # Run the test server
148
152
  agentr run
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Local Server",
3
+ "description": "Local server for testing",
4
+ "type": "local",
5
+ "transport": "sse",
6
+ "apps": [
7
+ {
8
+ "name": "markitdown"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,149 @@
1
+ [project]
2
+ name = "universal-mcp"
3
+ version = "0.1.2rc1"
4
+ description = "Universal MCP acts as a middle ware for your API applications. It can store your credentials, authorize, enable disable apps on the fly and much more."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Manoj Bajaj", email = "manojbajaj95@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "pydantic>=2.11.1",
12
+ "pydantic-settings>=2.8.1",
13
+ "loguru>=0.7.3",
14
+ "mcp>=1.5.0",
15
+ "pyyaml>=6.0.2",
16
+ "typer>=0.15.2",
17
+ "markitdown[all]>=0.1.1",
18
+ "keyring>=25.6.0",
19
+ "litellm>=1.30.7",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ playground = [
24
+ "fastapi[standard]>=0.115.12",
25
+ "langchain-anthropic>=0.3.10",
26
+ "langchain-mcp-adapters>=0.0.3",
27
+ "langgraph>=0.3.24",
28
+ "langgraph-checkpoint-sqlite>=2.0.6",
29
+ "streamlit>=1.44.1",
30
+ "python-dotenv>=1.0.1",
31
+ ]
32
+ dev = [
33
+ "ruff>=0.11.4",
34
+ "pytest>=8.3.5",
35
+ "pytest-asyncio>=0.26.0",
36
+ "pyright>=1.1.398",
37
+ "litellm>=1.30.7",
38
+ ]
39
+ e2b = [
40
+ "e2b-code-interpreter>=1.2.0",
41
+ ]
42
+ firecrawl=[
43
+ "firecrawl-py>=1.15.0",
44
+ ]
45
+ serpapi = [
46
+ "google-search-results>=2.4.2",
47
+ ]
48
+ [project.scripts]
49
+ universal_mcp = "universal_mcp.cli:app"
50
+
51
+ [build-system]
52
+ requires = ["hatchling"]
53
+ build-backend = "hatchling.build"
54
+
55
+ [tool.ruff]
56
+ # Exclude a variety of commonly ignored directories.
57
+ exclude = [
58
+ ".bzr",
59
+ ".direnv",
60
+ ".eggs",
61
+ ".git",
62
+ ".git-rewrite",
63
+ ".hg",
64
+ ".ipynb_checkpoints",
65
+ ".mypy_cache",
66
+ ".nox",
67
+ ".pants.d",
68
+ ".pyenv",
69
+ ".pytest_cache",
70
+ ".pytype",
71
+ ".ruff_cache",
72
+ ".svn",
73
+ ".tox",
74
+ ".venv",
75
+ ".vscode",
76
+ "__pypackages__",
77
+ "_build",
78
+ "buck-out",
79
+ "build",
80
+ "dist",
81
+ "node_modules",
82
+ "site-packages",
83
+ "venv",
84
+ ]
85
+
86
+ # Same as Black.
87
+ line-length = 88
88
+ indent-width = 4
89
+
90
+ # Assume Python 3.12
91
+ target-version = "py312"
92
+
93
+ [tool.ruff.lint]
94
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
95
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
96
+ # McCabe complexity (`C901`) by default.
97
+ select = [
98
+ # pycodestyle
99
+ "E",
100
+ # Pyflakes
101
+ "F",
102
+ # pyupgrade
103
+ "UP",
104
+ # flake8-bugbear
105
+ "B",
106
+ # flake8-simplify
107
+ "SIM",
108
+ # isort
109
+ "I",
110
+ ]
111
+ ignore = ['E501', 'B008']
112
+
113
+ # Allow fix for all enabled rules (when `--fix`) is provided.
114
+ fixable = ["ALL"]
115
+ unfixable = []
116
+
117
+ # Allow unused variables when underscore-prefixed.
118
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
119
+
120
+ [tool.ruff.format]
121
+ # Like Black, use double quotes for strings.
122
+ quote-style = "double"
123
+
124
+ # Like Black, indent with spaces, rather than tabs.
125
+ indent-style = "space"
126
+
127
+ # Like Black, respect magic trailing commas.
128
+ skip-magic-trailing-comma = false
129
+
130
+ # Like Black, automatically detect the appropriate line ending.
131
+ line-ending = "auto"
132
+
133
+ # Enable auto-formatting of code examples in docstrings. Markdown,
134
+ # reStructuredText code/literal blocks and doctests are all supported.
135
+ #
136
+ # This is currently disabled by default, but it is planned for this
137
+ # to be opt-out in the future.
138
+ docstring-code-format = false
139
+
140
+ # Set the line length limit used when formatting code snippets in
141
+ # docstrings.
142
+ #
143
+ # This only has an effect when the `docstring-code-format` setting is
144
+ # enabled.
145
+ docstring-code-line-length = "dynamic"
146
+
147
+ [tool.pytest.ini_options]
148
+ asyncio_mode = "strict"
149
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,65 @@
1
+ ## Running the Playground
2
+
3
+ ### Automated Startup (Recommended)
4
+
5
+ To easily start all necessary services (MCP Server, FastAPI Backend, Streamlit Frontend), you can use the provided Python startup script. This script will attempt to open each service in sequence and ensures they are terminated gracefully upon interruption.
6
+
7
+ **Prerequisites:**
8
+
9
+ * Python 3 installed and available in your PATH.
10
+ * All project dependencies installed (ensure you've run `pip install ...` for all requirements).
11
+ * **`local_config.json` file:** This configuration file for the MCP server must exist in the **project root directory**. It should contain a JSON array defining the MCP tools to load. For example:
12
+ ```json
13
+ {
14
+ "name": "Local Server",
15
+ "description": "Local server for testing",
16
+ "type": "local",
17
+ "transport": "sse",
18
+ "apps": [
19
+ {
20
+ "name": "zenquotes"
21
+ }
22
+ ]
23
+ }
24
+ ```
25
+ *(Adapt the content based on the actual MCP tools you intend to use.)*
26
+ * A compatible terminal environment for the script:
27
+ * On **macOS**, requires the standard `Terminal.app`.
28
+ * On **Linux**, assumes `gnome-terminal` is available (common on Ubuntu/Fedora). You may need to modify `src/playground/__main__.py` if you use a different terminal like `konsole` or `xfce4-terminal`.
29
+ * On **Windows**, requires the standard `cmd.exe`.
30
+
31
+ **Instructions:**
32
+
33
+ 1. Open your terminal application.
34
+ 2. Navigate (`cd`) to the **root directory** of this project (the directory containing the `src/` folder and `local_config.json`).
35
+ 3. Execute the startup script using Python:
36
+
37
+ ```bash
38
+ python src/playground
39
+ ```
40
+
41
+ *(Note: If your system uses `python3` to invoke Python 3, use that command instead: `python3 src/playground`)*
42
+
43
+ After running the command, you should see messages indicating that the components are being launched.
44
+
45
+ ### Manual Startup (Alternative)
46
+
47
+ If you prefer, or if the automated script does not work on your specific system configuration, you can still start each component manually. Make sure you run each command from the **project root directory** in a separate terminal window:
48
+
49
+ 1. **Terminal 1 (MCP Server):**
50
+ *(Ensure `local_config.json` exists in the project root as described above)*
51
+ ```bash
52
+ universal_mcp run -c local_config.json
53
+ ```
54
+ 2. **Terminal 2 (FastAPI App):**
55
+ ```bash
56
+ fastapi run src/playground
57
+ ```
58
+ 3. **Terminal 3 (Streamlit App):**
59
+ ```bash
60
+ streamlit run src/playground/streamlit.py
61
+ ```
62
+
63
+ ### Stopping the Services
64
+
65
+ To stop the running services, go to each of the terminal windows that were opened (either by the script or manually) and press `Ctrl + C`.
@@ -0,0 +1,37 @@
1
+ import subprocess
2
+ import time
3
+
4
+ from loguru import logger
5
+
6
+
7
+ def main():
8
+ processes = []
9
+
10
+ # Start MCP server first
11
+ mcp_process = subprocess.Popen(["universal_mcp", "run", "-c", "local_config.json"])
12
+ processes.append(mcp_process)
13
+ time.sleep(3) # Give MCP server time to start
14
+ logger.info("MCP server started")
15
+
16
+ # Start FastAPI app second
17
+ fastapi_process = subprocess.Popen(["fastapi", "run", "src/playground"])
18
+ processes.append(fastapi_process)
19
+ time.sleep(3) # Give FastAPI time to start
20
+ logger.info("FastAPI app started")
21
+ # Start Streamlit app last
22
+ streamlit_process = subprocess.Popen(
23
+ ["streamlit", "run", "src/playground/streamlit.py"]
24
+ )
25
+ processes.append(streamlit_process)
26
+ logger.info("Streamlit app started")
27
+ try:
28
+ for p in processes:
29
+ p.wait()
30
+ except KeyboardInterrupt:
31
+ for p in processes:
32
+ p.terminate()
33
+ p.wait()
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
@@ -1,9 +1,10 @@
1
+ import asyncio
1
2
  from contextlib import asynccontextmanager
3
+
2
4
  from langchain_anthropic import ChatAnthropic
3
- from langgraph.prebuilt import create_react_agent
4
5
  from langchain_core.messages import HumanMessage
5
- import asyncio
6
6
  from langchain_mcp_adapters.client import MultiServerMCPClient
7
+ from langgraph.prebuilt import create_react_agent
7
8
 
8
9
 
9
10
  @asynccontextmanager
@@ -56,7 +56,7 @@ class AgentClient:
56
56
  )
57
57
  response.raise_for_status()
58
58
  except httpx.HTTPError as e:
59
- raise AgentClientError(f"Error getting service info: {e}")
59
+ raise AgentClientError(f"Error getting service info: {e}") from e
60
60
 
61
61
  async def ainvoke(
62
62
  self,
@@ -92,7 +92,7 @@ class AgentClient:
92
92
  )
93
93
  response.raise_for_status()
94
94
  except httpx.HTTPError as e:
95
- raise AgentClientError(f"Error: {e}")
95
+ raise AgentClientError(f"Error: {e}") from e
96
96
 
97
97
  return ChatMessage.model_validate(response.json())
98
98
 
@@ -131,7 +131,7 @@ class AgentClient:
131
131
  )
132
132
  response.raise_for_status()
133
133
  except httpx.HTTPError as e:
134
- raise AgentClientError(f"Error: {e}")
134
+ raise AgentClientError(f"Error: {e}") from e
135
135
 
136
136
  return ChatMessage.model_validate(response.json())
137
137
 
@@ -144,14 +144,14 @@ class AgentClient:
144
144
  try:
145
145
  parsed = json.loads(data)
146
146
  except Exception as e:
147
- raise Exception(f"Error JSON parsing message from server: {e}")
147
+ raise Exception(f"Error JSON parsing message from server: {e}") from e
148
148
  match parsed["type"]:
149
149
  case "message":
150
150
  # Convert the JSON formatted message to an AnyMessage
151
151
  try:
152
152
  return ChatMessage.model_validate(parsed["content"])
153
153
  except Exception as e:
154
- raise Exception(f"Server returned invalid message: {e}")
154
+ raise Exception(f"Server returned invalid message: {e}") from e
155
155
  case "token":
156
156
  # Yield the str token directly
157
157
  return parsed["content"]
@@ -208,7 +208,7 @@ class AgentClient:
208
208
  break
209
209
  yield parsed
210
210
  except httpx.HTTPError as e:
211
- raise AgentClientError(f"Error: {e}")
211
+ raise AgentClientError(f"Error: {e}") from e
212
212
 
213
213
  async def astream(
214
214
  self,
@@ -261,7 +261,7 @@ class AgentClient:
261
261
  break
262
262
  yield parsed
263
263
  except httpx.HTTPError as e:
264
- raise AgentClientError(f"Error: {e}")
264
+ raise AgentClientError(f"Error: {e}") from e
265
265
 
266
266
  def get_history(
267
267
  self,
@@ -283,6 +283,6 @@ class AgentClient:
283
283
  )
284
284
  response.raise_for_status()
285
285
  except httpx.HTTPError as e:
286
- raise AgentClientError(f"Error: {e}")
286
+ raise AgentClientError(f"Error: {e}") from e
287
287
 
288
288
  return ChatHistory.model_validate(response.json())
@@ -1,5 +1,6 @@
1
+ from typing import Any, Literal, NotRequired, TypedDict
2
+
1
3
  from pydantic import BaseModel, Field
2
- from typing import Any, Literal, TypedDict, NotRequired
3
4
 
4
5
 
5
6
  class UserInput(BaseModel):
@@ -11,6 +11,7 @@ from langchain_core._api import LangChainBetaWarning
11
11
  from langchain_core.messages import AnyMessage, HumanMessage
12
12
  from langchain_core.runnables import RunnableConfig
13
13
  from langgraph.types import Command
14
+ from loguru import logger
14
15
 
15
16
  from playground.agents.react import create_agent
16
17
  from playground.memory import initialize_database
@@ -26,7 +27,6 @@ from playground.utils import (
26
27
  langchain_to_chat_message,
27
28
  remove_tool_calls,
28
29
  )
29
- from loguru import logger
30
30
 
31
31
  warnings.filterwarnings("ignore", category=LangChainBetaWarning)
32
32
 
@@ -40,13 +40,12 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
40
40
  Configurable lifespan that initializes the appropriate database checkpointer based on settings.
41
41
  """
42
42
  try:
43
- async with create_agent() as react_agent:
44
- async with initialize_database() as saver:
45
- await saver.setup()
46
- global agent
47
- agent = react_agent
48
- agent.checkpointer = saver
49
- yield
43
+ async with create_agent() as react_agent, initialize_database() as saver:
44
+ await saver.setup()
45
+ global agent
46
+ agent = react_agent
47
+ agent.checkpointer = saver
48
+ yield
50
49
  except Exception as e:
51
50
  logger.error(f"Error during database initialization: {e}")
52
51
  raise
@@ -102,7 +101,7 @@ async def invoke(user_input: UserInput) -> ChatMessage:
102
101
  return output
103
102
  except Exception as e:
104
103
  logger.error(f"An exception occurred: {e}")
105
- raise HTTPException(status_code=500, detail="Unexpected error")
104
+ raise HTTPException(status_code=500, detail="Unexpected error") from e
106
105
 
107
106
 
108
107
  async def message_generator(user_input: StreamInput) -> AsyncGenerator[str, None]:
@@ -223,7 +222,7 @@ def history(input: ChatHistoryInput) -> ChatHistory:
223
222
  return ChatHistory(messages=chat_messages)
224
223
  except Exception as e:
225
224
  logger.error(f"An exception occurred: {e}")
226
- raise HTTPException(status_code=500, detail="Unexpected error")
225
+ raise HTTPException(status_code=500, detail="Unexpected error") from e
227
226
 
228
227
 
229
228
  @app.get("/health")
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+
2
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
3
4
 
4
5