This guide explains how to extend and create new MCP servers for the TTA project.
When creating MCP servers, it’s important to distinguish between development and production servers:
Development Servers: Used for testing, learning, and development purposes. These servers should not be used in production environments.
Production Servers: Designed for use in production or prototype environments. These servers should be robust, well-tested, and secure.
The examples in this guide can be used for both development and production servers, but you should clearly label your servers as either development or production.
To create a new MCP server, you need to:
from fastmcp import FastMCP, Context
from typing import Dict, List, Any, Optional
mcp = FastMCP(
"My Server",
description="My MCP server description",
dependencies=["fastmcp", "other-dependencies"]
)
@mcp.tool()
def my_tool(param: str) -> str:
"""My tool description"""
return f"Result: {param}"
@mcp.resource("my://resource")
def my_resource() -> str:
"""My resource description"""
return "Resource content"
@mcp.prompt()
def my_prompt() -> str:
"""My prompt description"""
return "Prompt content"
if __name__ == "__main__":
mcp.run()
Tools are functions that can be called by the LLM. They should:
Example:
@mcp.tool()
def search_knowledge_graph(query: str, limit: int = 10) -> str:
"""
Search the knowledge graph for entities matching the query.
Args:
query: The search query
limit: Maximum number of results to return (default: 10)
Returns:
A formatted string containing the search results
"""
# Implementation...
return results
Resources are file-like data that can be read by clients. They should:
Example:
@mcp.resource("knowledge://locations/{location_id}")
def get_location(location_id: str) -> str:
"""
Get information about a specific location in the knowledge graph.
Args:
location_id: The ID of the location
Returns:
A formatted string containing information about the location
"""
# Implementation...
return formatted_location_info
Prompts are reusable templates for LLM interactions. They should:
Example:
@mcp.prompt()
def location_exploration_prompt(location_name: str) -> str:
"""
Create a prompt for exploring a location in the game world.
Args:
location_name: The name of the location to explore
Returns:
A prompt for exploring the location
"""
return f"""
I'd like to explore {location_name} in the game world.
Please describe what I can see, hear, and experience in this location.
What characters might I encounter? What items might I find?
"""
To add new capabilities to an existing MCP server:
from examples.mcp.basic_server import mcp
@mcp.tool()
def new_tool() -> str:
"""New tool description"""
return "New tool result"
if __name__ == "__main__":
mcp.run()
The AgentMCPAdapter can be customized to expose additional agent capabilities:
from src.mcp.agent_adapter import AgentMCPAdapter
class CustomAgentMCPAdapter(AgentMCPAdapter):
def __init__(self, agent, **kwargs):
super().__init__(agent, **kwargs)
# Register additional tools
self._register_custom_tools()
def _register_custom_tools(self):
@self.mcp.tool()
def custom_tool() -> str:
"""Custom tool description"""
return "Custom tool result"
When creating MCP servers, consider these security best practices:
To ensure good performance:
Good documentation is essential:
For production servers, ensure:
The Context object provides access to MCP capabilities:
@mcp.tool()
async def long_task(files: list[str], ctx: Context) -> str:
"""Process multiple files with progress tracking"""
for i, file in enumerate(files):
ctx.info(f"Processing {file}")
await ctx.report_progress(i, len(files))
# Read another resource if needed
data = await ctx.read_resource(f"file://{file}")
return "Processing complete"
FastMCP provides an Image class for handling images:
from fastmcp import FastMCP, Image
from PIL import Image as PILImage
@mcp.tool()
def create_thumbnail(image_path: str) -> Image:
"""Create a thumbnail from an image"""
img = PILImage.open(image_path)
img.thumbnail((100, 100))
# FastMCP automatically handles conversion and MIME types
return Image(data=img.tobytes(), format="png")
By default, MCP servers use the stdio transport, but you can specify other transports:
if __name__ == "__main__":
mcp.run(transport="http", host="localhost", port=8000)
This allows you to expose your MCP server over HTTP, which can be useful for certain integration scenarios.