Skip to main content
Glama
j3camp

mcp-starter-kit

by j3camp

mcp-scaffold

A Clean Architecture MCP Server Scaffolding Toolkit.

Generate production-ready MCP server projects, add new tools incrementally, and derive tool scaffolding from ordinary Python functions — while keeping your application code independent of FastMCP or any other MCP SDK.


Table of Contents

  1. Project purpose

  2. Suitable use cases

  3. Unsuitable use cases

  4. Three-minute quick start

  5. Installation

  6. CLI command reference

  7. Exit-code policy

  8. Script reference

  9. Creating the first MCP server

  10. Creating the first tool

  11. Inspect-driven generation

  12. Supported and unsupported Python types

  13. Generated template structure

  14. Generated versus handwritten files

  15. SDK adapter boundary

  16. Architecture and diagrams

  17. Testing

  18. Troubleshooting

  19. Known limitations

  20. Roadmap

  21. Contributing


Related MCP server: project-scaffold

Project purpose

mcp-scaffold creates and evolves MCP server projects so that you can focus on business logic instead of wiring. It generates a clean-architecture project skeleton, adds individual tool stubs, and can derive scaffolding directly from an existing Python function signature using inspect.

The generated code is directly importable, testable, lintable, and type-checkable without modification.


Suitable use cases

  • Bootstrapping a new Python MCP server from zero.

  • Adding a new tool to an existing generated project without touching unrelated files.

  • Exploring what MCP metadata an existing Python function would produce.

  • Enforcing a consistent project layout across a team.

  • Demonstrating Clean Architecture with a real, working project.


Unsuitable use cases

  • Web-based administration UI.

  • Cloud deployment automation.

  • Authentication or authorization systems.

  • Plugin marketplaces or dynamic runtime plugin installation.

  • Distributed service discovery.

  • Database-backed template registries.

  • Automatic module scanning across arbitrary directories.


Three-minute quick start

# 1. Install
pip install -e ".[dev]"

# 2. Create a project
mcp-scaffold new-project my-server

# 3. Enter the project and run its tests
cd my-server
python -m pytest tests/ -v

# 4. Add a tool
mcp-scaffold new-tool get-user-info --project-dir .

# 5. Validate the project structure
mcp-scaffold validate .

Installation

From source (development)

git clone https://github.com/j3camp/mcp-starter-kit.git
cd mcp-starter-kit
pip install -e ".[dev]"

Runtime only

pip install -e .

Required runtime dependencies

Package

Purpose

pydantic>=2.0

Domain models and validation

typer>=0.12

CLI framework

fastmcp>=2.0

MCP SDK (infrastructure only)

Optional development dependencies

Package

Purpose

pytest>=8.0

Test runner

pytest-asyncio>=0.23

Async test support

ruff>=0.4

Linter and formatter

mypy>=1.10

Static type checker


CLI command reference

mcp-scaffold new-project <project-name>

Create a new MCP server project.

mcp-scaffold new-project my-service
mcp-scaffold new-project my-service --output-dir /projects
mcp-scaffold new-project my-service --dry-run
mcp-scaffold new-project my-service --force

Option

Description

--output-dir, -o

Parent directory (default: .)

--force

Overwrite existing files

--dry-run

Show what would be created without writing


mcp-scaffold new-tool <tool-name>

Add a new tool stub to an existing generated project.

mcp-scaffold new-tool get-user-info
mcp-scaffold new-tool get-user-info --project-dir ./my-service
mcp-scaffold new-tool get-user-info --dry-run
mcp-scaffold new-tool get-user-info --force

Option

Description

--project-dir, -p

Project root (default: .)

--force

Overwrite existing files

--dry-run

Show what would be created without writing

Creates:

  • src/<pkg>/application/tools/<snake>.py — tool function stub

  • src/<pkg>/application/tools/<snake>_registration.py — registration helper

  • tests/test_<snake>.py — unit test stub


mcp-scaffold inspect <module:function>

Inspect a Python function and print normalized MCP tool metadata.

mcp-scaffold inspect my_module.tools:get_user_info
mcp-scaffold inspect my_module.tools:get_user_info --base-path .

Option

Description

--base-path

Directory prepended to sys.path before import (default: .)

Example output:

Function:    get_user_info
Tool name:   get-user-info
Description: Return public information for a user.
Mode:        sync
Returns:     UserInfo
Parameters:
  user_id: str — required

mcp-scaffold generate <module:function>

Inspect a Python function and generate tool scaffolding from its signature.

mcp-scaffold generate my_module.tools:get_user_info --project-dir ./my-service
mcp-scaffold generate my_module.tools:get_user_info --project-dir ./my-service --dry-run

Option

Description

--project-dir, -p

Target project root (default: .)

--base-path

Directory prepended to sys.path

--force

Overwrite existing files

--dry-run

Show what would be created without writing


mcp-scaffold validate [path]

Validate the structure of a generated project.

mcp-scaffold validate .
mcp-scaffold validate ./my-service

Checks:

  • pyproject.toml exists

  • src/ directory exists

  • At least one Python package under src/

  • tests/ directory exists


Exit-code policy

Code

Meaning

0

Success

1

General execution failure

2

Invalid command usage or arguments

3

Target already exists or overwrite denied

4

Unsupported inspected type or invalid source function

5

Generated project validation failure

All commands return non-zero codes on failure and print human-readable error messages to stderr. Python tracebacks are suppressed for expected user errors.


Script reference

Script (Unix)

Script (Windows)

Purpose

scripts/setup.sh

scripts/setup.bat

Install dependencies

scripts/test.sh

scripts/test.bat

Run test suite

scripts/lint.sh

scripts/lint.bat

Run linter

scripts/typecheck.sh

scripts/typecheck.bat

Run type checker

scripts/run.sh

scripts/run.bat

Start the MCP server

scripts/validate-template.sh

scripts/validate-template.bat

Validate project structure

Scripts contain no business or generation logic. They delegate entirely to Python tools and stop on any non-zero exit code.


Creating the first MCP server

# Generate the project
mcp-scaffold new-project hello-mcp --output-dir /tmp

# Inspect the generated layout
ls /tmp/hello-mcp/src/hello_mcp/

# Run the generated tests (no installation required)
cd /tmp/hello-mcp
python -m pytest tests/ -v

# Start the server
python -m hello_mcp

The generated server.py includes a composition root:

def create_server() -> fastmcp.FastMCP:
    """Create and configure the MCP server."""
    mcp = fastmcp.FastMCP("mcp-server")
    adapter = FastMCPAdapter(mcp)
    adapter.register_tool(name="echo", description="...", handler=echo)
    return mcp

Importing this module does not start the server. Only calling create_server() and then server.run() does.


Creating the first tool

mcp-scaffold new-tool search-products --project-dir /tmp/hello-mcp

This generates:

src/hello_mcp/application/tools/search_products.py
src/hello_mcp/application/tools/search_products_registration.py
tests/test_search_products.py

Edit the stub to implement your business logic, then call the registration helper from server.py:

from hello_mcp.application.tools.search_products_registration import register_search_products

def create_server() -> fastmcp.FastMCP:
    mcp = fastmcp.FastMCP("mcp-server")
    adapter = FastMCPAdapter(mcp)
    register_search_products(adapter)
    return mcp

Inspect-driven generation

Given an ordinary Python function:

# my_service/tools.py
from pydantic import BaseModel

class UserInfo(BaseModel):
    user_id: str
    display_name: str
    active: bool

def get_user_info(user_id: str) -> UserInfo:
    """Return public information for a user."""
    raise NotImplementedError

Inspect it:

mcp-scaffold inspect my_service.tools:get_user_info --base-path .

Output:

Function:    get_user_info
Tool name:   get-user-info
Description: Return public information for a user.
Mode:        sync
Returns:     UserInfo
Parameters:
  user_id: str — required

Generate scaffolding:

mcp-scaffold generate my_service.tools:get_user_info \
  --project-dir ./my-server --base-path .

The inspect pipeline runs in explicit stages:

Python function
  -> discovery         validate callable is public and annotated
  -> metadata          extract name, docstring, parameters, return type
  -> type mapping      validate each annotation
  -> normalization     produce ToolMetadata
  -> rendering model   plan files to create
  -> generated files   write to disk (skipped in --dry-run)

Supported and unsupported Python types

Supported

Python type

Example

str

x: str

int

x: int

float

x: float

bool

x: bool

None (return only)

-> None

Optional[T]

x: Optional[str]

Literal[...]

x: Literal["a", "b"]

list[T]

x: list[str]

dict[K, V]

x: dict[str, int]

Pydantic BaseModel subclass

x: UserInfo

Unsupported

Python type

Behaviour

Union[A, B] (non-nullable)

Raises UnsupportedTypeError

set, tuple, frozenset

Raises UnsupportedTypeError

*args, **kwargs

Validation error recorded in metadata

Missing type annotation

Recorded as unsupported parameter

Missing return annotation

Validation error (override with allow_missing_return=True)

Private functions (_name)

Raises InspectError (override with allow_private=True)

Classes

Raises InspectError

Unknown types never silently fall back to str.


Generated template structure

<project-name>/
├── pyproject.toml               # build config, pytest, ruff, mypy
├── README.md
├── .gitignore
├── src/
│   └── <pkg>/
│       ├── __init__.py
│       ├── __main__.py          # entry point
│       ├── server.py            # composition root (create_server)
│       ├── domain/
│       │   ├── __init__.py
│       │   └── ports.py         # MCPServerPort protocol
│       ├── application/
│       │   ├── __init__.py
│       │   └── tools/
│       │       ├── __init__.py
│       │       └── example_tool.py
│       └── infrastructure/
│           ├── __init__.py
│           └── mcp_adapter.py   # FastMCP adapter
├── tests/
│   ├── __init__.py
│   ├── test_example_tool.py
│   └── test_server.py
└── scripts/
    ├── run.sh / run.bat
    ├── test.sh / test.bat
    ├── lint.sh / lint.bat
    └── typecheck.sh / typecheck.bat

Generated versus handwritten files

File

Type

Notes

pyproject.toml

Generated

Regenerated by --force

README.md

Generated

Regenerated by --force

.gitignore

Generated

Regenerated by --force

src/<pkg>/__init__.py

Generated

Regenerated by --force

src/<pkg>/domain/ports.py

Generated

Regenerated by --force

src/<pkg>/infrastructure/mcp_adapter.py

Generated

Regenerated by --force

src/<pkg>/server.py

Generated (composition root)

Edit to add tool registrations

src/<pkg>/application/tools/example_tool.py

Generated

Replace with real logic

src/<pkg>/application/tools/<tool>.py

Generated stub

Handwritten after generation

src/<pkg>/application/tools/<tool>_registration.py

Generated

Safe to edit registration call

tests/test_*.py

Generated stub

Handwritten after generation

Files marked Handwritten after generation are created once and never overwritten unless --force is supplied.


SDK adapter boundary

mcp-scaffold applies Dependency Inversion between the application layer and the MCP SDK.

application/domain
    MCPServerPort  <-- Protocol owned by the inner layer
                       (no fastmcp import)

infrastructure
    FastMCPAdapter  --> implements MCPServerPort
                        (only file that imports fastmcp)

The composition root (server.py) wires them together:

import fastmcp
from <pkg>.infrastructure.mcp_adapter import FastMCPAdapter

def create_server() -> fastmcp.FastMCP:
    mcp = fastmcp.FastMCP("name")
    adapter = FastMCPAdapter(mcp)
    adapter.register_tool(...)
    return mcp

To replace FastMCP with another SDK:

  1. Create a new adapter class that implements register_tool(*, name, description, handler).

  2. Replace FastMCPAdapter in server.py with your adapter.

  3. The application layer requires zero changes.


Architecture and diagrams

Overall architecture

graph TD
    CLI["CLI (Typer)"]
    App["Application Services"]
    Domain["Domain Models + Ports"]
    Infra["Infrastructure Adapters"]

    CLI --> App
    App --> Domain
    Infra --> Domain
    CLI --> Infra

Dependency flow

graph LR
    CLI --> InspectSvc["inspect_service"]
    CLI --> GenerateSvc["generate_service"]
    CLI --> ValidateSvc["validate_service"]
    InspectSvc --> Models["domain/models.py"]
    InspectSvc --> TypeMapper["infrastructure/type_mapper.py"]
    GenerateSvc --> Models
    GenerateSvc --> Renderer["infrastructure/renderer.py"]
    FastMCPAdapter["infrastructure/fastmcp_adapter.py"] --> Ports["domain/ports.py"]

New-project generation flow

flowchart TD
    A["mcp-scaffold new-project my-server"] --> B["generate_project()"]
    B --> C["Renderer.stage() all files"]
    C --> D{"dry_run?"}
    D -- yes --> E["Return GenerationPlan (no writes)"]
    D -- no --> F["Conflict check"]
    F --> G{"conflict?"}
    G -- "yes, no --force" --> H["RenderConflictError (exit 3)"]
    G -- "no or --force" --> I["Write files to disk"]
    I --> J["Return GenerationPlan"]

New-tool generation flow

flowchart TD
    A["mcp-scaffold new-tool get-user"] --> B["generate_tool()"]
    B --> C["_detect_package()"]
    C --> D["Stage tool stub"]
    D --> E["Stage registration helper"]
    E --> F["Stage test stub"]
    F --> G["Renderer.commit()"]

Tool request sequence

sequenceDiagram
    participant Client
    participant FastMCP
    participant Adapter as FastMCPAdapter
    participant Tool as Tool Function

    Client->>FastMCP: call tool "echo"
    FastMCP->>Tool: invoke handler
    Tool-->>FastMCP: return result
    FastMCP-->>Client: tool result

Inspect-generation sequence

sequenceDiagram
    participant User
    participant CLI
    participant InspectSvc as inspect_service
    participant TypeMapper as type_mapper
    participant GenerateSvc as generate_service
    participant Renderer

    User->>CLI: mcp-scaffold generate module:fn
    CLI->>InspectSvc: inspect_from_string(spec)
    InspectSvc->>TypeMapper: map_type(annotation)
    TypeMapper-->>InspectSvc: mapped type string
    InspectSvc-->>CLI: ToolMetadata
    CLI->>GenerateSvc: generate_from_metadata(metadata)
    GenerateSvc->>Renderer: stage files
    Renderer-->>GenerateSvc: GenerationPlan
    GenerateSvc-->>CLI: GenerationPlan
    CLI-->>User: files created

CLI-to-Python-generator call flow

flowchart LR
    CLI["cli/main.py"] --> GenSvc["generate_service.py"]
    CLI --> InspSvc["inspect_service.py"]
    CLI --> ValSvc["validate_service.py"]
    GenSvc --> Renderer["renderer.py"]
    InspSvc --> TypeMapper["type_mapper.py"]

Testing

Run the full validation suite:

python -m pytest tests/ -v
python -m ruff check .
python -m mypy src tests
mcp-scaffold validate .

Test areas covered

Area

Tests

Inspect discovery

Public/async/private functions, optional params, missing annotations

Type mapping

Primitives, Optional, Literal, list, dict, Pydantic, unsupported types

Renderer

Stage/commit, dry-run, conflict detection, force, LF line endings

Generate service

Project generation, dry-run, conflict, tool addition

CLI

Help, new-project, new-tool, validate, dry-run, force, error codes

Adapter contract

RecordingAdapter sync/async, multiple tools, name propagation

Generated project validation

After mcp-scaffold new-project test-server:

cd test-server
python -m pytest tests/ -v   # should pass with no installation

Troubleshooting

ModuleNotFoundError: No module named '<pkg>' in generated project tests

The generated pyproject.toml sets pythonpath = ["src"] in pytest options. If you see this error, ensure your pytest version supports pythonpath (pytest >= 7.0).

TypeError: FastMCP.add_tool() got an unexpected keyword argument 'name'

This occurs with fastmcp >= 2.x. The adapter uses Tool.from_function(fn, name=..., description=...). Ensure you are using the adapter generated by this version of mcp-scaffold.

RenderConflictError: File already exists

Use --force to overwrite, or delete the conflicting file manually.

Unsupported type error during inspect

Check that all parameter and return type annotations use supported types (see Supported and unsupported Python types). Union types with more than two members (excluding None) are not supported.


Known limitations

  • mcp-scaffold generate does not automatically update server.py to register the new tool. You must add the register_<tool>(adapter) call manually.

  • Windows batch scripts require cmd.exe and do not support PowerShell-only features.

  • Paths containing ! may cause issues with set -e in some sh implementations.

  • Forward references (string annotations) are resolved with typing.get_type_hints; they may fail if the referenced type is not importable at inspect time.

  • The validate command checks structural presence only; it does not run tests or verify import correctness.


Roadmap

  • Automatic server.py update after new-tool.

  • Generated project type-check and import smoke-test in validate.

  • Support for Union[A, B] (non-nullable) with explicit opt-in.

  • Interactive prompts for project metadata.

  • Optional conftest.py and fixture generation.

  • GitHub Actions CI template generation.


Contributing

  1. Fork the repository and create a feature branch.

  2. Install development dependencies: pip install -e ".[dev]".

  3. Run checks before opening a PR:

    python -m pytest tests/ -v
    python -m ruff check .
    python -m mypy src tests
  4. Keep changes focused: one concern per PR.

  5. Add or update tests for every changed behaviour.

  6. Write commit messages in English.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    D
    maintenance
    A production-ready MCP server scaffold that features built-in authentication, Docker support, and a comprehensive CI/CD release pipeline. It provides a standardized template for deploying servers with multi-transport support and configurable read-only modes.
    MIT
  • F
    license
    C
    quality
    D
    maintenance
    A Model Context Protocol (MCP) server template built with mcp-framework, providing a foundation for creating custom tools and publishing npm packages.
    2

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/j3camp/mcp-starter-kit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server