mcp-server-scaffold
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-scaffoldsave a note to todo.md saying ship the scaffold"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-scaffold
A GitHub template repository for building your own MCP server in pure Python — no third-party packages, no API keys, no magic.
Built by Asdale Dynamics, LLC as a beginner-friendly starting point for Dylan Asdale's readers: clear, warm, polished, tested, and local-first.
The MCP (Model Context Protocol) is how AI agents like Claude call the tools you write. This scaffold handles the boring, fiddly parts for you, so you can focus on your tool's actual job.
What's inside
mcp_server/
__main__.py python -m mcp_server entry point
server.py JSON-RPC 2.0 over stdio: the whole protocol engine
schema.py small JSON-schema validator (types, bounds, enums, defaults)
guards.py path sandbox + data guards (no escapes, no binary junk)
safeio.py backup-before-write helpers, so tools never destroy data
tools/ your tools live here (two examples included)
base.py Tool base class + registry
echo.py example #1: the simplest working tool
note_writer.py example #2: guarded file writing with automatic backups
tests/ offline pytest suite: schema, dispatch, guards, e2e stdioZero runtime dependencies. Only the Python 3.10+ standard library.
Safe by default. Every file tool is sandboxed to one folder, rejects symlink escapes and control characters, and backs up files before overwriting.
Logged but clean. Request/response logging goes to stderr only, so the stdio transport never gets polluted.
Tested offline. ~100 pytest cases covering schema validation, dispatch, guards, backups, and a full end-to-end stdio handshake.
Related MCP server: My MCP Server
Run it
# run the server (it speaks JSON-RPC on stdin/stdout)
python -m mcp_server --root ./sandbox
# run the test suite
python -m pytest--root is the folder your file tools may touch (default: .).
--no-logging silences the stderr diagnostics.
Tutorial: your first custom tool in 3 steps
Step 1 — Write the tool
Create mcp_server/tools/word_count.py:
from typing import Any
from .base import Tool, content_text
class WordCountTool(Tool):
name = "word_count"
description = "Counts words, characters, and lines in a piece of text."
input_schema = {
"type": "object",
"properties": {
"text": {"type": "string", "minLength": 1, "maxLength": 100_000},
},
"required": ["text"],
"additionalProperties": False,
}
def run(self, arguments: dict[str, Any]) -> list[dict]:
text = arguments["text"]
words = len(text.split())
chars = len(text)
lines = text.count("\n") + 1
summary = f"{words} words, {chars} characters, {lines} line(s)"
return [content_text(summary)]What just happened:
input_schemais the typed contract the AI sees. It declares that one argument exists, that it is a string, and how long it may be. The base class validates incoming arguments against it automatically — bad arguments never reach yourrun(), and the class refuses to even load if the schema itself is malformed.run()receives pre-validated arguments and returns MCP content blocks. Plain text blocks are the common case; the AI reads your text back.No comments needed, no boilerplate: one schema, one method.
Step 2 — Register it
Open mcp_server/tools/__init__.py and add two lines to register_all:
from .word_count import WordCountTool
...
registry.register(WordCountTool())Order matters only for how tools appear in listings; the registry rejects duplicate names for you.
Step 3 — Test it like a real client
Add tests/test_word_count.py:
from mcp_server.tools import build_default_registry
from mcp_server.tools.word_count import WordCountTool
def test_counts_words():
tool = WordCountTool()
assert tool.run({"text": "one two three"})[0]["text"] == "3 words, 13 characters, 1 line(s)"
def test_registry_exposes_it():
reg = build_default_registry(".")
assert "word_count" in reg.names()Run python -m pytest — that's the whole loop. Handshake, dispatch, schema
validation, and logging are already wired: your tool is now callable by any MCP
client.
Want to touch files? Copy the pattern instead of the habit
Look at mcp_server/tools/note_writer.py — that is the template for any
file-modifying tool:
Wrap a folder in
PathGuard(...)so users can't read/write outside it.Run
check_text(...)on anything headed for disk.Write with
write_text_safe(...), which saves a rolling.bakbackup before overwriting and writes through a temp file, so a crash mid-write never corrupts or deletes your data — lessons learned, baked in.
Publishing your server: checklist
Before you share the repo or point a client at it:
python -m pytestpasses from a clean clone.python -m mcp_server --helpprints usage without starting the protocol.Your tools have names, descriptions, and tight schemas (min/max lengths, required fields,
additionalProperties: False).File-enabled tools are rooted with
PathGuardand usewrite_text_safe.No absolute personal paths, no secrets, no API keys anywhere — including tests and README examples.
LICENSEkept as MIT with your copyright line; README credits updated.README quickstart (this file's sections) still matches your tools.
Tested with a real client: connect over stdio, list tools, call one.
Sanity-checked in a fresh directory:
--rootfolder auto-created, backups land insandbox/.backups/<tool>/.Version bumped in
pyproject.tomlandmcp_server/__init__.py.
How clients connect
Most MCP clients run your server as a subprocess and talk JSON-RPC over stdio. A typical client config looks like:
{
"mcpServers": {
"my-scaffold": {
"command": "python",
"args": ["-m", "mcp_server", "--root", "/absolute/path/to/sandbox"]
}
}
}Run from your repo folder so python -m mcp_server resolves the package.
Notes and limits (be honest with yourself)
The schema validator is intentionally small: types, required fields, bounds, enums, patterns, defaults. It covers the great majority of beginner tools. If you need full JSON Schema, swap
schema.pyfor any library — the tool interface stays the same.Backups land beside the data (
<root>/.backups/<tool>/) and keep the 5 most recent versions per directory. They're insurance, not sync.This scaffold implements the core of the MCP protocol (initialize, ping, tools/list, tools/call). Resources and prompts/draft-primitives are out of scope; the
MCPKernelclass is deliberately small so you can extend it.
License
MIT — Copyright (c) 2026 Asdale Dynamics, LLC. From The Massive AI Manual tradition: plain English, step by step, never assuming you were born knowing this. See https://dylanasdale.github.io.
This server cannot be deployed
Maintenance
Related MCP Connectors
Convert, lint and generate LLM tool schemas for OpenAI, Anthropic, Gemini and MCP
A registry of AI agent tools — MCP servers, APIs, CLIs, SDKs — kept current by automated ingestion.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Persistent memory, hybrid search and a goal graph for AI agents, over stdio or remote HTTP.
Related MCP Servers
- AlicenseCqualityNot gradedmaintenanceA basic structure to build and run local tools that support the Model Context Protocol (MCP) standard, compatible with AI tools like Cursor and Claude.1-
- AlicenseNot gradedqualityDmaintenanceA Python template for building Model Context Protocol (MCP) servers that expose tools via JSON-RPC, enabling secure and scalable context and tool invocation for language models.MIT
- AlicenseNot gradedqualityCmaintenanceA zero-dependency Python framework for building Model Context Protocol servers using a simple @tool decorator, with support for stdio and SSE transports.MIT
- AlicenseNot gradedqualityCmaintenanceEnables registering custom agent tools via a minimal JSON-RPC over stdio implementation, with zero external dependencies, to expose them to AI agents like Claude, Cursor, and Gemini.MIT