Large Language ModelsGenerate imagesGenerate videos
MCP Server Tutorial: Setup, Examples and First Tool for Beginners
A beginner tutorial that takes you from an empty folder to a working MCP server. Set up Python, write your first tool in 15 lines, test it in the Inspector, connect it to a real client, and see how image and video generation plug in through the same protocol.
Your AI assistant can write a sonnet about spreadsheets, but ask it how big a file on your laptop is and it shrugs. The Model Context Protocol closes that gap. An MCP server is a small program that hands an assistant real abilities: read a folder, query a database, call an API, even generate an image. This tutorial builds one from an empty folder, ending with a working first tool in about twenty minutes and no prior protocol experience required.
You will install the SDK, write a tool, test it in the Inspector, plug it into a real client, and then see how the same pattern powers image and video generation on PicassoIA. Everything runs on plain Python, so if you can read a function, you can follow along.
What an MCP Server Does
The USB-C Analogy
Before USB-C, every gadget demanded its own cable. MCP does for AI what that single port did for hardware. Without it, each assistant needed custom code for each service, which meant N assistants times M services worth of glue. With it, you write one server and every MCP-compatible client can use it.
Anthropic introduced the protocol in late 2024, and since then many chat apps, code editors and agent frameworks have adopted it. That adoption is the real reason to bother: a tool you build today is not locked to one product, and the skills you pick up transfer to every client that speaks the protocol.
Host, Client and Server
Three roles show up in every MCP conversation, and beginners often mix them up.
Role
What it is
Who writes it
Host
The app you talk to, such as a desktop chat app or a code editor
The app vendor
Client
A connector inside the host, one per server
The host handles it for you
Server
A program that exposes tools, data and prompts
You
Messages travel as JSON-RPC 2.0. A local server speaks over stdio: the host launches your script as a child process and trades messages through its input and output streams. A remote server speaks over Streamable HTTP, which is how hosted connectors work.
Here is what happens when you ask a question:
The host starts your server, and its client asks, "What can you do?"
The server replies with a list of tools and the schema of each one.
You ask a question. The model decides a tool fits and emits a call with arguments.
The host shows a permission prompt, then forwards the call to your server.
Your function runs, the result travels back, and the model writes the final answer.
Tools, Resources and Prompts
A server can offer three kinds of things, and each one has a different owner.
Primitive
Who triggers it
Best for
Example
Tools
The model decides
Actions and calculations
Count words, send an email
Resources
The app decides
Read-only data
A notes file, a database row
Prompts
The user picks
Reusable templates
A code review request
💡 Build tools first. They are the most widely supported primitive, and one working tool teaches you most of what the protocol asks of you.
Set Up Your Environment
What You Need
Gather four things before typing any code:
Python 3.10 or newer. Check with python --version.
Node.js 18 or newer, only for the Inspector debugger, which runs through npx.
A terminal and any code editor, even a plain one.
An MCP client, such as Claude Desktop, Claude Code or a compatible editor.
Windows, macOS and Linux all work. Only the command that activates the virtual environment changes, and the code below is identical on every system.
Python or TypeScript?
Official SDKs exist for several languages. Two are the safest bets for a first server:
SDK
Install
Pick it when
Python (mcp)
pip install "mcp[cli]"
You want the shortest path. Type hints become the tool schema automatically
TypeScript (@modelcontextprotocol/sdk)
npm install @modelcontextprotocol/sdk zod
Your project already lives in Node, or you plan to deploy to a web runtime
This tutorial uses Python. The concepts, from tools to transports, carry over to every other SDK unchanged.
Write Your First Tool
Create the Project
Make a folder, add an isolated environment and install the SDK:
The [cli] extra installs the mcp command, which includes a dev runner for quick tests.
Your Tool in 15 Lines
Create server.py with this content:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("word-counter")
@mcp.tool()
def count_words(text: str) -> dict:
"""Count the words, characters and lines in a piece of text."""
return {
"words": len(text.split()),
"characters": len(text),
"lines": len(text.splitlines()),
}
if __name__ == "__main__":
mcp.run(transport="stdio")
Three details do all the work:
The docstring is what the model reads to decide when to call the tool. Write it like a one-line job description.
The type hints (text: str) become the JSON schema that tells the client which arguments exist and what type each one takes.
The return value is serialized and sent back to the model as the tool result.
When a client connects, it asks your server for a list of tools. FastMCP answers with the name count_words, your docstring as the description and an input schema generated from the signature: an object with one required string property called text. That tiny JSON document is everything the model knows about your function, which is why naming and wording matter more than clever code.
💡 If a tool never gets called, the cause is almost always a vague docstring, not a bug in your code.
Test It in the Inspector
Do not connect a chat app yet. Debug in the MCP Inspector, a browser-based test bench:
Open the Tools tab and press List Tools. count_words should appear.
Select it, type a sentence into the text field and run it.
Check that the JSON result shows the right counts.
⚠️ Never use print() in a stdio server. Standard output is the message channel, and a stray print corrupts it. Send logs to stderr or use Python's logging module.
Connect It to a Real Client
Once the Inspector shows green, register the server with a client. For Claude Desktop, add this to the claude_desktop_config.json file:
claude mcp add word-counter -- /absolute/path/to/.venv/bin/python /absolute/path/to/server.py
Restart the app fully, then ask: "How many words are in this paragraph?" followed by some text. The client asks permission, runs count_words and answers with the exact numbers instead of a guess.
If nothing shows up, check these in order:
Absolute paths only. Relative paths break because the host launches the process from its own folder.
Point at the venv interpreter. A bare python often finds a different install without the SDK.
Quit the app fully. Closing the window usually leaves it running in the tray.
Read the logs. Clients write per-server logs that show the exact traceback.
When a local script is no longer enough, switch the transport with mcp.run(transport="streamable-http"), host it behind HTTPS and add authentication. The tool code stays exactly the same, which is the quiet payoff of building on a protocol.
Three Examples Worth Copying
A Read-Only Resource
Resources expose data by URI. This one serves a notes file:
from pathlib import Path
@mcp.resource("notes://today")
def todays_notes() -> str:
"""Return the contents of today's notes file."""
return Path("notes/today.md").read_text(encoding="utf-8")
The app can attach it as context without the model calling anything. Resources can also use URI templates such as notes://{date}, so one function serves a whole family of files.
A Prompt Template
A prompt is a reusable starting point the user picks from a menu. Unlike a tool, the model never decides to run it: the user chooses it, and the result becomes the opening message of the conversation.
@mcp.prompt()
def review_code(code: str) -> str:
"""Ask for a short, friendly code review."""
return f"Review this code and list the three most important fixes:\n\n{code}"
A Tool That Calls an API
Most real servers wrap a web service. This one checks whether a site is up:
import httpx
@mcp.tool()
async def check_site(url: str) -> str:
"""Return the HTTP status code of a website."""
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(url, follow_redirects=True)
return f"{url} answered with status {response.status_code}"
httpx already comes with the SDK, and declaring the function async lets the server stay responsive while it waits on the network. Network calls fail, so catch the exception and return a short, readable message. A model that sees "the site timed out after 10 seconds" can adapt and try something else, while a raw traceback just confuses it.
Mistakes That Waste Your Afternoon
Mistake
What happens
Fix
Printing to stdout
Client shows a parse error
Log to stderr
Vague docstring
The model ignores your tool
Say what it does and when to use it
Relative file paths
"File not found" only inside the client
Build paths from __file__ or use absolute ones
Returning huge payloads
Slow answers, wasted context
Return a trimmed summary
Too many tools at once
The model picks the wrong one
Start with three to five focused tools
Naming helps as much as the table above. Pick verbs that say what happens, such as count_words or check_site, keep each tool to one job, and limit arguments to the few the model really needs. A tool called process with six optional fields is an invitation to guess wrong.
Lock Down Access
A tool is code the model can run on your machine, so treat it with respect:
Prefer read-only. Add write or delete tools only when you truly need them.
Validate inputs. A file tool should refuse paths outside one chosen folder.
Keep secrets out of code. Pass tokens through the env field of the client config, which keeps them out of your repository.
Read the permission prompt. Do not approve a tool call you cannot explain.
Connect PicassoIA Through MCP
What the Connector Offers
Servers are not limited to local scripts. PicassoIA exposes image and video generation to MCP clients, so an assistant can create media straight from a chat. The connector and the developer API share the same four models:
Under the hood the API follows a familiar pattern: create a prediction, poll for its status, then fetch the result. Requests use a Bearer token, prompts can run up to 4,000 characters, and an account runs up to 5 predictions at once, shared across tokens and MCP connections. Manage connections from the MCP page in your PicassoIA account, and check your plan for what MCP access includes.
Draft Tool Code With an LLM
You do not have to write every tool by hand. Large language models turn a plain sentence into a first draft that you can test in the Inspector:
Describe the tool in one sentence, ask for a FastMCP version, then run it in the Inspector before you trust it. Models write plausible code, and the Inspector is how you find the plausible-but-wrong parts.
Generate Images From a Chat
Once connected, the workflow is short:
Open your MCP-enabled chat and confirm the PicassoIA connector is active.
Describe the shot with concrete details: subject, lens, light and mood. A line like "a ceramic mug on an oak desk, soft window light from the left, 50mm lens" beats "nice coffee photo".
Let the assistant call PicassoIA Image and wait for the result.
Treat each prompt as a short checklist: subject and action, setting, light direction, lens and surface texture. Keep one idea per image, and generate in small batches so you stay under the five-at-once limit while the first results are still rendering.
💡 Generation is asynchronous. If a client reports "pending", it is polling, not failing.
Try It on Picasso IA Today
You now have the pieces: a server, a first tool, an Inspector test and a client connection. Add a second tool this week, turn one of your own scripts into a server, and watch how quickly your assistant becomes useful.
Then put the creative side to work. Head to Picasso IA, pick a model such as PicassoIA Image, and generate your first image from a single sentence. Experiment with lighting, lenses and moods, send the best result to Seedance 2.5 Lite to bring it to life, and see how far one good prompt goes.