Building Your Own MCP Server for AI
Resources vs Tools
Expose read-only data as an MCP resource and keep mutations on tools — when each shape is right.
A tool is “do this.” A resource is “here is a document at this URI.” If the assistant only needs to read a snapshot, a resource is often cleaner: the host can fetch it without inventing arguments.
Use a resource when
- The data is read-only for this session (a config file, a schema, today’s menu).
- You can name it with a stable URI (
config://app,notes://inbox). - The model should treat it like a file, not like a function with five optional filters.
Use a tool when
- Something changes (create, update, send, restart).
- You need arguments that are not a single URI (date ranges, search strings).
- The call is expensive or rate-limited and should be explicit.
A resource next to a tool
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("notes")
NOTES = {"welcome": "First note. Replace this in the next lesson."}
@mcp.resource("notes://welcome")
def welcome_note() -> str:
'''The welcome note as plain text.'''
return NOTES["welcome"]
@mcp.tool()
def set_welcome(text: str) -> str:
'''Replace the welcome note. Use this when the user wants it changed.'''
NOTES["welcome"] = text.strip()
return "updated"The resource URI is not an HTTP URL. It is an MCP identifier the host understands. After set_welcome, a later read of notes://welcome should show the new text (in-memory only — restart clears it).
Next: reusable MCP prompts.