Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a complete, runnable file rather than an isolated handler. The smallest useful Model Context Protocol (MCP) server registers a tool with an explicit input schema, starts a transport, and can be inspected by an MCP client. This guide builds that server in Python, shows the equivalent TypeScript pattern, and then covers local testing, transport choices, and safe extensions.

What an MCP server exposes

MCP standardizes how an application provides context to a large language model. The server surface has three primitives: tools (callable actions), resources (readable context), and prompts (reusable interaction templates). The official Python SDK supports stdio, Streamable HTTP, and SSE transports; the TypeScript SDK supports stdio and Streamable HTTP, with HTTP+SSE retained for backward compatibility. See the official Python SDK documentation and official TypeScript SDK documentation.

Start with one deterministic tool. Once it works in an inspector and a client test, add resources, prompts, remote transport, authentication, and authorization deliberately.

Choose Python or TypeScript

Decision Python SDK TypeScript SDK
Runtime Python 3.10 or newer Node.js (the SDK examples use a modern npm project)
Install uv add "mcp[cli]" or pip install "mcp[cli]" npm install @modelcontextprotocol/sdk zod
Schema style Python type annotations and SDK decorators Zod input and output schemas
Best first transport stdio for a locally spawned process stdio for a locally spawned process
Development run uv run mcp dev server.py Run the compiled or configured Node entry point
Testing path In-memory Client(mcp) or MCP Inspector SDK client example or MCP Inspector

Python is concise for a first sample. TypeScript is a good fit when your existing service, validation, and deployment stack is already Node-based.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a minimal Python MCP server

1. Create the project and install the SDK

  1. Install Python 3.10 or newer.
  2. Create and enter a project directory, then create a virtual environment if you normally isolate Python dependencies.
  3. Install the official package with uv add "mcp[cli]", or use pip install "mcp[cli]".

2. Save a complete server file

Save this as server.py. It registers a deterministic add tool, plus one resource and one prompt so you can see all three MCP primitives in a single, copyable file.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Arithmetic Server")

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

@mcp.resource("config://welcome")
def welcome_resource() -> str:
    """Provide a small, read-only resource."""
    return "Use the add tool for integer addition."

@mcp.prompt()
def explain_addition(a: int, b: int) -> str:
    """Create a prompt that asks for an explanation of a sum."""
    return f"Explain step by step why {a} + {b} equals {a + b}."

if __name__ == "__main__":
    mcp.run()

The function annotations make the tool contract explicit. A client can discover the tool name, description, and input shape instead of guessing how to call it. Keep the first tool deterministic: predictable output makes transport and schema problems easier to isolate.

3. Run it in development mode

From the project directory, run:

uv run mcp dev server.py

The Python getting-started guide documents this command and recommends opening the server in MCP Inspector. Its complete code blocks are intended to be copied and used directly; see the official Python getting-started guide.

4. Inspect the primitives

In Inspector, connect to the development server and verify:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Tools: discover add, enter integer values, and confirm the result.
  • Resources: open config://welcome and check the returned text.
  • Prompts: select explain_addition, provide values, and inspect the generated prompt.

Inspector catches naming, schema, and startup errors before an LLM client adds another layer of variables.

Test the Python server without a subprocess or port

The SDK’s in-memory client connects directly to the server object. The documented approach uses no subprocess, port, or transport, which makes it a fast unit-level check.

import asyncio

from mcp import ClientSession
from mcp.client.inmemory import create_connected_server_and_client_session
from server import mcp

async def main() -> None:
    async with create_connected_server_and_client_session(mcp) as session:
        result = await session.call_tool("add", {"a": 1, "b": 2})
        assert result.structured_content == {"result": 3}
        print(result.structured_content)

if __name__ == "__main__":
    asyncio.run(main())

Run the test from the same environment as the server. A successful assertion proves that registration, argument validation, execution, and structured output work independently of stdio or HTTP.

Equivalent TypeScript server

Install and define the server

In a Node project, install the official packages:

npm install @modelcontextprotocol/sdk zod

The TypeScript SDK’s tool pattern supplies a name, title or description, an input schema, an output schema, and a result containing text plus structured content.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

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

server.registerTool(
  "add",
  {
    title: "Add integers",
    description: "Add two integers and return the sum.",
    inputSchema: { a: z.number().int(), b: z.number().int() },
    outputSchema: { result: z.number().int() },
  },
  async ({ a, b }) => {
    const result = a + b;
    return {
      content: [{ type: "text", text: String(result) }],
      structuredContent: { result },
    };
  },
);

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

The documented minimal connection is an McpServer followed by new StdioServerTransport() and await server.connect(transport). Runnable examples are included with the TypeScript SDK. Keep protocol messages on the expected stdio channel; ordinary diagnostic logging should not corrupt that stream.

Choose a transport deliberately

Transport Use it when Operational shape
stdio A local client can spawn your server The client starts a process and exchanges protocol messages through standard input and output. It is the simplest local integration.
Streamable HTTP A remote client needs network access Run an HTTP endpoint that clients can reach; plan for deployment, connectivity, and any session or state behavior your application requires.
HTTP+SSE You must support an older integration The TypeScript documentation lists it as backward-compatible support rather than the preferred new remote choice.

The current TypeScript guidance recommends Streamable HTTP for remote servers and stdio for local integrations. Do not move to HTTP merely to make a sample look more advanced: first prove the protocol with stdio and Inspector.

Extend the sample safely

Make schemas stricter

Declare bounds, formats, and required fields in the schema rather than relying on prompt wording. In TypeScript, express those constraints with Zod; in Python, use precise annotations and add explicit checks for domain rules that annotations cannot express.

Return useful errors

Validate inputs before performing side effects. Return an actionable protocol error for invalid values, and log the underlying exception where your host environment keeps server logs. Avoid exposing secrets, stack traces, or internal paths in tool results.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate read and write capabilities

Resources should represent readable context; tools should perform actions. Give destructive operations distinct names and require the calling application to provide whatever confirmation policy its environment uses. The official material establishes the primitives and transports, but it does not provide a complete production security control set; treat authentication, authorization, secret storage, auditing, and rate limits as deployment-specific work that needs its own review.

Add remote access only after local tests pass

For Streamable HTTP, define how clients reach the endpoint, how sessions or state are handled, and how access is authenticated. Test malformed requests, dropped connections, duplicate calls, and server restarts before exposing the service outside the local machine.

Troubleshoot common failures

The client cannot start the server

  • Confirm Python is 3.10 or newer, or that Node can execute the TypeScript build.
  • Run the command from the directory containing server.py or the configured JavaScript entry point.
  • Check that the SDK was installed in the same environment used to launch the process.

The tool does not appear in Inspector

  • Verify the decorator (Python) or registerTool call (TypeScript) executes during startup.
  • Check that the tool name is stable and that the input schema is valid.
  • Restart Inspector after changing registration code.

Arguments fail validation

  • Send integers to the Python add function, not quoted strings.
  • In TypeScript, satisfy both Zod schemas: values must be integers and the output must contain an integer result.
  • Inspect the discovered schema in Inspector rather than guessing field names.

The process exits immediately

  • For Python, ensure the if __name__ == "__main__" block calls mcp.run().
  • For TypeScript, ensure the top-level connection is reached and that the process is not being terminated by an unhandled startup exception.
  • Keep protocol output separate from diagnostic messages.

In-memory tests pass but a real client fails

The in-memory test excludes transport and process boundaries by design. Reproduce the call in Inspector, then test the exact stdio command or HTTP endpoint used by the target client. This narrows the problem to transport configuration instead of tool logic.

Performance, reliability, and cost considerations

  • Start with deterministic, short-running tools so failures are obvious and retries are safe to reason about.
  • Keep resource payloads focused; large context responses increase client-side processing even when the protocol call succeeds.
  • For remote HTTP, measure the behavior that matters to your application—connection setup, server processing, and downstream calls—rather than assuming local stdio timings apply.
  • Do not claim reliability or throughput from this minimal sample. The official SDK pages describe supported transports and examples, not a universal performance benchmark.
  • Pin and review SDK versions in your project, and rerun Inspector and client tests after upgrades.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your MCP tool needs website screenshots, an API can remove browser orchestration from the server. ScreenshotNeo is a website screenshot API and MCP server: one GET request returns a PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for request options. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The service also supports full-page lazy-image loading, CSS-selector element capture, dark mode, device and viewport settings, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work.

Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get an API key.

What to verify before calling the sample complete

  • The server starts with the documented runtime and package command.
  • Inspector discovers every primitive you intended to expose.
  • Valid and invalid tool inputs produce the expected structured result or error.
  • An in-memory test passes before transport debugging begins.
  • Your chosen transport matches the deployment: stdio locally, Streamable HTTP remotely, and HTTP+SSE only when compatibility requires it.
  • Any remote deployment has a documented plan for authentication, authorization, secrets, logging, and failure recovery.

Frequently Asked Questions

Can an MCP server expose only tools?

Yes. Tools are the only primitive required for a minimal action-oriented server; resources and prompts are optional additions.

Do I need a network port to test a Python server?

No. The documented in-memory client connects directly to the server object without a subprocess, port, or transport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which transport should a remote MCP server use?

The current TypeScript documentation recommends Streamable HTTP for remote servers. stdio is intended for local clients that spawn the process; HTTP+SSE remains for backward compatibility.

Where can I find complete SDK examples?

Use the official Python pages at https://py.sdk.modelcontextprotocol.io/get-started/ and https://py.sdk.modelcontextprotocol.io/, and the TypeScript SDK and server guide at https://ts.sdk.modelcontextprotocol.io/ and https://ts.sdk.modelcontextprotocol.io/server.

The Bottom Line

Write one complete, schema-driven tool first, run it over stdio, inspect it, and test it in memory. Only then add resources, prompts, remote HTTP, and production security controls.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.