← Back to blog

Build a Minimal MCP Server in Minutes for Devs: Quickstart & Publish

September 24, 2026
Build a Minimal MCP Server in Minutes for Devs: Quickstart & Publish

An MCP server is a lightweight program that exposes tools, resources, and prompts to AI models over the Model Context Protocol, so an agent can call real functions instead of guessing at text. The fastest path forward: pick the Python SDK or TypeScript SDK, run the quickstart, and inspect it with the MCP Inspector before writing a single line of production code.


TL;DR:

  • Use the MCP Inspector to test server responses, ensuring correct tool registration, schemas, and response handling before integrating with hosts or production systems.
  • Choose transport protocols based on deployment: stdio for local development, Streamable HTTP for remote or cloud hosting, and implement strict OAuth 2.1 practices for security in HTTP endpoints.
  • Version your tool schemas explicitly to avoid silent breaking changes, and keep deprecation plans transparent with proper documentation and release management.
  • Building and maintaining a production MCP server requires ongoing effort, so using a hosted solution like Quikturn's MCP server can save time on security, schema management, and data verification.

Quikturn
Streamline Finance Data Workflows
Quikturn helps finance professionals create data-driven decks, market maps, and slides with verified company information in seconds.
Explore Quikturn

Table of Contents

MCP Server Guide: Build a Minimal Working Server in Minutes

The fastest way to understand MCP is to build the smallest possible server and watch it respond. Skip the theory for now. Below is a runnable example in both Python and TypeScript, because your choice of language matters less than getting something working today.

Python setup (using uv, the package manager most MCP tutorials assume):

uv init weather-server
cd weather-server
uv add "mcp[cli]"

Then a minimal server, saved as server.py:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
def get_forecast(city: str) -> str:
    """Return a one-line weather forecast for a city."""
    return f"{city}: 72°F, partly cloudy."

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

Node/TypeScript setup, using the MCP TypeScript SDK:

npm init -y
npm install @modelcontextprotocol/sdk zod

A comparable minimal server:

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: "weather", version: "1.0.0" });

server.tool("get_forecast", { city: z.string() }, async ({ city }) => ({
  content: [{ type: "text", text: `${city}: 72°F, partly cloudy.` }],
}));

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

Run the Python version with uv run mcp dev server.py, or run the Node version with node server.js. Both commands start your server on the stdio transport, which is the default for local development.

Once it's running, follow these steps to confirm it actually works:

  1. Launch the MCP Inspector with npx @modelcontextprotocol/inspector.
  2. Point it at your server command (for example, uv run mcp dev server.py).
  3. Open the Inspector UI, list your tools, and call get_forecast with a test city.
  4. Confirm the response body matches what your handler returns, with no extra text mixed in.

If nothing shows up, check these first: is your SDK version current, are you accidentally printing to stdout instead of stderr, and does your process have permission to bind or execute in your current shell? Nine times out of ten, a broken quickstart traces back to one of those three.

Pro Tip: Keep your first tool absurdly simple, like the forecast example above. Debugging transport and discovery issues is much easier when your business logic isn't also a suspect.

Core MCP Concepts and Architecture

MCP servers expose exactly three primitives: tools, resources, and prompts. Understanding what each one does, and what it is not for, prevents most of the design mistakes that show up later.

Tools are callable functions, like the get_forecast example above. Resources are readable data, addressable by URI, similar to files or database rows an agent can fetch without side effects. Prompts are reusable templates that shape how a model should approach a task, letting you standardize common workflows across every client that connects.

The protocol runs on a client-host-server model: a host application, such as an AI-powered code editor or agent framework, creates a dedicated MCP client for each server it talks to. That separation is what keeps servers model-agnostic — the same weather server works whether it's plugged into a chat app or an autonomous agent, since your server code never touches model-specific logic.

Discovery happens through server/discover, which tells the connecting client what tools, resources, and prompts are available before any of them get called. The protocol itself is stateless, with per-request _meta fields carrying context like request IDs or tracing data rather than persistent session state living on the server.

Design-wise, the strongest guidance is also the simplest: build one clear operation per tool rather than a single tool with a dozen optional parameters. A tool named search_companies that takes a query string is easier for a model to select correctly than a generic database_query tool that accepts raw SQL. OpenAI's developer guidance on this point is blunt: treat tool descriptions and schemas as part of the model-facing contract, not internal documentation. Give every tool a human-readable title, a specific usage description, and explicit input and output schemas, and the model will pick the right tool far more often.

Version your schemas the same way you'd version a public API. A breaking change to a tool's input shape should bump a version marker somewhere in your server metadata, because clients built against the old shape will otherwise fail silently or, worse, pass malformed arguments that your handler wasn't built to catch.

Core MCP Concepts and Architecture — overview diagram

Which Transport Should Your MCP Server Use?

Three transports cover almost every deployment scenario, and picking the wrong one is the most common early mistake.

Stdio is the default for local development and desktop integrations. Your server runs as a subprocess, and the host communicates over standard input and output. It's fast, requires no network configuration, and is what the quickstart above uses. The tradeoff: it only works when the host can spawn a local process, which rules out most cloud-hosted agents.

Streamable HTTP replaced the older HTTP+SSE combination as the recommended transport for remote servers. It supports both request/response calls and server-initiated streaming over a single endpoint, and it's the right choice any time your server needs to live on a network rather than a local machine.

SSE (Server-Sent Events) still shows up in older implementations and some client libraries, but new servers should default to Streamable HTTP unless a specific client requires legacy SSE support.

Authorization only becomes a real concern once you leave stdio. A local subprocess inherits the trust boundary of whoever launched it. An HTTP-exposed server is a different story entirely, and the MCP authorization specification is explicit about the pattern to follow:

  • Implement OAuth 2.1 rather than legacy OAuth 2.0 flows, which lack mandatory PKCE protection.
  • Publish Protected Resource Metadata per RFC9728, so clients can discover your authorization requirements automatically.
  • Require the resource parameter defined in RFC8707, and validate that incoming tokens were actually issued for your server's audience, not lifted from another API.
  • Scope tokens narrowly. A token that grants read access to company data shouldn't also unlock write operations on billing endpoints.
  • Reject any token whose audience claim doesn't match your server, even if the signature is otherwise valid.

Skipping audience validation is the single most common HTTP MCP vulnerability. A token minted for one service, if it isn't checked against your specific resource identifier, can sometimes be replayed against a different one.

Python vs. TypeScript: Choosing an SDK and Runtime Pattern

Both official SDKs cover the same protocol surface, so the decision usually comes down to what your team already runs in production.

The Python SDK favors a decorator-based style, as shown in the quickstart, and its example server runs in roughly fifteen lines of code. It pairs naturally with uv, which handles dependency resolution and virtual environments without the usual pip friction. One detail worth knowing early: the SDK's Context object gives you request-scoped access to logging, progress reporting, and things like database connections. Keep that request-scoped state separate from anything you initialize at startup. Mixing the two is a fast way to introduce hard-to-trace bugs where one request's connection leaks into another's handler.

The TypeScript SDK exposes @modelcontextprotocol/server for Node, along with StdioServerTransport and Streamable HTTP helpers that mirror the Python API closely enough that porting a server between languages is mostly mechanical. If your organization already runs a Node-based backend or your agent host is JavaScript native, staying in TypeScript avoids a second runtime in your deployment stack.

A few practical differences to plan around:

  • Python's async model (asyncio) handles concurrent tool calls well, but blocking calls inside a handler, such as a synchronous database driver, will stall the entire event loop unless you offload them to a thread pool.
  • Node's single-threaded event loop has the same constraint in the other direction. A CPU-heavy tool handler blocks everything else until it finishes.
  • Packaging differs meaningfully. Python servers typically ship as PyPI packages or Docker images; Node servers commonly ship as npm packages, which also matters later when you publish to the MCP Registry.
  • Both SDK repos include runnable example directories that mirror common real-world patterns, like file-system tools and API wrappers, worth reading before you design your own from scratch.

Pro Tip: If you're building an MCP server for AI agents in a mixed-language shop, prototype in whichever SDK your team can debug fastest, then port later. The protocol layer is nearly identical, so the migration cost is lower than most teams expect.

How Do You Debug an MCP Server with the Inspector?

The MCP Inspector is the tool most developers underuse until they hit their first confusing bug, and by then they've usually already lost an hour to it.

  1. Start it with npx @modelcontextprotocol/inspector, which launches both a UI and a local proxy that mediates between the Inspector and your server process.
  2. Point it at your server by supplying the launch command, plus any environment variables or arguments your server expects, directly in the Inspector's connection panel.
  3. Use the UI mode to manually list tools, resources, and prompts, then call each one with sample input to confirm the response shape matches your schema.
  4. Switch to CLI mode for automated checks. This mode runs the same operations non-interactively, which makes it usable inside CI pipelines rather than just local debugging.
  5. Export a working configuration as mcp.json once you've confirmed the server behaves correctly. Most hosts consume this same file format for connecting to your server.

The Inspector runs a proxy that should bind to localhost by default. Exposing that proxy to a broader network is a real risk since it can spawn processes on your behalf, so keep the session token and local-only binding in place for both development and CI use, and never relax that constraint just to make remote debugging more convenient.

What Security Practices Do Production MCP Servers Need?

Most MCP server failures in production trace back to one of a handful of preventable mistakes, and almost none of them require exotic security knowledge to fix.

The most common one: writing logs to stdout on a stdio server. Stdout carries your JSON-RPC protocol messages, so any stray print() statement or unstructured log line corrupts the stream and causes cryptic, hard-to-diagnose failures on the client side. Send all diagnostics to stderr or route them through your SDK's logging facility instead. This single rule causes more early debugging pain than almost anything else on this list.

Beyond that:

  • Validate every input at the tool boundary, not just at the top of your handler. Treat arguments coming from a model the same way you'd treat user input from an untrusted web form, because a model can and will pass malformed or unexpected values.
  • Perform authorization checks inside each handler rather than assuming a connection-level check covers every subsequent call. A single authenticated session can still attempt actions it shouldn't be allowed to complete.
  • Apply the principle of least privilege to OAuth scopes, and validate both token audience and expiration on every request, not just at connection time.
  • Add timeouts to any tool that calls an external service. A hanging API call on your end can stall an entire agent workflow on the client end.
  • Rate-limit tools that trigger real side effects, like sending emails or writing to a database, separately from read-only tools that carry no similar risk.
  • Keep credentials out of committed configuration files entirely. Use environment variables or workspace-level configuration, a pattern VS Code's own MCP integration guidance recommends explicitly for exactly this reason.

Pro Tip: Write a fake malicious input test for every tool before you ship it, things like oversized strings, unexpected types, or SQL-looking text in a plain string field. If your handler survives that test, it will survive most real-world model mistakes too.

How Do You Publish an MCP Server to the Registry?

The MCP Registry stores metadata about your server, not the server itself. That distinction trips people up. Publishing doesn't deploy anything; it just makes your server discoverable, with a pointer to wherever your actual package lives.

The basic publishing flow starts with a server.json file describing your server's name, version, and package location, followed by running mcp-publisher publish to submit that metadata to the registry.

Supported package types cover the environments developers already use: npm and PyPI for language-specific packages, NuGet for .NET, OCI or Docker images for containerized deployments, and MCPB for bundled distributions. Each type carries its own ownership verification rules, so you'll need to prove control over the namespace you're publishing under before the registry accepts your submission.

If your server only needs to be discoverable inside your own organization, skip public registry publication entirely. Private registry patterns exist specifically for internal discovery, letting teams register servers without exposing artifacts publicly. Public publication only makes sense once your package is genuinely accessible and your namespace ownership is verifiable.

Before publishing publicly, run through this short checklist:

  • Confirm your package is actually installable from the location your server.json points to.
  • Verify namespace ownership on whichever package registry you're using, since unverified submissions get rejected.
  • Test your server end to end with the Inspector one more time, using the exact package version you're about to publish.
  • Double check that your server.json version field matches your package's actual released version, not a placeholder.

How Do You Test and Connect an MCP Server to a Host?

Testing an MCP server happens in layers, and skipping the earlier layers usually means debugging integration failures that a unit test would have caught in seconds.

  1. Unit test your handler logic directly, independent of the protocol layer. If a tool returns a company lookup result, test that function with normal, empty, and malformed inputs before MCP ever enters the picture.
  2. Validate your JSON schemas against real sample payloads, not just the happy path, to catch mismatches between what you documented and what your handler actually returns.
  3. Run integration tests using the Inspector's CLI mode, which can execute the same tool calls a real client would make, in a form that's scriptable inside CI.
  4. Add your server to a host's configuration, typically an mcpServers entry inside that host's mcp.json, specifying the launch command, arguments, and any required environment variables.
  5. Restart the host and confirm your server appears in its tool list, then run a live test call through the actual host interface, not just the Inspector.

When something breaks, the failure almost always falls into one of three buckets: discovery failures, where the host can't see your tools at all, usually a transport or startup crash; authentication errors, where tokens fail audience or scope validation; or malformed schema errors, where your tool's declared input shape doesn't match what your handler actually expects. Check those three in order before assuming the bug is something more exotic.

Scaling MCP Servers and Performance Optimization Strategies

An MCP server that works fine for one developer testing locally can behave very differently under real concurrent load from multiple agents or users.

Start with statelessness where possible. Because the protocol itself carries no persistent session state beyond per-request _meta fields, servers that avoid holding onto in-memory state between calls scale horizontally far more easily. Run multiple instances behind a load balancer instead of one large process.

Cache aggressively at the data layer, not the protocol layer. If a tool queries an external database or third-party API, cache results that don't change frequently rather than hitting that dependency on every call. A tool that looks up static company metadata, for instance, rarely needs a fresh query every time an agent calls it.

Watch for tools that block on slow external calls. A single slow dependency can create a queue of stalled requests if your runtime doesn't offload blocking work, whether that's a synchronous database driver in Python or a CPU-heavy computation in Node's single-threaded event loop.

Set connection pool limits deliberately rather than relying on defaults, especially for HTTP-transport servers expecting concurrent connections from multiple agent sessions. And instrument your server early. Basic request latency and error-rate logging, routed to stderr for stdio servers, makes it possible to spot a performance regression before it becomes a production incident rather than after.

How Do You Manage Versioning and Backward Compatibility?

Treat your tool schemas as a public API contract, because from the model's perspective, that's exactly what they are.

A breaking change, like renaming a parameter or changing a return type, will silently break every client still expecting the old shape. The safest pattern is additive change: add new optional parameters rather than modifying existing required ones, and add new tools rather than repurposing an existing tool's behavior for a different task.

When a breaking change is genuinely unavoidable, version it explicitly. Some teams append a version suffix to the tool name itself, like search_companies_v2, running both versions in parallel until every client has migrated. Others track version in server metadata and require clients to negotiate a compatible version at connection time. Either approach beats silently swapping behavior under a tool name clients already trust.

Deprecation needs a runway. Announce it, keep the old version functional for a defined period, and only remove it once you have reasonable confidence no active client still depends on it. This matters more for MCP than for typical REST APIs, because the "client" calling your tool is often a model deciding on its own how to interpret a tool's description, not a developer reading a changelog.

Document every schema change in whatever changelog or release notes your project already maintains. It sounds obvious. It's also the step most commonly skipped once a server moves from a side project into something other teams actually depend on.

Deployment Considerations and Environment Configuration

Where your MCP server runs shapes almost every other decision, from transport choice to how you handle secrets.

Local development almost always means stdio, launched directly by whatever host you're testing against, whether that's an editor, a CLI tool, or a custom agent harness. No network configuration required, which is exactly why it's the default for the quickstart earlier in this guide.

Cloud deployment changes the calculus entirely. A server meant to serve multiple remote clients needs Streamable HTTP, a real deployment target, such as a container platform or serverless function, and a plan for horizontal scaling under concurrent load. Containerizing with Docker is a common pattern here, partly because it's one of the package types the MCP Registry already supports natively.

Environment configuration deserves the same discipline you'd apply to any backend service. Separate configuration for development, staging, and production environments, and never hardcode credentials or API keys directly into your server code. Environment variables, secrets managers, or workspace-level configuration files all work, but the committed code itself should contain none of it.

Health checks matter more once you're running behind a load balancer or in a container orchestration platform. A simple endpoint or process check that confirms your server can still respond to a basic tool call catches failures before they cascade into failed agent sessions.

Quikturn's Perspective on MCP Servers for Finance Workflows

Financial data has a specific problem MCP was almost built to solve: agents need reliable, structured access to it, but exposing a raw database or generic API to a model invites exactly the kind of ambiguous, high-risk calls that a narrow tool surface is designed to prevent.

That's the reasoning behind Quikturn's own MCP server: interoperability with agent workflows, without handing model unrestricted access to a verified database of company logos and market intelligence. A well-scoped MCP tool, like a company lookup by ticker or domain, gives an agent exactly the capability it needs and nothing more.

For teams building anything comparable, the pattern worth borrowing is rate limiting paired with narrow, purpose-built tools rather than one broad data-access endpoint. Enterprise data deserves the same authorization discipline outlined earlier in this guide: audience-validated tokens, least-privilege scopes, and input validation at every tool boundary, because financial data carries a different risk profile than a weather lookup. Teams evaluating their own enterprise integration patterns should treat MCP exposure as a security decision first, a convenience feature second.

— Quikturn Team

Should You Build Your Own MCP Server or Use Quikturn's?

Building the quickstart earlier in this guide takes an afternoon. Building and maintaining a production-grade MCP server that handles authorization, rate limiting, schema versioning, and a verified data source behind it is a very different commitment, and most finance teams don't actually need to own that infrastructure.

Quikturn's MCP server gives investment bankers, consultants, and corporate strategy teams direct agent access to a verified database of over 17 million company logos and market intelligence, without anyone on the team writing or hosting a single line of server code. Instead of standing up your own tool surface for company lookups, ticker resolution, or logo search, you connect an agent to an endpoint that's already handling the authorization and data verification work covered throughout this guide.

Quikturn

If your team is deciding between building in-house and adopting a hosted option, the deciding factor is usually maintenance appetite. Teams with dedicated engineering time for ongoing schema versioning and security hardening can reasonably build their own. Teams that just want reliable financial data inside an agent workflow today are better served by a server someone else already secured and keeps current.

Quikturn's Platform Pro plan runs $9.99 per month and includes API access for teams that want to move past the free tier's limits, while Platform Free costs nothing and is a reasonable way to test the MCP integration before committing. Larger teams with heavier throughput needs should look at the API Growth plan at $170 per month, or reach out about Enterprise options for custom volume and security requirements. Start with the get-started page to connect an agent to Quikturn's MCP endpoint and see how a verified financial dataset behaves inside your existing workflow.

Sources

Read in this order: the official MCP documentation for spec-level detail, the Python or TypeScript SDK repos for runnable examples, and the Inspector README when something breaks. For validating outputs across multiple models once your server is live, a tool like BabyLoveGrowth's multi-LLM audit can help confirm consistent tool selection behavior.

FAQ

What MCP server should I be using?

There's no single correct server since it depends on what data or capability you need to expose. For general development, start with the official Python or TypeScript SDK quickstart, and for finance-specific data access, Quikturn's MCP server provides agent access to verified company and market data without custom build work.

How do I build a basic MCP server?

Install the official SDK for your language, define a tool using a decorator (Python) or the server.tool() method (TypeScript), and run it over the stdio transport for local testing. The MCP Server Guide: Build a Minimal Working Server in Minutes walks through both language variants with copy-paste code.

What do I use an MCP server for?

An MCP server exposes callable tools, readable resources, and reusable prompts so an AI agent can take real actions, like looking up data or triggering a workflow, instead of relying purely on generated text. Common examples include database lookups, API wrappers, file access, and structured data retrieval like company or market intelligence lookups.

How do you run an MCP server?

Start it with your language's run command, such as uv run mcp dev server.py for Python or node server.js for TypeScript, then connect it to a host by adding an entry to that host's mcp.json configuration. Use the MCP Inspector first to confirm your tools work correctly before connecting a real host.

Do I need to publish my MCP server to the Registry?

No. The MCP Registry stores metadata for discoverability, but internal or personal servers can run entirely unpublished, connected directly through a host's configuration. Public publication only matters if you want other developers to discover and install your server.