1+ from pathlib import Path
2+
13from mcp .server .fastmcp import FastMCP
24
35from pythonanywhere_core .webapp import Webapp
46from pythonanywhere_core .base import AuthenticationError , NoTokenError
57
8+ # ToDo: Add the log file functions once pythonanywhere-core webapp log file functions
9+ # have been improved
610
711def register_webapp_tools (mcp : FastMCP ) -> None :
812 @mcp .tool ()
@@ -28,3 +32,206 @@ def reload_webapp(domain: str) -> str:
2832 raise RuntimeError ("Authentication failed — check API_TOKEN and domain." )
2933 except Exception as exc :
3034 raise RuntimeError (str (exc )) from exc
35+
36+ @mcp .tool ()
37+ def create_webapp (domain : str , python_version : str , virtualenv_path : str , project_path : str ) -> str :
38+ """
39+ Create a new uWSGI-based web application for the given domain.
40+
41+ This creates a new webapp on PythonAnywhere with the specified configuration.
42+ The webapp will be created using the specified Python version, virtual environment,
43+ and project path. If a webapp already exists for this domain, it will fail unless
44+ the nuke parameter is set to True.
45+
46+ The WSGI configuration file can be found in /var/www. The file is of the format:
47+ domain.replace('.', '_') + '_wsgi.py' It would be automatically created as side effect of running this tool.
48+
49+ Note:
50+ Virtual environments should be configured in the PythonAnywhere web app
51+ configuration, not in the WSGI file itself.
52+
53+ Args:
54+ domain (str): The domain name for the new webapp (e.g., 'alice.pythonanywhere.com').
55+ python_version (str): Python version to use (e.g., '3.11', '3.10', '3.9').
56+ virtualenv_path (str | None): Path to the virtual environment to use. (or None for
57+ no virtualenv when using one of pre-installed Pythons with batteries included packages)
58+ project_path (str): Path to the project directory containing the webapp code.
59+
60+ Returns:
61+ str: Status message indicating creation result.
62+
63+ Raises:
64+ RuntimeError: If authentication fails, webapp already exists (and nuke=False),
65+ or other API errors occur. If 403 error is raised, it may mean that there
66+ is already a non-uwsgi-based website. `list_websites` tool can be used to check that.
67+ """
68+ try :
69+ webapp = Webapp (domain )
70+ webapp .create (
71+ python_version = python_version ,
72+ virtualenv_path = Path (virtualenv_path ),
73+ project_path = Path (project_path ),
74+ nuke = False
75+ )
76+ return f"Webapp '{ domain } ' created successfully."
77+ except (AuthenticationError , NoTokenError ):
78+ raise RuntimeError ("Authentication failed — check API_TOKEN and domain." )
79+ except Exception as exc :
80+ raise RuntimeError (str (exc )) from exc
81+
82+ @mcp .tool ()
83+ def delete_webapp (domain : str ) -> str :
84+ """
85+ Delete a uWSGI-based web application for the given domain.
86+
87+ This permanently deletes the webapp configuration from PythonAnywhere. The actual
88+ files in your file system are not deleted, only the webapp configuration that
89+ serves them. This action cannot be undone.
90+
91+ Args:
92+ domain (str): The domain name of the webapp to delete
93+ (e.g., 'alice.pythonanywhere.com').
94+
95+ Returns:
96+ str: Status message indicating deletion result.
97+
98+ Raises:
99+ RuntimeError: If authentication fails, webapp doesn't exist, or other API errors occur.
100+ """
101+ try :
102+ Webapp (domain ).delete ()
103+ return f"Webapp '{ domain } ' deleted successfully."
104+ except (AuthenticationError , NoTokenError ):
105+ raise RuntimeError ("Authentication failed — check API_TOKEN and domain." )
106+ except Exception as exc :
107+ raise RuntimeError (str (exc )) from exc
108+
109+ @mcp .tool ()
110+ def patch_webapp (domain : str , data : dict ) -> dict :
111+ """
112+ Update configuration settings for a uWSGI-based web application.
113+
114+ This allows you to modify various webapp settings such as the Python version,
115+ virtual environment path, source directory, and other configuration options.
116+ Only the fields provided in the data dictionary will be updated.
117+
118+ In order for any changes to take effect you must reload the webapp.
119+
120+ If you provide an invalid Python version you will receive a 400 error with an error
121+ message that the version you used is not a valid choice. You will need to choose a
122+ different Python version if you get this error.
123+
124+ Args:
125+ domain (str): The domain name of the webapp to update
126+ (e.g., 'alice.pythonanywhere.com').
127+ data (dict): Dictionary containing the configuration updates. Supported keys are:
128+ - 'python_version': Python version (e.g., '3.11')
129+ - 'virtualenv_path': Path to virtual environment
130+ - 'source_directory': Path to source code directory
131+ - 'working_directory': Working directory for the webapp
132+ - 'force_https': Force the use of HTTPS when accessing the webapp
133+ - 'password_protection_enabled': Enable basic HTTP password to your webapp, provided via PythonAnywhere not in the webapp code
134+ - 'password_protection_username': The username used for HTTP password protection
135+ - 'password_protection_password': The password used for HTTP password protection
136+
137+ Returns:
138+ dict: Updated webapp configuration information.
139+ Example: {
140+ "id": 2097234,
141+ "user": username,
142+ "domain_name": domain,
143+ "python_version": "3.10",
144+ "source_directory": f"/home/{username}/mysite",
145+ "working_directory": f"/home/{username}/",
146+ "virtualenv_path": "",
147+ "expiry": "2025-10-16",
148+ "force_https": False,
149+ "password_protection_enabled": False,
150+ "password_protection_username": "foo",
151+ "password_protection_password": "bar"
152+ }
153+
154+ Raises:
155+ RuntimeError: If authentication fails, webapp doesn't exist, or other API errors occur.
156+ """
157+ try :
158+ result = Webapp (domain ).patch (data )
159+ return result
160+ except (AuthenticationError , NoTokenError ):
161+ raise RuntimeError ("Authentication failed — check API_TOKEN and domain." )
162+ except Exception as exc :
163+ raise RuntimeError (str (exc )) from exc
164+
165+ @mcp .tool ()
166+ def list_webapps () -> list :
167+ """
168+ List all uWSGI-based web applications for the current user.
169+
170+ This retrieves information about all webapps configured in your PythonAnywhere
171+ account. The returned list contains dictionaries with detailed information about
172+ each webapp including domain, Python version, paths, and status.
173+
174+ On PythonAnywhere one may also have non-uWSGI-based websites (usually ASGI-based),
175+ which are not included in this list. For those, use the `list_websites` tool.
176+
177+ Returns:
178+ list: List of dictionaries containing webapp information. Empty list means
179+ no WSGI-based webapps are deployed. That still could mean that there are
180+ non-WSGI-based apps that can be listed with the `list_websites` tool.
181+
182+ See also:
183+ get_webapp_info: Get detailed information about a specific webapp.
184+
185+ Raises:
186+ RuntimeError: If authentication fails or other API errors occur.
187+ """
188+ try :
189+ result = Webapp .list_webapps ()
190+ return result
191+ except (AuthenticationError , NoTokenError ):
192+ raise RuntimeError ("Authentication failed — check API_TOKEN." )
193+ except Exception as exc :
194+ raise RuntimeError (str (exc )) from exc
195+
196+ @mcp .tool ()
197+ def get_webapp_info (domain : str ) -> dict :
198+ """
199+ Get detailed information about a specific uWSGI-based web application.
200+
201+ This retrieves comprehensive configuration and status information for the
202+ specified webapp, including paths, Python version, enabled status, and
203+ other configuration details.
204+
205+ Args:
206+ domain (str): The domain name of the webapp to get information for
207+ (e.g., 'alice.pythonanywhere.com').
208+
209+ Returns:
210+ dict: Dictionary containing detailed webapp information including:
211+ - "id": int, # Unique identifier for the site or user session
212+ - "user": str, # Username associated with the deployment
213+ - "domain_name": str, # Domain name for the deployed site
214+ - "python_version": str, # Python version used, e.g., "3.10"
215+ - "source_directory": str, # Absolute path to the site's source directory
216+ - "working_directory": str, # Absolute path to the working directory
217+ - "virtualenv_path": str, # Path to the Python virtual environment (can be empty)
218+ - "expiry": str, # Expiration date in ISO format, e.g., "2025-10-16"
219+ - "force_https": bool, # Whether HTTPS is enforced
220+ - "password_protection_enabled": bool, # Whether password protection is enabled
221+ - "password_protection_username": str, # Username for password-protected access
222+ - "password_protection_password": str # Password for password-protected access
223+
224+
225+ Raises:
226+ RuntimeError: If authentication fails, webapp doesn't exist, or other API errors occur.
227+ """
228+ try :
229+ result = Webapp (domain ).get ()
230+ return result
231+ except (AuthenticationError , NoTokenError ):
232+ raise RuntimeError ("Authentication failed — check API_TOKEN and domain." )
233+ except Exception as exc :
234+ raise RuntimeError (str (exc )) from exc
235+
236+
237+
0 commit comments