Skip to content

Commit 4318900

Browse files
filiplajszczakcaseneuveleec1979
committed
initial commit
Co-authored-by: Piotr Kaznowski <[email protected]> Co-authored-by: Lee Cartwright <[email protected]>
0 parents  commit 4318900

24 files changed

+1606
-0
lines changed

.dxtignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
# Pytest cache
13+
.pytest_cache/
14+
15+
# Intellij IDEA files
16+
.idea
17+

.github/workflows/release.yml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*.*.*'
7+
workflow_dispatch:
8+
9+
jobs:
10+
test:
11+
uses: ./.github/workflows/test.yml
12+
13+
build_package:
14+
runs-on: ubuntu-latest
15+
needs: test
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.11'
24+
25+
- name: Install uv
26+
uses: astral-sh/setup-uv@v3
27+
28+
- name: Check tag matches pyproject.toml version
29+
run: |
30+
TAG_VERSION="${GITHUB_REF##*/}"
31+
TAG_VERSION_NO_PREFIX="${TAG_VERSION#v}"
32+
echo "Tag version: $TAG_VERSION (stripped: $TAG_VERSION_NO_PREFIX)"
33+
PYPROJECT_VERSION=$(grep '^version =' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
34+
echo "pyproject.toml version: $PYPROJECT_VERSION"
35+
if [ "$TAG_VERSION_NO_PREFIX" != "$PYPROJECT_VERSION" ]; then
36+
echo "Tag version ($TAG_VERSION_NO_PREFIX) does not match pyproject.toml version ($PYPROJECT_VERSION)" >&2
37+
exit 1
38+
fi
39+
shell: bash
40+
41+
- name: Build package
42+
run: uv build
43+
44+
- name: Publish to PyPI
45+
uses: pypa/gh-action-pypi-publish@release/v1
46+
with:
47+
user: __token__
48+
password: ${{ secrets.PYPI_API_TOKEN }}
49+
50+
- name: Upload Python artifacts
51+
uses: actions/upload-artifact@v4
52+
with:
53+
name: python-dist
54+
path: dist/
55+
56+
build_extension:
57+
runs-on: ubuntu-latest
58+
needs: test
59+
steps:
60+
- name: Checkout code
61+
uses: actions/checkout@v4
62+
63+
- name: Set up Node.js
64+
uses: actions/setup-node@v4
65+
with:
66+
node-version: '18'
67+
68+
- name: Check tag matches manifest.json version
69+
run: |
70+
TAG_VERSION="${GITHUB_REF##*/}"
71+
TAG_VERSION_NO_PREFIX="${TAG_VERSION#v}"
72+
echo "Tag version: $TAG_VERSION (stripped: $TAG_VERSION_NO_PREFIX)"
73+
MANIFEST_VERSION=$(jq -r .version manifest.json)
74+
echo "manifest.json version: $MANIFEST_VERSION"
75+
if [ "$TAG_VERSION_NO_PREFIX" != "$MANIFEST_VERSION" ]; then
76+
echo "Tag version ($TAG_VERSION_NO_PREFIX) does not match manifest.json version ($MANIFEST_VERSION)" >&2
77+
exit 1
78+
fi
79+
shell: bash
80+
81+
- name: Install DXT toolchain
82+
run: npm install -g @anthropic-ai/dxt
83+
84+
- name: Pack extension
85+
run: dxt pack
86+
87+
- name: Upload DXT artifacts
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: dxt-dist
91+
path: '*.dxt'
92+
93+
create_release:
94+
runs-on: ubuntu-latest
95+
needs: [build_package, build_extension]
96+
steps:
97+
- name: Download Python artifacts
98+
uses: actions/download-artifact@v4
99+
with:
100+
name: python-dist
101+
path: dist/
102+
103+
- name: Download DXT artifacts
104+
uses: actions/download-artifact@v4
105+
with:
106+
name: dxt-dist
107+
path: ./
108+
109+
- name: Create Release
110+
uses: softprops/action-gh-release@v1
111+
with:
112+
files: |
113+
*.dxt
114+
dist/*
115+
env:
116+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
workflow_call:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: ["3.13"]
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Install the latest version of uv and set the python version
19+
uses: astral-sh/setup-uv@v6
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
activate-environment: true
23+
- name: Install dependencies
24+
run: uv pip install -r pyproject.toml
25+
- name: Install test dependencies
26+
run: uv pip install ".[test]"
27+
- name: Test with python ${{ matrix.python-version }}
28+
run: uv run --frozen pytest

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Desktop Extension Artifacts
10+
*.dxt
11+
12+
# Virtual environments
13+
.venv
14+
15+
# Pytest cache
16+
.pytest_cache/
17+
18+
# Intellij IDEA files
19+
.idea

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2025 PythonAnywhere
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.
22+

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# PythonAnywhere Model Context Protocol Server
2+
3+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction)
4+
server acts as a bridge between AI-powered tools and your
5+
[PythonAnywhere](https://www.pythonanywhere.com/) account, enabling secure,
6+
programmatic management of files, websites, webapps, and scheduled tasks. By
7+
exposing a standardized interface, it allows language models and automation
8+
clients to perform operations—such as editing files, deploying web apps, or
9+
scheduling jobs—on your behalf, all while maintaining fine-grained control
10+
and auditability.
11+
12+
## Features
13+
- **File management**: Read, upload, delete files and list directory trees.
14+
_(also enables debugging with direct access to log files, which are just
15+
files on PythonAnywhere)_
16+
- **ASGI Web app management**: Create, delete, reload, and list.
17+
_(as described in the [PythonAnywhere ASGI
18+
documentation](https://help.pythonanywhere.com/pages/ASGICommandLine))_
19+
- **WSGI Web app management**: Reload only _(at the moment)_.
20+
- **Scheduled task management**: List, create, update, and delete.
21+
_(enables LLMs to execute arbitrary commands if a task is scheduled to
22+
soon after creation and deleted after execution)_
23+
24+
## Installation
25+
MCP protocol is well-defined and supported by various clients, but
26+
installation is different depending on the client you are using. We will
27+
cover cases that we tried and tested.
28+
29+
In all cases, you need to have `uv` installed and available in your `PATH`.
30+
31+
Have your PythonAnywhere API token and username ready. You can find (or
32+
generate) your API token in the [API section of your PythonAnywhere
33+
account](https://www.pythonanywhere.com/account/#api_token).
34+
35+
### Desktop Extension - works with Claude Desktop
36+
Probably the most straightforward way to install the MCP server is to use
37+
the [desktop extension](https://github.com/anthropics/dxt/) for Claude Desktop.
38+
39+
1. Open Claude Desktop.
40+
2. **[Download the latest .dxt file](https://github.com/pythonanywhere/pythonanywhere-mcp-server/releases/latest/download/pythonanywhere-mcp-server.dxt)**.
41+
3. Double-click on the downloaded .dxt file or drag the file into the window.
42+
4. Configure your PythonAnywhere API token and username.
43+
5. Restart Claude Desktop.
44+
45+
### Claude Code
46+
Run:
47+
```bash
48+
claude mcp add pythonanywhere-mcp-server\
49+
-e API_TOKEN=yourpythonanywhereapitoken\
50+
-e LOGNAME=yourpythonanywhereusername\
51+
-- uvx pythonanywhere-mcp-server
52+
```
53+
54+
### GitHub Copilot in PyCharm:
55+
You need to have it to your `mcp.json`.
56+
57+
```json
58+
{
59+
"servers": {
60+
"pythonanywhere-mcp-server": {
61+
"type": "stdio",
62+
"command": "uvx",
63+
"args": ["pythonanywhere-mcp-server"],
64+
"env": {
65+
"API_TOKEN": "yourpythonanywhereapitoken",
66+
"LOGNAME": "yourpythonanywhereusername"
67+
}
68+
}
69+
}
70+
}
71+
```
72+
73+
### Claude Desktop (manual setup) and Cursor:
74+
Add it to `claude_desktop_config.json` (for Claude Desktop) or (`mcp.json`
75+
for Cursor).
76+
77+
```json
78+
{
79+
"mcpServers": {
80+
"pythonanywhere-mcp-server": {
81+
"type": "stdio",
82+
"command": "uvx",
83+
"args": ["pythonanywhere-mcp-server"],
84+
"env": {
85+
"API_TOKEN": "yourpythonanywhereapitoken",
86+
"LOGNAME": "yourpythonanywhereusername"
87+
}
88+
}
89+
}
90+
}
91+
```
92+
93+
## Caveats
94+
95+
Direct integration of an LLM with your PythonAnywhere account offers
96+
significant capabilities, but also introduces risks. We strongly advise
97+
maintaining human oversight, especially for sensitive actions such as
98+
modifying or deleting files.
99+
100+
If you are running multiple MCP servers simultaneously, be
101+
cautious—particularly if any server can access external resources you do not
102+
control, such as GitHub issues. These can become attack vectors. For more
103+
details, see [this story](https://simonwillison.net/2025/Jul/6/supabase-mcp-lethal-trifecta/).

manifest.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"dxt_version": "0.1",
3+
"name": "PythonAnywhere MCP Server",
4+
"description": "Manage files, websites, and scheduled tasks on PythonAnywhere via the Model Context Protocol.",
5+
"version": "0.0.1",
6+
"author": {
7+
"name": "PythonAnywhere Developers",
8+
"email": "[email protected]"
9+
},
10+
"main": "pythonanywhere_mcp_server.py",
11+
"categories": [
12+
"developer-tools",
13+
"utilities"
14+
],
15+
"license": "MIT",
16+
"permissions": [
17+
"mcp",
18+
"filesystem"
19+
],
20+
"user_config": {
21+
"pa_api_token": {
22+
"type": "string",
23+
"title": "PythonAnywhere API Token",
24+
"description": "Your PythonAnywhere API token",
25+
"required": true
26+
},
27+
"pa_username": {
28+
"type": "string",
29+
"title": "PythonAnywhere Username",
30+
"description": "Your PythonAnywhere username",
31+
"required": true
32+
}
33+
},
34+
"server": {
35+
"type": "binary",
36+
"entry_point": "pythonanywhere_mcp_server.py",
37+
"mcp_config": {
38+
"command": "uvx",
39+
"args": ["pythonanywhere-mcp-server"],
40+
"env": {
41+
"API_TOKEN": "${user_config.pa_api_token}",
42+
"LOGNAME": "${user_config.pa_username}"
43+
}
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)