project-mcp
# Project Development MCP Server
A [FastMCP](https://gofastmcp.com/) server that exposes predefined artifacts (templates, configs, code snippets, assets) as **Resources** and project lifecycle operations as **Tools**. Use it from [Cursor](https://cursor.com/) or any MCP client to create, update, deploy, debug, test, monitor, and configure projects with minimal token usage.
**Recommended: HTTP transport.** Run the server once; all clients (Cursor, other IDEs, CLIs) connect to the same URL. One process, shared use, no per-client spawn.
## Setup
### Option A: Nix + devenv (recommended)
With [Nix](https://nixos.org/) and [devenv](https://devenv.sh/) installed:
```bash
cd project-mcp
devenv up
```
This installs Python and uv, runs `uv sync`, and starts the MCP server on **HTTP** at `http://localhost:8000/mcp`. Leave it running; point Cursor and other clients at that URL. Use `direnv allow` if you use direnv (optional for `devenv up`).
### Option B: uv only
- Python 3.10+
- [uv](https://github.com/astral-sh/uv)
- [FastMCP](https://gofastmcp.com/) 3.x (installed via uv)
```bash
cd project-mcp
uv sync
```
## Running the server
**HTTP (recommended)** — one server for all clients:
```bash
# With devenv (starts HTTP server)
devenv up
# Or with uv only (HTTP is the default)
uv run python server.py
```
Server base URL: **`http://localhost:8000/mcp`** (port 8000 unless you set `MCP_PORT`). Connect Cursor and other clients to this URL; no need for each client to run the server. When using HTTP, a **`/health`** endpoint returns `{"status": "ok"}` for load balancers or k8s probes.
**Stdio (alternative)** — Cursor or another client runs the server as a subprocess (one process per client). Set `MCP_TRANSPORT=stdio` or use the fastmcp CLI:
```bash
MCP_TRANSPORT=stdio uv run python server.py
# or
uv run fastmcp run fastmcp.json
```
Use stdio if you prefer zero “run the server” step and only one client.
### Configuration
- **`PROJECT_MCP_ROOT`** — Root directory for project paths (default: current working directory). All tool paths (`target_path`, `project_path`, `path`) must resolve under this root; path traversal (e.g. `../`) is rejected. Set this to your workspace or a dedicated projects directory to scope and secure where the server can read/write. At startup, the server warns if this is set but not a directory or missing.
- **`PROJECT_MCP_ALLOWED_COMMANDS`** — Comma-separated list of command prefixes allowed by `run_command` (e.g. `python,npm,uv`). If unset, defaults to: python, npm, npx, uv, pip, node, pytest, make.
- **`MCP_TRANSPORT`** — `http` (default) or `stdio`.
- **`MCP_PORT`** — Port for HTTP (default: `8000`).
- **`LOG_LEVEL`** — Logging level (default: `INFO`). Set to `DEBUG` for more verbose tool logs.
## Running tests
Install dev dependencies (pytest, ruff), then run the test suite:
```bash
uv sync --extra dev
uv run pytest tests/ -v
```
To run linting and format checks (same as CI):
```bash
uv run ruff check .
uv run ruff format --check .
```
## Cursor integration
**HTTP (recommended):** Run the server once (e.g. `devenv up` or the HTTP command above), then add the server in Cursor by URL. Example MCP config (e.g. in Cursor Settings → MCP or `.cursor/mcp.json`):
```json
{
"mcpServers": {
"project-dev": {
"url": "http://localhost:8000/mcp"
}
}
}
```
If your Cursor version uses a different shape (e.g. `transport: "sse"` with a separate `url`), see [Cursor + FastMCP](https://gofastmcp.com/integrations/cursor). Use your actual host/port if not localhost. All Cursor windows and other clients can use the same running server.
**Stdio (alternative):** Cursor runs the server itself. In MCP settings use a **command** instead of a URL:
```json
{
"mcpServers": {
"project-dev": {
"command": "uv",
"args": ["run", "fastmcp", "run", "fastmcp.json"],
"cwd": "/absolute/path/to/project-mcp"
}
}
}
```
Replace `/absolute/path/to/project-mcp` with the real path.
## Artifacts and URI scheme
Predefined content is organized by **context** first (folder under `artifacts/`), then **type** (folder under each context). **Context** is a flexible grouping—maintainers choose the strategy that fits their needs (e.g. by technology, project type, or other axes).
**URI pattern:** `artifact://{context}/{type}/{path}`
| Part | Purpose | Examples |
| --------- | ------- | -------- |
| `context` | Grouping chosen by maintainer | `default` (generic), `fastapi`, `react`, `internal-admin`, `data-pipeline` |
| `type` | Kind of artifact under that context | `templates`, `configs`, `snippets`, `assets`, `components`, `iac` |
| `path` | Relative path under context/type | `fastapi-app`, `pyproject.toml`, `Button.tsx` |
**Context examples:**
- By technology: `fastapi`, `react`, `aws`, `gcp`
- By project type: `internal-admin`, `data-pipeline`, `research-notebook`, `app-documentation`
- `default`: generic, stack-agnostic artifacts only
**URI examples:**
- `artifact://default/configs/pyproject.toml` — generic Python config
- `artifact://default/snippets/hello.py` — generic hello snippet
- `artifact://fastapi/templates/fastapi-app` — FastAPI app template
- `artifact://react/templates/react-component.tsx` — React component template
- `artifact://data-pipeline/configs/dag.yaml` — data pipeline DAG config
Add new contexts by adding a folder under `artifacts/`; add new types by adding a folder under a context. No server code changes required. Use **Resources** to read these URIs on demand so the LLM does not hold large blobs in context.
## Tools
| Tool | Description |
| ---------------- | ----------------------------------------------------------------- |
| `list_artifacts` | List available artifacts (optionally filter by context/type). Returns JSON with `uri` per artifact. |
| `create_project` | Create project from a template; use `context` to pick the group. Optional `variables` for `{{key}}` substitution. |
| `read_file` | Read a file at path (under project root). |
| `list_directory` | List directory contents at path (one level). |
| `search_files` | Search for regex pattern in project files; optional include/exclude globs. |
| `edit_file` | Replace old_string with new_string in file (first or all). |
| `write_file` | Write or overwrite a file under the project root. |
| `run_tests` | Run tests (pytest or npm test). |
| `deploy` | Run deploy (Makefile, npm run deploy, or custom script). |
| `run_command` | Run an allowed command in project dir (python, npm, uv, etc.). |
| `status` | Project status and detected type. |
| `get_logs` | Recent log content from `.log` files. |
| `get_config` | Read config key (e.g. name, version) from pyproject/package.json. |
| `update_config` | Update name or version in pyproject.toml or package.json. |
All paths are validated against `PROJECT_MCP_ROOT` to prevent path traversal.
## Usage examples
From an MCP client (e.g. Cursor), you can call tools and read resources like this:
**Discover artifacts:** Call `list_artifacts()` (or `list_artifacts(context="fastapi")`) to get a JSON list of artifact URIs, then read any via the Resource `artifact://{context}/{type}/{path}`.
**Create a FastAPI project:**
```text
create_project(template_id="fastapi-app", target_path="./my-api", context="fastapi")
```
**Create a project with template variables:** If the template contains `{{project_name}}` or `{{version}}`, pass them in:
```text
create_project(template_id="var-test", target_path="./my-app", context="default", variables={"project_name": "MyApp", "version": "1.0"})
```
**Write a file:** `write_file(path="src/main.py", content="print('hello')")`
**Project status:** `status(project_path=".")` — returns detected type (Python/Node) and top-level listing.
**Run tests:** `run_tests(project_path=".")` — runs pytest or `npm test` based on project type.
## Project layout
```
project-mcp/
├── server.py # FastMCP app and registration
├── path_util.py # Path validation helpers
├── artifact_loader.py # Artifact discovery and read (type/context/path)
├── fastmcp.json # FastMCP project config
├── devenv.nix # Nix + devenv (packages, process)
├── devenv.yaml # Devenv inputs
├── .envrc # direnv: use devenv
├── pyproject.toml
└── artifacts/ # Client-facing content: artifact://{context}/{type}/{path}
├── default/ # generic, stack-agnostic only
│ ├── configs/ # pyproject.toml, tsconfig.json, Dockerfile
│ ├── snippets/ # hello.py
│ └── assets/ # placeholder.svg
├── fastapi/ # context: technology
│ └── templates/ # fastapi-app
├── react/ # context: technology
│ └── templates/ # react-component.tsx
# Add contexts as needed: internal-admin/, data-pipeline/, aws/, gcp/, etc.
```
## License
MIT.
TDQS
Scored across 14 tools
Several tools overlap in purpose: run_command can duplicate run_tests and deploy, read_file/get_config both read config files, edit_file/update_config can both modify config, and list_artifacts/list_directory/search_files share discovery space. Descriptions help, but an agent could plausibly misselect among these.
Most tools follow a readable snake_case verb_noun pattern (read_file, write_file, get_config, update_config, run_tests). Minor deviations include _substitute_vars with a leading underscore, plus deploy and status as bare verb/noun names.
14 tools is within the typical well-scoped range and matches a project lifecycle surface spanning files, config, tests, deployment, commands, status, and logs. No excessive proliferation or obvious under-provisioning.
Core project operations are covered: file read/write/list/search/edit, config get/update, test running, deployment, command execution, status, and logs. Minor gaps remain around explicit delete_file/create_directory and artifact lifecycle beyond listing, though run_command can work around many of them.