mcp-starter-kit

mcp-starter-kit

A scaffolding toolkit to generate clean architecture MCP servers with tool scaffolding and function inspection.

Category
Visit Server

README

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

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.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured