| name | mcp-project-creator |
| description | Creates a new MCP server package in this monorepo. Uses Python FastMCP 3.0 in a Docker container, deployed to AWS Lambda via CDK and GitHub Actions. |
MCP Server Creator
Creates a new MCP server using FastMCP 3.0, Docker, and AWS Lambda.
See CLAUDE.md at the repo root for project context.
Workflow
- Check one-time setup — confirm the shared infra exists (see below). Skip if a server already deploys successfully.
- Gather info — server name (kebab-case)
- Create package —
packages/{name}/ from references/mcp-server.md
- Register CDK stack — add to
infrastructure/bin/infrastructure.ts per references/infrastructure.md
- Wire env vars (only if the server needs them) — add to
.github/workflows/deploy.yml per references/shared-env-vars.md
- Verify — walk the completion checklist below
Step 0: One-Time Setup (first server in a fresh clone)
The per-server steps below assume shared scaffolding already exists. On the
first server in a freshly cloned/templated repo, verify (and create if missing):
A quick local sanity check before relying on CI: npm ci && npm run build --workspace=infrastructure && (cd infrastructure && npx cdk synth).
Completion Checklist
Confirm each item before declaring the task done:
Step 1: Gather Info
Ask for the MCP server name in kebab-case (e.g., weather, pdf-tools, github-issues).
Validate:
- Lowercase + hyphens only
- Does not conflict with an existing
packages/{name}/
- Does not start with
_ (reserved for shared internal packages)
Step 2: Create the Package
mkdir -p packages/{name}
Create from references/mcp-server.md:
app.py — FastMCP 3.0 server with create_mcp_server(), lambda_handler(), and a local __main__ entry. Calls load_dotenv() so .env values are picked up automatically when running locally.
pyproject.toml — Project metadata + dependencies (fastmcp>=3.0, mangum, uvicorn, python-dotenv, plus any server deps). [tool.uv] package = false marks it as an app.
.python-version — Pins to 3.12 so uv auto-uses (and auto-installs) the right interpreter.
uv.lock — Generated by uv lock (or first uv sync); commit it for reproducible builds.
Dockerfile — Multi-stage Lambda container that uses uv to install deps into ${LAMBDA_TASK_ROOT}.
.env — Placeholder values for local dev (gitignored repo-wide).
Create from references/readme-template.md:
README.md — features, tools table, use cases, quick start
Why include use cases in the README? They help AI assistants understand when to use each tool, document expected behavior for ambiguous queries, and double as integration test scenarios.
Step 3: Register with CDK
Edit infrastructure/bin/infrastructure.ts:
new McpDockerStack(app, `McpServer-{name}-${stage}`, {
env,
stage,
serverName: '{name}',
imageTag,
sharedResources: sharedStack.sharedResources,
environment: {
},
});
The imageTag constant (defined at the top of infrastructure.ts) reads process.env.IMAGE_TAG — the deploy workflow sets it to the git SHA so CloudFormation always replaces the Lambda image. Always pass imageTag.
For env vars, IAM permissions, and custom stacks, see references/infrastructure.md.
Step 4: Wire Env Vars (only if needed)
If the server reads no environment variables, skip this step.
The deploy workflow's "Deploy server stack" step runs cdk deploy. Any process.env.X your CDK code reads must be in its env: block:
- name: Deploy server stack
env:
STAGE: ${{ inputs.environment || 'dev' }}
IMAGE_TAG: ${{ github.sha }}
{NAME}_API_BASE_URL: ${{ vars.{NAME}_API_BASE_URL }}
{NAME}_API_KEY: ${{ secrets.{NAME}_API_KEY }}
- Prefix server-specific vars with
{NAME}_ (uppercase, underscores) — prevents collisions as the repo grows
- Use
vars. for non-sensitive values, secrets. for sensitive ones
- The GitHub variable/secret must be created in the repo settings before the first deploy
- If reusing a shared variable already wired up in the workflow, do not re-declare it. See references/shared-env-vars.md.
Note: The deploy workflow auto-detects changed packages — you do not need to update a workflow_dispatch.options dropdown.
Step 5: Run Locally (optional)
Local dev runs directly via uv — no Docker required. app.py calls load_dotenv() on startup, so any values in packages/{name}/.env are picked up automatically.
cd packages/{name}
uv sync
PORT=8000 uv run python app.py
Prerequisite: uv installed (curl -LsSf https://astral.sh/uv/install.sh | sh). uv handles the Python 3.12 install for you via .python-version, so contributors do not need to install python3.12 separately.
Adding a dependency: cd packages/{name} && uv add <package> — this updates both pyproject.toml and uv.lock atomically. Commit both files.
Note: The .env file is gitignored repo-wide (via the root .gitignore's .env entry). Never commit it.
Step 6: Deploy
Push to main. GitHub Actions:
- Detects the new package in
packages/{name}/
- Creates the ECR repository if missing
- Builds and pushes the Docker image (tagged with the git SHA)
- Deploys
McpServer-{name}-{stage} via CDK
The first deploy creates the ECR repo and Lambda together. Subsequent deploys swap the image based on IMAGE_TAG.
Gotchas:
- The workflow's
paths: filter only watches packages/**, infrastructure/**, and deploy.yml. Changes confined to the repo root (e.g. a new root package-lock.json) do not auto-trigger it — use gh workflow run deploy.yml -f server=all -f environment=dev to dispatch manually.
- The Lambda is fronted by an IAM-auth Function URL, so a plain
curl returns 403. To verify the live endpoint, SigV4-sign a POST {FunctionUrl}mcp request — see references/cdk-constructs.md. The stack outputs FunctionUrl and FunctionName.
Configuration Defaults
The McpDockerConstruct sets:
| Setting | Default | Override via |
|---|
| Runtime | Python 3.12 (Lambda container) | Dockerfile base image |
| Architecture | X86_64 | construct prop |
| Memory | 512 MB | memorySize prop |
| Timeout | 30 s | timeout prop |
| Auth | AWS_IAM | construct prop |
| Log retention | 14 days | construct prop |
Environment Variables: Naming
Server-specific vars: {SERVER_NAME}_{VAR} (uppercase, underscores).
Examples for weather:
WEATHER_API_BASE_URL
WEATHER_API_KEY
Shared vars (e.g., a common auth provider used by every server) are declared once at the top of infrastructure.ts and reused — they don't carry a server prefix. See references/shared-env-vars.md.
References