·
Last changedVersion:
This version passed the hardening gate. Hardening applied 14 targeted changes and preserved all other text. Classified findings before hardening: 4. Classified findings closed: 0. Classified findings remaining: 4. Unclassified scanner findings remaining: 0. Review findings closed: 16. Review findings remaining: 0.
2
2
2
2
Expand | File | Group | Subtype | Severity | Status |
|---|---|---|---|---|---|
SKILL.md | Malicious Code & Command Execution | — | Medium | Open | |
SKILL.md | Supply Chain & Install-Time Trust | Install-time download and execute | Medium | Open | |
SKILL.md | Tool & MCP Manipulation | MCP configuration abuse | Low | Open | |
references/streamable-http.md | Sensitive Data & Secret Exposure | Hardcoded secret | Low | Open |
4 findings
Create a Model Context Protocol (MCP) server in Python with the official MCP Python SDK v2. SDK v2 supports the MCP specification revision 2026-07-28. The same server also serves clients that use earlier revisions.
Copy this checklist and track your progress:
Before you run these commands, check that the project directory does not exist yet or is empty. Run these commands:
Then delete main.py only when it is the unmodified file that uv created. The server goes in server.py.
--no-package, uv makes a src/ package layout, and the tests cannot import server.py.uv init makes the .gitignore file. Do not write a different one.server.py, test_server.py, or .vscode/mcp.json file.Start from this template:
Then change the template for the request of the user:
divide with the tools that the user needs.TypedDict when the client needs machine-readable data. The SDK makes the output schema from the return type.async def for I/O. The SDK runs a sync tool on a worker thread.@mcp.tool(annotations=ToolAnnotations(read_only_hint=True)) (from mcp.types). Use destructive_hint=True for a destructive tool. Add a destructive tool only when the user asks for it. Clients may ignore the hint, so also give the tool a server-side safeguard, such as an explicit confirmation parameter or a confirmation step that asks the user, and limit what it can change.@mcp.resource("users://{user_id}")) and prompts (@mcp.prompt()) only when the user asks for them. Treat resource template parameters as untrusted. Validate their format and check that the caller may access the record before you return data. Map them to file paths only through a fixed allowlist.Treat every tool argument as untrusted model input:
server.py, the client configuration, and every committed file. Keep any local secrets file out of git.Use stdio by default. Read references/streamable-http.md only when the user asks for a remote server or an HTTP server. For an HTTP server, keep the local default binding. Before you bind to any other host, set the transport security settings, and require authentication and TLS for every client.
Put test_server.py next to server.py. Add tests that show each tool rejects malicious or out-of-range input. Write a test for each tool and for each ToolError:
Client needs no subprocess and no port. It connects with the 2026-07-28 revision.mcp package installs anyio, which supplies the anyio marker. Do not add pytest-asyncio.uv run pytest.uv run pytest again.For VS Code, write .vscode/mcp.json:
VS Code launches the command in this file. Tell the user to review it before trusting the workspace. The absolute path is specific to this machine, so commit this file only on purpose, and keep secrets and personal paths out of it. Give secrets to the server through environment variables, not as literal values in this file.
For Claude Desktop, the install command edits the global Claude Desktop configuration outside the project and registers a command that Claude Desktop launches. Tell the user this, get explicit confirmation, back up that configuration, and check for an existing entry with the same name. Then run uv run mcp install server.py.
Tell the user these commands:
uv run server.py starts the stdio server. The server waits for a host on stdin and prints nothing.uv run mcp dev server.py opens the MCP Inspector. The Inspector needs Node.js. The Inspector is optional and is for local development. This command downloads and runs the Inspector from npm, so use a current patched release, keep it on localhost, and stop it after use.SDK v2 and the 2026-07-28 revision changed many SDK v1 patterns. Do not copy SDK v1 examples.
FastMCP and mcp.server.fastmcp. Import the server class with from mcp.server import MCPServer.Context, Image, Audio, Resolve, Elicit, ElicitationResult, AcceptedElicitation, Message, UserMessage, and AssistantMessage from mcp.server.mcpserver.MCPServer argument. Give all other constructor arguments as keyword arguments. Their positional order changed in SDK v2.version. If you do not set it, the server reports an empty version.transport, host, port, json_response, stateless_http, transport_security) to run(), not to MCPServer(...).read_only_hint, structured_content, and is_error.ToolError for an error that the model must read. For other exceptions, the model gets only a generic error message. Keep ToolError messages short, and keep stack traces, SQL, credentials, and host names out of them. Log details to stderr.MCPError for a tool failure. The client gets a protocol error, not a tool result with is_error. Many hosts do not show this error to the model.print(). Log to stderr with the logging module.ctx.elicit(). It fails on a 2026-07-28 connection. For user input during a tool call, annotate a parameter with Resolve(fn). Return Elicit(message, Model) from fn. Treat elicited values as untrusted and validate them with constrained Pydantic fields. Use elicitation only for values that are not secret, such as choices and names, and keep passwords and tokens out of it.await ctx.notify_tools_changed(). A 2026-07-28 connection drops ctx.session.send_tool_list_changed().Client(mcp) in a test runs the lifespan again. Read its object with ctx.request_context.lifespan_context.ctx.session.create_message()): Call the LLM provider API directly.ctx.log(), ctx.info(), and similar methods): Use the standard logging module.ctx.session.list_roots()): Get paths from tool parameters, resource URIs, or the server configuration.Get started with chainctlchainctl skill install public/github/python-mcp-server-generator:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.chainreg.biz/public/github.com/github/skills/python-mcp-server-generator:latest
MIT
HARDENING.md
LICENSE
SKILL.md
references/streamable-http.md