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.

MCP Server Tutorial: Setup, Examples and First Tool for Beginners
Cristian Da Conceicao
Founder of Picasso IA

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.

A hand plugging a braided USB-C cable into a laptop, the everyday picture behind the one-connector idea of MCP

Host, Client and Server

Three roles show up in every MCP conversation, and beginners often mix them up.

RoleWhat it isWho writes it
HostThe app you talk to, such as a desktop chat app or a code editorThe app vendor
ClientA connector inside the host, one per serverThe host handles it for you
ServerA program that exposes tools, data and promptsYou

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:

  1. The host starts your server, and its client asks, "What can you do?"
  2. The server replies with a list of tools and the schema of each one.
  3. You ask a question. The model decides a tool fits and emits a call with arguments.
  4. The host shows a permission prompt, then forwards the call to your server.
  5. Your function runs, the result travels back, and the model writes the final answer.

An overhead view of a notebook sketch with three boxes joined by arrows, standing in for host, client and server

Tools, Resources and Prompts

A server can offer three kinds of things, and each one has a different owner.

PrimitiveWho triggers itBest forExample
ToolsThe model decidesActions and calculationsCount words, send an email
ResourcesThe app decidesRead-only dataA notes file, a database row
PromptsThe user picksReusable templatesA 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.

A woman at a kitchen island with a laptop and a steaming mug, ready to install her tools on a quiet morning

Python or TypeScript?

Official SDKs exist for several languages. Two are the safest bets for a first server:

SDKInstallPick 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 zodYour 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:

mkdir word-counter
cd word-counter
python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install "mcp[cli]"

The [cli] extra installs the mcp command, which includes a dev runner for quick tests.

Low-angle view of a developer's hands typing at a desk while a dark editor window glows softly behind

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:

  1. The docstring is what the model reads to decide when to call the tool. Write it like a one-line job description.
  2. The type hints (text: str) become the JSON schema that tells the client which arguments exist and what type each one takes.
  3. 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:

npx @modelcontextprotocol/inspector python server.py

A local page opens. Then:

  1. Click Connect to launch your server.
  2. Open the Tools tab and press List Tools. count_words should appear.
  3. Select it, type a sentence into the text field and run it.
  4. Check that the JSON result shows the right counts.

A close-up of a bearded developer in round glasses studying a test result on screen

⚠️ 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:

{
  "mcpServers": {
    "word-counter": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["/absolute/path/to/server.py"]
    }
  }
}

For Claude Code, one command does the same job:

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.

A young man in a denim shirt pointing at his monitor after the first tool call works

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.

Two developers at a shared laptop in a bright coworking space, comparing the output of a new tool

Mistakes That Waste Your Afternoon

MistakeWhat happensFix
Printing to stdoutClient shows a parse errorLog to stderr
Vague docstringThe model ignores your toolSay what it does and when to use it
Relative file paths"File not found" only inside the clientBuild paths from __file__ or use absolute ones
Returning huge payloadsSlow answers, wasted contextReturn a trimmed summary
Too many tools at onceThe model picks the wrong oneStart 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.

A worn brass padlock on a dark oak drawer, a picture of access control for your server

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:

ModelJob
PicassoIA ImageText to image
PicassoIA Image Editor ProEdit an existing image
PicassoIA VideoText or image to video
Seedance 2.5 LiteVideo with audio

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:

ModelGood for
Claude Sonnet 5Careful code and refactors
GPT 5.6 TerraProduction-ready drafts
Kimi K2.6Agent-style tool workflows
Gemini 3.5 FlashQuick iterations

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:

  1. Open your MCP-enabled chat and confirm the PicassoIA connector is active.
  2. 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".
  3. Let the assistant call PicassoIA Image and wait for the result.
  4. Refine with PicassoIA Image Editor Pro instead of starting over.
  5. Animate the best frame with PicassoIA Video.

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.

A creative studio desk filled with printed landscape photographs, the output of a chat-driven image workflow

💡 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.

Share this article