From economist-agents
Standards for building MCP servers in this project. Use when creating a new MCP server, when reviewing MCP server code, when debugging import or tool registration issues.
npx claudepluginhub oviney/economist-agentsThis skill uses the workspace's default tool permissions.
Every MCP server must use the official SDK import (`from mcp.server.fastmcp import FastMCP`), return structured error dicts instead of exceptions, and follow the template below. This skill exists because agents consistently use the wrong import.
Applies Acme Corporation brand guidelines including colors, fonts, layouts, and messaging to generated PowerPoint, Excel, and PDF documents.
Guides strict Test-Driven Development (TDD): write failing tests first for features, bugfixes, refactors before any production code. Enforces red-green-refactor cycle.
Share bugs, ideas, or general feedback.
Every MCP server must use the official SDK import (from mcp.server.fastmcp import FastMCP), return structured error dicts instead of exceptions, and follow the template below. This skill exists because agents consistently use the wrong import.
mcp_servers/python-qualitytestingquality-gates# CORRECT — official MCP SDK
from mcp.server.fastmcp import FastMCP
# WRONG — third-party package, different API
from fastmcp import FastMCP
Dependency: mcp>=1.0.0 in requirements.txt. Never add fastmcp as separate dependency.
#!/usr/bin/env python3
"""<Name> MCP Server.
<One-line description.>
Tools:
<tool_name>: <what it does>
Usage:
python -m mcp_servers.<module_name>
"""
import logging
from typing import Any
from mcp.server.fastmcp import FastMCP
logger = logging.getLogger(__name__)
mcp = FastMCP("<server-name>")
@mcp.tool()
def my_tool(param: str, count: int = 5) -> dict[str, Any]:
"""One-line description of the tool.
Args:
param: What this parameter controls.
count: How many results to return.
Returns:
Dict with results or error information.
"""
try:
result = do_work(param, count)
return {"success": True, "data": result}
except SpecificError as e:
logger.exception("Known failure mode")
return {"success": False, "error": str(e)}
except Exception as e:
logger.exception("Unexpected error in my_tool")
return {"success": False, "error": f"Unexpected: {e}"}
if __name__ == "__main__":
mcp.run(transport="stdio")
from mcp.server.fastmcp import FastMCP@mcp.tool() with parentheses{"success": False, "error": "..."}mcp.run(transport="stdio") under __main__ guard"article-evaluator")mcp_servers/ directorylogging.getLogger(__name__), not print()tests/test_mcp_servers/test_<server_name>.pymcp._tool_manager._tools for tool introspection (no public .tools attribute)Register in .mcp.json:
{
"mcpServers": {
"article-evaluator": {
"command": "python",
"args": ["-m", "mcp_servers.article_evaluator_server"],
"transport": "stdio"
}
}
}
| Rationalization | Reality |
|---|---|
"The fastmcp package has better ergonomics" | It's a different package with an incompatible API — this caused 4/5 Copilot PRs to fail in Sprint 18 |
"We can skip the parentheses on @mcp.tool" | Inconsistent with SDK docs and breaks in some versions |
| "Raising exceptions is simpler than returning dicts" | Exceptions crash the MCP client connection; structured errors keep the session alive |
| "We don't need all those test categories" | Registration tests catch the import bug; error tests catch exception leaks — both are Sprint 18 bugs |
from fastmcp import FastMCP anywhere in the codebasefastmcp listed as a dependency in requirements.txt@mcp.tool without parenthesesprint() used instead of logging.getLogger()mcp_servers/ directoryfrom mcp.server.fastmcp import FastMCP — evidence: grep confirms no from fastmcp@mcp.tool() with parentheses — evidence: grep @mcp.tool[^(] returns emptypytest --cov output.mcp.json — evidence: JSON entry matches server name