ScreenshotNeo

BlogAI agents

MCP Server Examples for Developers

Runnable Python and TypeScript MCP server examples, transport choices, host integration, troubleshooting, and production guidance for developers.

By the ScreenshotNeo team30 September 20268 min read

MCP Server Examples for Developers

Short answer: the simplest MCP server is a small typed program that registers one or more tools, resources, or prompts, then connects them to a transport. Use stdio when an MCP host launches your server as a local process. Use Streamable HTTP when clients connect to a remote service. Python and TypeScript are the clearest starting points because their official SDKs are Tier 1 and provide concise examples.

This guide gives you runnable Python and TypeScript servers, explains the protocol pieces, shows how to inspect and connect them, and covers transport, schemas, deployment, reliability, security, performance, and common failures.

1. What an MCP server exposes

Model Context Protocol servers expose three kinds of capabilities:

An MCP host discovers typed tools and resources, sends a request, and receives a structured result.
An MCP host discovers typed tools and resources, sends a request, and receives a structured result.
Capability Use it for Example
Tools Actions with typed inputs and outputs Query an API, create a ticket, run a calculation
Resources Readable data addressed by a URI greeting://Ada or a document snapshot
Prompts Reusable message templates A code-review or incident-analysis workflow

The SDK derives request schemas from your declarations, validates incoming arguments, and handles protocol messages. A host such as Claude Code, VS Code, Cursor, GitHub Copilot, or your own application connects as an MCP client.

2. Minimal MCP server in Python

The official Python SDK v2 is the current stable line and requires Python 3.10 or newer. Install the CLI extras, create a file, and run it through the Inspector.

Install and run

uv init mcp-demo
cd mcp-demo
uv add "mcp[cli]"
# Or: pip install "mcp[cli]"

Create server.py:

from mcp.server import MCPServer

mcp = MCPServer("Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"

Open it in the MCP Inspector:

uv run mcp dev server.py

The Inspector lets you list capabilities, invoke add, and read a greeting resource. The annotations are significant: int arguments become part of the tool schema, so the client can discover the contract before calling it.

Returning structured data

For a larger result, return a JSON-serializable object and document the fields in the function docstring. Keep errors actionable: raise an exception with a message that tells the caller what input was invalid or which dependency failed.

from mcp.server import MCPServer

mcp = MCPServer("Catalog")

@mcp.tool()
def find_product(sku: str) -> dict:
    """Return product information for an SKU."""
    if not sku.strip():
        raise ValueError("sku must not be empty")
    return {
        "sku": sku,
        "name": "Example product",
        "available": True,
    }

3. Minimal MCP server in TypeScript

The TypeScript SDK v2 uses the @modelcontextprotocol/server package. The normal sequence is: create an McpServer, register capabilities, create a transport, and connect the server to it.

Local stdio server

mkdir mcp-ts-demo
cd mcp-ts-demo
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D typescript tsx @types/node

Create server.ts:

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

const server = new McpServer({ name: "demo", version: "1.0.0" });

server.tool(
  "add",
  "Add two numbers",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }],
  })
);

server.resource(
  "greeting",
  "greeting://{name}",
  async (uri) => ({
    contents: [{ uri: uri.href, text: `Hello, ${uri.pathname.slice(1)}!` }],
  })
);

const transport = new StdioServerTransport();
await server.connect(transport);

Run it with:

npx tsx server.ts

When a host starts a stdio server, it communicates over the process’s standard input and output. Never write debug messages to stdout; they can corrupt the protocol stream. Send diagnostics to stderr instead.

4. Choosing stdio or Streamable HTTP

Transport Best fit Advantages Trade-offs
stdio Local, process-spawned integrations Simple setup, no listening port, easy per-user configuration The host owns the process lifecycle; remote clients cannot connect directly
Streamable HTTP Remote network services Central deployment, multiple clients, normal HTTP infrastructure Needs authentication, routing, timeouts, and observability

Streamable HTTP can be stateful or stateless. A session ID generator enables stateful sessions and resumability. Passing undefined selects stateless mode, which is simpler but does not support resumability.

Remote TypeScript pattern

import { McpServer } from "@modelcontextprotocol/server";
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/server/node";

const server = new McpServer({ name: "remote-demo", version: "1.0.0" });

// Register tools, resources, and prompts here.

const transport = new NodeStreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID(),
});

await server.connect(transport);

Use stateless mode when each request can be handled independently and you do not need resumable sessions. Stateful mode is useful when a workflow spans multiple requests and the client must resume after an interruption.

5. Connecting a host

A host normally receives a command and argument list, starts your process, and speaks MCP over the selected transport. A generic stdio configuration looks like this:

{
  "mcpServers": {
    "demo": {
      "command": "uv",
      "args": ["run", "mcp", "run", "/absolute/path/server.py"]
    }
  }
}

For Node.js, use a command such as node or npx and provide the compiled or source entry point. GitHub Copilot’s SDK documents the same pattern for Node.js/TypeScript and Python: configure the server command and its arguments, then let the host launch it.

Checklist for host configuration

  1. Use absolute paths for scripts and working directories.
  2. Put secrets in environment variables rather than command arguments.
  3. Keep stdout reserved for MCP messages.
  4. Verify the configured interpreter exists in the host’s environment.
  5. Run the exact command manually before adding it to the host.

6. Designing useful tools

Make schemas narrow

Expose the smallest input contract that completes the task. Prefer an enum or bounded string over an unrestricted command. Validate IDs, URLs, paths, and pagination values before making an external request.

Make results predictable

Return a stable shape with a concise human-readable summary and machine-readable fields where your SDK supports them. Include enough context for the model to decide what to do next, but avoid returning an entire database row when three fields are sufficient.

Separate read and write actions

Use distinct tools for inspection and mutation. Give destructive operations explicit names and require a confirmation argument or a dry-run mode. This makes host policies and human review easier.

Resources versus tools

Use a resource for addressable information that clients read. Use a tool for an operation that computes, queries, changes state, or needs arguments. A page snapshot, configuration document, or generated report can be a resource; “create deployment” should be a tool.

7. Runnable screenshot tool example with ScreenshotNeo

If your MCP server needs website images, you can wrap a screenshot API as a typed tool instead of managing a browser yourself. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It supports screenshots and PDFs, and its MCP tools include take_screenshot, get_page_info, and capture_pdf.

A screenshot workflow can remove common overlays before capturing the page.
A screenshot workflow can remove common overlays before capturing the page.

The following Python tool calls the ScreenshotNeo endpoint. See the ScreenshotNeo API documentation for the complete option list.

import requests
from mcp.server import MCPServer

mcp = MCPServer("screenshots")

@mcp.tool()
def take_screenshot(url: str, access_key: str) -> str:
    """Capture a webpage and save the returned image."""
    r = requests.get(
        "https://api.screenshotneo.com/v1/shot",
        params={"access_key": access_key, "url": url},
        timeout=90,
    )
    r.raise_for_status()
    filename = "shot.webp"
    open(filename, "wb").write(r.content)
    return filename

Equivalent direct calls:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Or skip the browser setup

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. It supports full-page and element screenshots, device presets, custom viewports, dark mode, retina scale, JavaScript and CSS, waits, request blocking, cookies, headers, geolocation, PDFs, caching, signed links, webhooks, bulk capture, and an MCP server for AI agents.

There are 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

8. Performance, reliability, and cost

  • Keep tools bounded: impose timeouts, maximum result sizes, and pagination.
  • Reuse clients: create HTTP clients once where the SDK and runtime allow it.
  • Control concurrency: cap parallel upstream calls so one model request cannot exhaust sockets or rate limits.
  • Make retries selective: retry transient network failures with backoff, but do not blindly retry validation or authorization errors.
  • Design for restart: stdio processes can exit and be relaunched by the host; remote servers should be stateless where possible or persist session state explicitly.
  • Measure the boundary: log request duration, tool name, status, and correlation ID to stderr or your service logger. Never log API keys or sensitive arguments.

For screenshot workloads, cache repeated URLs with a deliberate TTL, use bulk capture for batches of up to 100 URLs per call, and choose image format and dimensions based on the consumer. Only successful clean captures are billed by ScreenshotNeo, which helps separate application failures from paid work.

9. Troubleshooting common errors

Symptom Likely cause Fix
Host reports invalid JSON or disconnects Logs were written to stdout Write diagnostics to stderr and keep stdout exclusively for MCP traffic.
Tool is not listed Registration code did not run, or the process exited early Run the file directly, inspect stderr, and verify the server connects after registration.
Arguments fail validation Client input does not match the declared schema Check Python annotations or Zod definitions and provide an example valid call.
Command not found in a host The host has a different PATH or virtual environment Use an absolute interpreter path or configure the host environment explicitly.
HTTP client cannot resume Server is stateless Configure a session ID generator and preserve session state, or accept stateless behavior.
Screenshot response is not an image Invalid key, URL, timeout, bot check, or blank page Check the HTTP status and X-Page-Verdict/X-Billed headers before saving the body.

10. Security and deployment checklist

  • Authenticate remote HTTP clients and authorize each tool.
  • Allowlist outbound hosts when tools fetch URLs.
  • Never pass arbitrary shell commands from model-controlled input.
  • Redact credentials, cookies, authorization headers, and personal data from logs.
  • Set request, upstream, and idle timeouts.
  • Pin SDK versions and review protocol changes before upgrades.
  • Use the official server collection as reference material only. Its README states: “They are meant to serve as educational examples for developers building their own MCP servers, not as production-ready solutions.”

11. Frequently asked questions

Which language should I start with?

Choose Python for the shortest typed example and fast experimentation. Choose TypeScript when your host, deployment, or existing service is JavaScript-based.

Can one server expose tools and resources?

Yes. The minimal Python example above registers both in one file, and the TypeScript SDK follows the same model.

Is Streamable HTTP required for remote use?

It is the transport described for remote network services. Use stateful sessions when resumability matters; use stateless mode when independent requests are sufficient.

How do I test without a full AI host?

Run the Python server with uv run mcp dev server.py and use the MCP Inspector to list and invoke capabilities.

Where can I find larger examples?

The official TypeScript repository includes runnable, self-verifying client/server pairs for Node.js, Bun, and Deno. Treat the official server collection as educational reference material and add your own authentication, validation, monitoring, and deployment controls.