How to Build MCP Server

Build an order lookup MCP server with Python, test it in Inspector, connect Claude Code and Cursor, then add HTTP auth and hosting.

Published

How to Build MCP Server

You can let Claude Code or Cursor answer an order-status question from your company's data without copying a record into chat. Build one read-only MCP tool, prove it in the Inspector, then choose a local process or an authenticated HTTP service that your team can share.

The useful first server has a small job: accept an order ID and return the status. Start there. A general database tool makes the model responsible for too many decisions and gives it more access than this workflow needs.

This walkthrough follows the current official server tutorial. As of 7 October 2026, the documentation uses MCP specification 2026-07-28 and the official Python SDK, the library that handles MCP messages, with its MCPServer API. The example pins SDK 2.3.0, so an older tutorial's imports cannot quietly change what you install.

What Your MCP Server Exposes

An MCP server is a controlled service desk between an AI application and your systems. The application can ask what is available, submit a request, and receive a result. Your code decides what the desk can do.

MCP, the Model Context Protocol, gives that conversation a shared format. A host is the application you use, such as Claude Code or Cursor. Its client handles the protocol conversation with your server. These are roles, not three extra applications you need to install.

CapabilityIn plain wordsAn example for an order workflow
ToolsFunctions the client can call with defined argumentslookup_order(order_id) returns an order's status
ResourcesReadable context identified by a URI, an address for that informationA returns-policy document the client can read
PromptsReusable message templates that the host can present to a userA template for drafting a reply about a delayed shipment

A tool can be read-only. Calling something a resource does not replace access checks. A prompt gives instructions, not permission. Client support and presentation vary, so verify the features you actually expose. These are the three server primitives; the server below needs only a tool.

An architectural MCP service desk with separate tools, resources and prompts bays connected to a client
A server can expose three kinds of capability. This first implementation exposes only the order lookup tool.

Before building, check whether a maintained connector already covers the job. Our best MCP servers for 2026 is the starting point. A custom server earns its keep when your internal data, permission rules or workflow differ from those connectors.

Build a Read-Only Order Lookup in Python

Use the official SDK to handle the protocol and keep your code focused on the lookup. You need Python 3.10 or newer, uv, the Python project manager used in the official tutorial, and Node.js for the Inspector. The current Inspector requires Node 22.19.0 or newer.

In a terminal, run these commands in order:

  1. uv init orders-mcp
  2. cd orders-mcp
  3. uv venv
  4. uv add "mcp[cli]==2.3.0"

Create orders.py in that folder and paste this complete server. The records are fictional training data. There are no customer names, payment details or API credentials.

Python
import json

from mcp.server import MCPServer

mcp = MCPServer("orders")

# Fictional training data. No customer records or credentials.
ORDERS = {
    "A100": {"status": "shipped", "carrier": "Demo Courier"},
    "A101": {"status": "packing", "carrier": "not assigned"},
}


@mcp.tool()
def lookup_order(order_id: str) -> str:
    """Look up a fictional order by ID, such as A100. Read-only.

    Args:
        order_id: Exact order ID, for example A100 or A101.
    """
    key = order_id.strip().upper()
    order = ORDERS.get(key)
    if order is None:
        return json.dumps({"found": False, "order_id": key})
    return json.dumps({"found": True, "order_id": key, **order})


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

This uses the tutorial's documented MCPServer, @mcp.tool() and mcp.run(transport="stdio") pattern, with an order lookup in place of its weather tools. The string type hint tells the SDK that order_id is required text. The docstring tells the client when the tool is useful. The SDK creates the tool definition and handles protocol messages.

Run uv run orders.py. A quiet process waiting for input is expected. stdio, standard input and output, is the pipe the client uses to talk to this process. Stop this manual run before letting a client launch its own copy.

Keep application logs out of standard output. Use Python's logging module, which writes to standard error by default. A stray print() can corrupt the protocol stream. This is a documented stdio constraint, not a cosmetic logging preference.

When you replace the dictionary with a database, preserve the same small interface. Use a parameterized lookup, a database account that can only read the necessary fields, and a caller-specific permission check before returning a record. Never take an arbitrary SQL statement from the model for this task.

Test It With MCP Inspector

Prove the tool works before asking a model to use it. From the project folder, run uv run mcp dev orders.py. The SDK development command launches MCP Inspector. Open the browser URL printed by the command and connect the server if it is not already connected.

In Tools, select lookup_order. The form should show a required order_id field. Call it with A100: the result should contain found: true, status: shipped and carrier: Demo Courier. Call A101 for packing. Call DOES-NOT-EXIST: the result should contain found: false.

Also try a request without order_id. It should fail input validation instead of running a lookup. The Inspector's Protocol and Console views help distinguish a malformed request from a server-process failure.

For a repeatable terminal check, use npx @modelcontextprotocol/inspector --cli uv run orders.py --method tools/list. To call the tool, use npx @modelcontextprotocol/inspector --cli uv run orders.py --method tools/call --tool-name lookup_order --tool-arg order_id=A100. These use the documented Inspector CLI.

The pass condition is concrete: one discoverable tool, the expected status for a known order, and an explicit missing-record response. A plausible paragraph generated by a model is not evidence that the lookup ran.

Connect the Same Server to Claude Code and Cursor

Each local client launches its own server process. You do not need to keep the Inspector running. Use absolute paths so the launch does not depend on whichever folder the client happens to open.

Claude Code: run claude mcp add --transport stdio --scope local orders -- /ABSOLUTE/PATH/orders-mcp/.venv/bin/python /ABSOLUTE/PATH/orders-mcp/orders.py. On Windows, the interpreter is .venv\Scripts\python.exe. This follows Claude Code's local-server syntax, including the -- separator before the launch command.

Run claude mcp get orders to check the connection. In a Claude Code session, open /mcp, then ask: “Use lookup_order to check A100. Report only the returned status and carrier.” Inspect the tool call and its arguments.

Cursor: create .cursor/mcp.json in your project. Add this JSON, replacing both absolute paths: {"mcpServers":{"orders":{"type":"stdio","command":"/ABSOLUTE/PATH/orders-mcp/.venv/bin/python","args":["/ABSOLUTE/PATH/orders-mcp/orders.py"]}}}.

Open Customize, enable the server and ask the same question in Agent. Review the call according to your approval settings. The file location, launch fields and controls follow Cursor's MCP configuration. If the connection fails, check the executable path and server stderr before changing the tool.

Choose Local stdio or Remote HTTP

Stay local for a personal workflow. Choose remote HTTP when multiple people or hosted clients need one managed service.

DecisionLocal stdioRemote Streamable HTTP
Where it runsA process launched on the client's machineA web service at an HTTPS endpoint
How the client finds itA command and argumentsA URL such as https://orders.example.com/mcp
Access boundaryOS permissions, process environment and downstream credentialsVerified caller identity, scopes and per-record authorization
UpdatesUpdate each installed copyDeploy one service
Best first useOne developer proving a bounded workflowShared access with central operations

A remote service also needs network access to your company data. Publishing an endpoint does not make a private database reachable or make its permissions correct.

The 2026-07-28 HTTP protocol uses self-contained requests. The current Python SDK can also serve older clients, whose sessions may need sticky routing when you add replicas. Use the documented legacy settings deliberately before scaling; do not assume every connected client speaks the newest revision.

Two architectural routes comparing a local stdio process on one machine with remote HTTP access through an auth gate
Choose the deployment boundary: a local process for an individual workflow, or an authenticated shared HTTP service.

Move the Lookup to HTTP With Authentication

Protect HTTP access with tokens issued for this service. OAuth 2.1 is the authorization framework in the MCP specification: an identity provider signs the user in and issues a token, while your MCP server verifies it. A scope is a named permission, such as orders:read. The audience says which service may accept the token.

The SDK supplies the resource-server integration, not your company's login system. For this example, configure an identity provider with OAuth discovery, client registration for your chosen clients, PKCE for user login, which proves the app completing sign-in is the one that started it, and a token-introspection endpoint. Introspection asks the issuer whether a token is active and what it permits.

This adapter expects HTTPS introspection with HTTP Basic client authentication and a response containing active, aud, exp, client_id and scope. Configure the issuer to include this endpoint's exact public URL in aud and to issue orders:read. If your provider uses a different introspection authentication method, adapt that request to its documentation. If it provides JWTs, signed tokens, instead, implement signature, issuer, expiry and audience verification in the same TokenVerifier interface.

Add uvicorn with uv add uvicorn, then create remote.py beside orders.py. This follows the SDK's official introspection example and its documented HTTP and auth interfaces. It reuses the lookup you just tested.

Python
import os
import time
from urllib.parse import urlsplit

import httpx2
from pydantic import AnyHttpUrl

from mcp.server import MCPServer
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.transport_security import TransportSecuritySettings
from orders import lookup_order as local_lookup

RESOURCE = os.environ["MCP_RESOURCE_URL"]
ISSUER = os.environ["MCP_ISSUER_URL"]
INTROSPECT = os.environ["MCP_INTROSPECTION_URL"]
if any(urlsplit(url).scheme != "https" for url in (RESOURCE, ISSUER, INTROSPECT)):
    raise ValueError("Public auth and resource URLs must use HTTPS")


class OrderTokenVerifier(TokenVerifier):
    async def verify_token(self, token: str) -> AccessToken | None:
        try:
            async with httpx2.AsyncClient(timeout=5.0) as client:
                response = await client.post(
                    INTROSPECT,
                    data={"token": token},
                    auth=(os.environ["MCP_INTROSPECTION_CLIENT_ID"],
                          os.environ["MCP_INTROSPECTION_CLIENT_SECRET"]),
                )
                response.raise_for_status()
                data = response.json()
            audiences = data.get("aud", [])
            if isinstance(audiences, str):
                audiences = [audiences]
            expiry = data.get("exp")
            if (data.get("active") is not True or RESOURCE not in audiences
                    or not isinstance(expiry, int) or expiry <= time.time()):
                return None
            if data.get("iss", ISSUER) != ISSUER:
                return None
            return AccessToken(
                token=token, client_id=data["client_id"],
                scopes=data.get("scope", "").split(), expires_at=expiry,
                resource=RESOURCE, subject=data.get("sub"),
            )
        except Exception:
            return None


mcp = MCPServer(
    "orders",
    token_verifier=OrderTokenVerifier(),
    auth=AuthSettings(
        issuer_url=AnyHttpUrl(ISSUER),
        resource_server_url=AnyHttpUrl(RESOURCE),
        required_scopes=["orders:read"], validate_token_resource=True,
    ),
)


@mcp.tool()
def lookup_order(order_id: str) -> str:
    """Look up a fictional order by ID, such as A100. Read-only."""
    return local_lookup(order_id)


hostname = urlsplit(RESOURCE).hostname
security = TransportSecuritySettings(
    allowed_hosts=[hostname, f"{hostname}:*"],
    allowed_origins=[os.environ["MCP_ALLOWED_ORIGIN"]],
)
app = mcp.streamable_http_app(transport_security=security)

Set these values in your deployment's environment or secret store:

VariableWhat to supply
MCP_RESOURCE_URLThe exact public endpoint, for example https://orders.example.com/mcp
MCP_ISSUER_URLYour identity provider's issuer URL, matching its metadata
MCP_INTROSPECTION_URLIts documented HTTPS token-introspection endpoint
MCP_INTROSPECTION_CLIENT_IDThe confidential client authorized to introspect tokens
MCP_INTROSPECTION_CLIENT_SECRETThat client's secret
MCP_ALLOWED_ORIGINAn exact permitted browser origin, for example your internal app's origin

The host allowlist is explicit because the SDK otherwise accepts localhost by default and rejects a public hostname with 421 Misdirected Request. Browser origins are a separate check; list only the origins you actually use. The returned app already includes its startup and shutdown lifecycle. These details follow the SDK deployment and ASGI app documentation. ASGI is the interface Python web servers use to run this application.

Start it behind your host's HTTPS proxy with uv run uvicorn remote:app --host 0.0.0.0 --port 8000. The public resource URL remains HTTPS even if the proxy talks HTTP to the process. Configure forwarded-header trust for that host's actual proxy boundary.

Before using real records, run these checks against HTTP:

  • No token, expired token or token for another audience: access is refused.
  • Valid token without orders:read: access is refused.
  • Valid token with the correct audience and scope: lookup_order returns the demo status.
  • /.well-known/oauth-protected-resource/mcp: the metadata identifies the correct resource and issuer.

Use npx @modelcontextprotocol/inspector --server-url https://orders.example.com/mcp --transport http to inspect the deployed endpoint and complete its authentication flow. An in-memory tool test skips HTTP authorization, so it cannot prove this boundary works.

For Claude Code, add a separate connection with claude mcp add --transport http orders-remote https://orders.example.com/mcp, then authenticate through /mcp. For Cursor, add a remote entry under mcpServers with "url":"https://orders.example.com/mcp" and complete OAuth. Cursor also documents an auth object with CLIENT_ID and scopes for pre-registered clients. Register the appropriate client callbacks with your issuer. See Claude Code authentication and Cursor's remote OAuth setup.

This is a small authenticated adapter, not the whole production system. Before replacing the demo dictionary, enforce tenant and record permissions using verified identity, add an audit event around the call, reuse HTTP connections, and cap requests. A scope permits the operation; it does not establish ownership of every order.

Host It on Render or Cloudflare Workers

For the Python server above, I would start with Render. A Python web service preserves the app you have already built. Cloudflare Workers is a strong option if you want to implement the same bounded tool in its documented Worker handler.

These are maker-published prices checked on 7 October 2026:

HostPublished starting costWhat the price coversFit for this tutorial
Cloudflare Workers FreeNo plan charge100,000 requests/day, 10 ms CPU per invocationA Worker prototype that fits those limits
Cloudflare Workers Paid$5/month minimum10 million requests/month and 30 million CPU-ms/month includedShared Worker deployment; additional requests are $0.30/million and CPU time $0.02/million CPU-ms
Render paid web service$7/month for 0.5c-512mb compute512 MB RAM; the Hobby workspace fee is $0 plus computeRun this Python ASGI app

Sources: Cloudflare Workers pricing and Render pricing. Render's Pro workspace adds $25/month plus compute if you choose its team features. Storage, identity services, model usage and other extras must be budgeted separately; these are hosting prices, not the cost of an AI workflow.

Render deployment: put orders.py, remote.py and requirements.txt in your repository. The requirements file needs mcp[cli]==2.3.0 and uvicorn, each on its own line. Create a Python Web Service, use build command pip install -r requirements.txt, and start command uvicorn remote:app --host 0.0.0.0 --port $PORT. Add the environment variables above, then use the assigned hostname or your custom domain in MCP_RESOURCE_URL. This adapts Render's documented Python web-service deployment to the SDK's ASGI app.

Render's free service is useful for a demo, but it sleeps after 15 idle minutes and takes about a minute to wake. I would use paid compute for an interactive shared tool.

Cloudflare deployment: use the current MCP handler documentation and remote-server guide. The current TypeScript path uses createMcpHandler from agents/mcp/server with @modelcontextprotocol/server. Implement the same order lookup there and configure authentication before sharing the URL. The Python uvicorn launch command is for a Python host; it is not a Worker deployment recipe.

Keep the Security Boundary Small

Give the server only the access its tool requires. For order status, that means a read-only backend credential, selected fields and a per-record permission check. Keep refunds, cancellations and address changes behind separate tools and permissions. The model's choice of arguments is never authorization.

Validate HTTP tokens for your issuer, expiry, audience and scope. Use HTTPS and separate credentials when the server calls a downstream API. The MCP security guidance forbids token passthrough: a token presented to your MCP endpoint is not automatically a credential for your order system. For local stdio, restrict the launching process, its environment and its filesystem access.

Log the verified caller, tool name, an appropriately redacted record reference, outcome, latency and request ID. Avoid tokens and complete customer records. Keep logs on stderr for stdio and in your host's log system for HTTP. Treat text retrieved from records as data; a note inside an order must not grant permission for another action.

Put a gateway in front when multiple servers or teams need shared identity policy, rate limits, audit collection or revocation. A gateway can centralize those controls; each backend still needs correct record permissions. Our MCP gateway guide explains that decision.

An architectural security flow from verified identity through token checks to a read-only order lookup and an audit log
Identity and scope admit the request. Record permissions constrain the result. An audit event makes the call reviewable.

Six Useful Workflows, Ranked by Immediate Payoff

These are possible extensions of the same pattern. Start where a person repeatedly retrieves a bounded fact and can recognize a correct answer.

RankWho could use itThe exact workflowWhy it could pay
1A support team answering shipment questionsFetch permitted order status and carrier, then draft a reply in its existing assistantLess switching between the inbox and order system
2A technical operator handling customer incidentsFetch an account's enabled features and recent service events by account IDLess time reconstructing context before investigating
3An account manager preparing a renewalRetrieve an authorized customer's plan, renewal date and open issuesFewer stale facts in meeting preparation
4A finance operator chasing invoice questionsLook up a permitted invoice's status and related order referenceFaster reconciliation without granting payment authority
5An engineer debugging an internal integrationRetrieve a redacted job result and approved diagnostic fields by job IDLess manual log hunting and credential sharing
6An employee answering a policy questionRead an authorized policy resource with its source and update dateAnswers can point to the actual policy instead of remembered wording

The business math should start with your own workflow. Illustrative only: 80 lookups a day at 2 minutes each consume 160 minutes. If measurement later shows that the connected workflow saves 1 minute per lookup, that is 80 minutes recovered per day. This is arithmetic, not a performance benchmark. Measure correct answers and time saved before claiming a return from the hosting spend.

Two Things Worth Building

The strongest opportunity is an order-context adapter for support teams. A team could buy a narrow integration that retrieves the right shipment facts inside the assistant it already uses. DataForSEO estimates 260 US Google searches per month for “customer support automation”, checked on 7 October 2026. That is broad interest in the job, not a count of MCP buyers. Intercom publishes Fin at $0.99 per outcome, which shows an existing budget for support automation; this tiny adapter supplies context rather than replacing that product.

The smallest sellable version could cover one order backend, lookup_order, scoped login, an audit trail and a reply draft that cites returned fields. Its advantage would be fitting a specific company's data and access rules. The catch is that existing vendors may already have the connector, and a team's model seats, data cleanup and support still cost money. Validate that gap with a support lead before adding more tools.

A permission-aware policy lookup is the second opportunity. An operator could build access to one company's approved policy collection through resources or a narrow search tool. DataForSEO estimates 390 US monthly searches for “enterprise search”, checked on the same date. The MVP could include one collection, source citations, freshness checks and filtering by the signed-in person's permissions. The catch is that retrieval quality and access control are the product; wrapping a folder in MCP is easy to copy. The search figure is a demand signal for the broader job, not evidence that users will pay for this implementation.

What This Does Not Solve

MCP standardizes access. You still own data quality, authorization, backend reliability and the choice of what a tool can do. A model can misread a valid result, and a connected client can have different capability support or approval policies.

Build this when a shared tool interface will improve a workflow you can measure. For a fixed batch job that never needs an assistant to choose a tool, an ordinary API call or script may be the better implementation.

Your Monday move: pick one recurring lookup with a support operator, use fictional records to get both clients connected, then replace the dataset behind a read-only account and test record permissions. Keep writes out of that first rollout. Promote it to shared HTTP when the workflow and identity boundary are ready.

Is it hard to build an MCP server?

A small read-only server is straightforward with the official SDK: define the function, describe its input and choose a transport. Making company data available safely takes more work because you must enforce permissions, manage credentials and operate the service.

Is there a free MCP server I can use for testing?

The fictional order server in this tutorial can run locally without a hosting charge. MCP Inspector lets you call it without a model subscription. Cloud hosting and the AI client you later choose have their own pricing.

How much does an MCP server cost?

A local process needs no separate hosting plan. Cloudflare Workers publishes a free tier and a $5/month paid minimum. Render publishes $7/month compute for its small paid web service. These figures exclude model usage, identity services, storage and engineering.

Do I need to install an MCP server?

For stdio, the server runs on the client's machine, so its code and runtime must be available there. For remote HTTP, you configure an endpoint and authenticate; the server runs on the host. Use the deployment your chosen client supports.

If you want a company-data MCP service built and operated for your team, our AI production systems service covers the integration and its access controls.

Published
Category
Build
Related Articles
Gumloop vs n8n (Verified October 2026)

Gumloop vs n8n (Verified October 2026)

Gumloop vs n8n: current pricing, credits vs executions, self-hosting and the same AI lead workflow, with a verdict for each builder.Oct 7, 2026Build
AI Agent Frameworks in 2026: LangGraph, CrewAI, OpenAI Agents SDK, Claude Agent SDK, Mastra and Google ADK (Compared)

AI Agent Frameworks in 2026: LangGraph, CrewAI, OpenAI Agents SDK, Claude Agent SDK, Mastra and Google ADK (Compared)

Compare eight AI agent frameworks by language, state, MCP, approvals and current hosted prices. Pick the right runtime for production.Oct 7, 2026Build
Codex Cloud

Codex Cloud

Set up a reusable Codex Cloud environment, keep coding tasks running with your laptop off, steer from your phone, and choose cloud or local CLI.Oct 7, 2026Build
GitHub Copilot Pricing (2026): The Seat Is the Floor, Credits Are the Bill

GitHub Copilot Pricing (2026): The Seat Is the Floor, Credits Are the Bill

GitHub Copilot plans, AI Credits, model rates, and three worked monthly bills. Choose Pro, Pro+, Max, Business, or Enterprise with spending limits.Oct 6, 2026Build
GitHub Copilot CLI

GitHub Copilot CLI

Install GitHub Copilot CLI, sign in, fix tests and open a PR. Current GitHub plans, AI credits, models, permissions, MCP and instructions.Oct 6, 2026Build
Best Web Scraping Tools in 2026: Firecrawl, Bright Data, Browse AI, and Context.dev (Compared)

Best Web Scraping Tools in 2026: Firecrawl, Bright Data, Browse AI, and Context.dev (Compared)

Choose web scraping tools by the job: agent-ready pages, no-code monitoring, or collection at scale. Eight tools with verified prices and billing units.Oct 6, 2026Build
Agent Memory Explained: Context, Sessions, Stores and Costs

Agent Memory Explained: Context, Sessions, Stores and Costs

Agent memory explained for builders: context, sessions, long-term stores, files and skills, provider costs, and fixes for stale facts and user leaks.Oct 5, 2026Build
Pinecone Pricing (2026): What 1M to 100M Vectors Cost

Pinecone Pricing (2026): What 1M to 100M Vectors Cost

Pinecone pricing verified October 2026: free Starter, $20 Builder, paid minimums, and costs for 1M to 100M vectors at stated query volumes.Oct 5, 2026Build
Newsletter

One letter, every Sunday.Working systems, not hot takes.

Weekly. No spam. Unsubscribe anytime.