mcp-modal 0.2.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.
@@ -0,0 +1,174 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sajid Mehmood
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-modal
3
+ Version: 0.2.0
4
+ Summary: MCP server for managing Modal (modal.com) apps, containers, volumes, and secrets.
5
+ Project-URL: Homepage, https://github.com/george-bobby/mcp-modal
6
+ Project-URL: Repository, https://github.com/george-bobby/mcp-modal
7
+ Author: George Bobby
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude,mcp,modal,model-context-protocol
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: mcp>=1.9.2
13
+ Requires-Dist: modal>=1.0
14
+ Requires-Dist: pydantic>=2.10.6
15
+ Requires-Dist: uv>=0.6.9
16
+ Description-Content-Type: text/markdown
17
+
18
+ <!-- mcp-name: io.github.george-bobby/mcp-modal -->
19
+
20
+ # MCP Modal Server
21
+
22
+ An MCP server for managing [Modal](https://modal.com) — apps, containers, volumes, and secrets — and for deploying & running Modal apps directly from [Claude Code](https://docs.claude.com/en/docs/claude-code) and other MCP clients.
23
+
24
+ > **Heads up:** This is for [Modal](https://modal.com), the serverless cloud platform for running code on GPUs/CPUs — not any other product named "Modal".
25
+
26
+ Every tool shells out to your local `modal` CLI, so it operates against whatever Modal profile and credentials are configured on your machine. There are no extra tokens to manage.
27
+
28
+ ## Installation
29
+
30
+ 1. Clone this repository:
31
+ ```bash
32
+ git clone https://github.com/george-bobby/mcp-modal.git
33
+ cd mcp-modal
34
+ ```
35
+
36
+ 2. Install dependencies using [`uv`](https://docs.astral.sh/uv/):
37
+ ```bash
38
+ uv sync
39
+ ```
40
+
41
+ ## Logging in to Modal
42
+
43
+ This server uses your local Modal credentials. If you haven't authenticated yet, run:
44
+
45
+ ```bash
46
+ modal setup
47
+ ```
48
+
49
+ This opens a browser to log in and stores a token in `~/.modal.toml`. Already logged in elsewhere? Check with `modal profile current`.
50
+
51
+ ## Configuration
52
+
53
+ Add the server to Claude Code with the `claude mcp` CLI:
54
+
55
+ ```bash
56
+ claude mcp add mcp-modal -- \
57
+ uv --project /path/to/mcp-modal run /path/to/mcp-modal/src/mcp_modal/server.py
58
+ ```
59
+
60
+ Or add it to a `.mcp.json` file in your project root:
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "mcp-modal": {
66
+ "command": "uv",
67
+ "args": [
68
+ "--project", "/path/to/mcp-modal",
69
+ "run", "/path/to/mcp-modal/src/mcp_modal/server.py"
70
+ ]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ Replace `/path/to/mcp-modal` with the absolute path to your cloned repository.
77
+
78
+ ## Requirements
79
+
80
+ - Python 3.11 or higher
81
+ - `uv` package manager
82
+ - Modal CLI 1.x configured with valid credentials (`modal setup`)
83
+ - For Modal **deploy** and **run** support:
84
+ - The project being deployed/run must use `uv` for dependency management
85
+ - `modal` must be installed in that project's virtual environment
86
+
87
+ ## Supported Tools
88
+
89
+ 26 tools, grouped by area. Account-scoped tools accept an optional `env` argument to
90
+ target a specific [Modal environment](https://modal.com/docs/guide/environments); if
91
+ omitted, they use the profile's default (or `MODAL_ENVIRONMENT`).
92
+
93
+ ### Deploy & Run
94
+
95
+ 1. **Deploy Modal App** (`deploy_modal_app`)
96
+ - Deploys a Modal app (`modal deploy`). Deployed web endpoints persist, so any links
97
+ in the output are live and shareable (returned in `urls`).
98
+ - Parameters: `absolute_path_to_app` (required), `env`, `name`, `tag`,
99
+ `strategy` (`rolling`/`recreate`), `stream_logs`
100
+ - The app's directory must use `uv` with `modal` installed in its virtualenv.
101
+
102
+ 2. **Run Modal App** (`run_modal_app`)
103
+ - Runs a function or local entrypoint once and streams its output (`modal run`).
104
+ - Parameters: `absolute_path_to_app` (required), `function_name`, `env`,
105
+ `detach`, `timeout_seconds` (default 120)
106
+ - Returns a snapshot with `truncated: true` if the run is still going at the timeout.
107
+ Pass `detach=True` to keep long jobs alive on Modal past the timeout.
108
+
109
+ > **Why no `modal serve` tool?** `modal serve` only keeps its endpoints alive while the
110
+ > blocking process runs — an MCP tool that returns would tear them down immediately,
111
+ > handing back a dead URL. Use `deploy_modal_app` for a persistent, shareable endpoint.
112
+
113
+ ### Apps
114
+
115
+ 3. **List Modal Apps** (`list_modal_apps`)
116
+ - Lists apps currently deployed/running or recently stopped. Use this to find the
117
+ app name/ID for the other app tools.
118
+ - Parameters: `env`
119
+
120
+ 4. **Get Modal App Logs** (`get_modal_app_logs`)
121
+ - Fetches or streams logs for an app by name or ID (`modal app logs`).
122
+ - Parameters: `app_identifier` (required), `timeout_seconds` (default 30), `env`,
123
+ `since`, `until`, `tail`, `search`, `source` (`stdout`/`stderr`/`system`), `follow`
124
+ - With `follow=True`, logs stream until the app stops or `timeout_seconds` is reached,
125
+ returning a snapshot with `truncated: true`.
126
+
127
+ 5. **Stop Modal App** (`stop_modal_app`)
128
+ - Permanently stops an app and terminates its containers (`modal app stop`).
129
+ - Parameters: `app_identifier` (required), `env`
130
+
131
+ 6. **Roll Back Modal App** (`rollback_modal_app`)
132
+ - Redeploys a previous version of an app (`modal app rollback`).
133
+ - Parameters: `app_identifier` (required), `version` (optional — defaults to the
134
+ previous version), `env`
135
+
136
+ 7. **Get Modal App History** (`get_modal_app_history`)
137
+ - Returns an app's deployment history (`modal app history`). Use it to find a
138
+ `version` for rollback.
139
+ - Parameters: `app_identifier` (required), `env`
140
+
141
+ ### Containers
142
+
143
+ 8. **List Modal Containers** (`list_modal_containers`)
144
+ - Lists currently running containers (`modal container list`).
145
+ - Parameters: `app_id` (optional filter), `env`
146
+
147
+ 9. **Get Modal Container Logs** (`get_modal_container_logs`)
148
+ - Fetches or streams logs for a container ID (`modal container logs`).
149
+ - Parameters: `container_id` (required), `timeout_seconds` (default 30),
150
+ `since`, `until`, `tail`, `search`, `source`, `follow`
151
+
152
+ 10. **Exec in Modal Container** (`exec_modal_container`)
153
+ - Runs a command inside a running container (`modal container exec --no-pty`).
154
+ - Parameters: `container_id` (required), `command` (list of args, e.g.
155
+ `["python", "-c", "print('hi')"]`), `timeout_seconds` (default 60)
156
+
157
+ 11. **Stop Modal Container** (`stop_modal_container`)
158
+ - Terminates a running container (`modal container stop`).
159
+ - Parameters: `container_id` (required)
160
+
161
+ ### Log Search
162
+
163
+ 12. **Search Modal Logs** (`search_modal_logs`)
164
+ - Greps an app's or container's logs for a pattern and returns each hit **with the
165
+ surrounding lines** — built for "where did it go wrong?" debugging. Logs are fetched
166
+ once and searched locally, so (unlike the `search` argument on the log tools) you get
167
+ context, regex, case control, and match counts, not just the bare matching line.
168
+ - Parameters: `identifier` (required — app name/ID or container ID), `pattern` (required),
169
+ `target` (`app`/`container`, default `app`), `regex`, `case_sensitive`,
170
+ `context_lines` (default 3), `max_matches` (default 50), `since`, `tail`
171
+ (defaults to the last 1000 entries), `timeout_seconds`, `env`
172
+ - Returns `match_count` and `matches`: line-numbered context blocks where matched lines
173
+ are prefixed with `>`, e.g. `> 8: ValueError: bad input`.
174
+
175
+ ### Volumes — Files
176
+
177
+ 13. **List Modal Volumes** (`list_modal_volumes`) — lists all volumes. Parameters: none.
178
+ 14. **List Volume Contents** (`list_modal_volume_contents`) — `volume_name`, `path` (default `/`).
179
+ 15. **Copy Files** (`copy_modal_volume_files`) — `volume_name`, `paths` (last is destination).
180
+ 16. **Remove File** (`remove_modal_volume_file`) — `volume_name`, `remote_path`, `recursive`.
181
+ 17. **Upload File** (`put_modal_volume_file`) — `volume_name`, `local_path`, `remote_path`, `force`.
182
+ 18. **Download File** (`get_modal_volume_file`) — `volume_name`, `remote_path`, `local_destination`, `force`. Use `-` as the destination to stream contents to stdout.
183
+
184
+ ### Volumes — Lifecycle
185
+
186
+ 19. **Create Volume** (`create_modal_volume`) — creates a named persistent volume. Parameters: `volume_name`, `env`.
187
+ 20. **Delete Volume** (`delete_modal_volume`) — deletes a volume **and all its data** (irreversible). Parameters: `volume_name`, `env`.
188
+ 21. **Rename Volume** (`rename_modal_volume`) — Parameters: `old_name`, `new_name`, `env`.
189
+
190
+ ### Secrets
191
+
192
+ 22. **List Secrets** (`list_modal_secrets`)
193
+ - Lists published secrets (names and timestamps only — values are never exposed).
194
+ - Parameters: `env`
195
+
196
+ 23. **Create Secret** (`create_modal_secret`)
197
+ - Creates a secret from inline key/values or a local file (`modal secret create`).
198
+ Secret values are **redacted** from the returned `command`.
199
+ - Parameters: `secret_name` (required), `key_values` (dict), `from_dotenv` (path),
200
+ `from_json` (path), `force`, `env`. Provide at least one of `key_values`,
201
+ `from_dotenv`, or `from_json`.
202
+
203
+ 24. **Delete Secret** (`delete_modal_secret`) — Parameters: `secret_name`, `env`.
204
+
205
+ ### Discovery
206
+
207
+ 25. **Get Modal Profile** (`get_modal_profile`)
208
+ - Shows the active profile and all configured profiles. Use it to confirm which
209
+ workspace/account the server is authenticated as. Parameters: none.
210
+
211
+ 26. **List Modal Environments** (`list_modal_environments`)
212
+ - Lists the environments in the current workspace; the names are valid `env`
213
+ arguments for the other tools. Parameters: none.
214
+
215
+ ## Response Format
216
+
217
+ All tools return responses in a standardized format, with slight variations depending on the operation type:
218
+
219
+ ```python
220
+ # JSON / list operations (apps, containers, volumes, secrets, history, ...):
221
+ {
222
+ "success": True,
223
+ "apps": [...] # or "containers", "volumes", "secrets", "history", "environments"
224
+ }
225
+
226
+ # Action operations (deploy, stop, create, delete, rename, copy, put, get, rm):
227
+ {
228
+ "success": True,
229
+ "message": "Operation successful message",
230
+ "command": "executed command string",
231
+ "stdout": "command output", # if any
232
+ "stderr": "error output" # if any
233
+ }
234
+
235
+ # Log / run / exec operations (snapshot-based):
236
+ {
237
+ "success": True,
238
+ "logs": "...", # or "output" for run/exec
239
+ "truncated": False, # True when cut off at timeout_seconds
240
+ "command": "executed command string"
241
+ }
242
+
243
+ # Error case (all operations):
244
+ {
245
+ "success": False,
246
+ "error": "Error message describing what went wrong",
247
+ "command": "executed command string",
248
+ "stdout": "command output", # if available
249
+ "stderr": "error output" # if available
250
+ }
251
+ ```
252
+
253
+ ## Publishing to the MCP Registry
254
+
255
+ This repo ships a [`server.json`](server.json) following the [MCP Registry](https://github.com/modelcontextprotocol/registry) spec, and the README carries the `mcp-name:` ownership marker required for PyPI verification. To publish a new version:
256
+
257
+ ```bash
258
+ # 1. Build and publish the package to PyPI (the registry only hosts metadata)
259
+ uv build
260
+ uv publish
261
+
262
+ # 2. Publish the server entry to the MCP Registry
263
+ mcp-publisher login github # GitHub auth for the io.github.george-bobby/* namespace
264
+ mcp-publisher publish
265
+ ```
266
+
267
+ Bump the `version` in both [`pyproject.toml`](pyproject.toml) and [`server.json`](server.json) together — they must match.
268
+
269
+ ## Contributing
270
+
271
+ Contributions are welcome! Please feel free to submit a Pull Request.
272
+
273
+ ## License
274
+
275
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.