Home Blog How to From zero to a working MCP server: connecting your internal API to Claude, Cursor, and OpenCode

From zero to a working MCP server: connecting your internal API to Claude, Cursor, and OpenCode

An AI agent and an internal API solve two separate problems. Getting them to talk to each other is a third problem, usually solved by hand: pasting data into a prompt, exporting a CSV so an agent can “see” what’s in a database. That works for reading data. It breaks the moment an agent needs to act on something, and it has to be rebuilt from scratch for every new AI tool added to the stack.

MCP server development solves this properly. Model Context Protocol (MCP) is an open standard published by Anthropic that lets an AI agent call your systems directly, the same way a REST API lets a frontend call a backend. Build the connection once, and Claude, Cursor, OpenCode, or whatever your team adopts next can all use it. This tutorial builds a minimal MCP server for a simple internal API, connects it to Claude, tests it before trusting it with real data, and covers where a weekend prototype stops being a weekend prototype.

From zero to a working MCP server: connecting your internal API to Claude, Cursor, and OpenCode

Table of contents

What an MCP server actually does (and why REST alone isn’t enough)

A REST API is built for developers. It assumes whoever’s calling it already knows the endpoint, the required parameters, and the authentication flow. That’s a reasonable assumption when the caller is a piece of code someone wrote on purpose.

An AI agent doesn’t have that context by default. It can’t read your OpenAPI spec mid-conversation and figure out which endpoint to call, what your custom status codes mean, or when it’s safe to trigger a write operation. Someone has to bridge that gap, usually by copying data manually or writing brittle one-off integration code for each agent.

An MCP server sits between your API and any MCP-compatible agent. Instead of raw endpoints, it exposes tools: named actions with a plain-language description, defined inputs, and a defined output. The agent reads the tool description, decides when to call it, and the server translates that call into a request against your actual API.

The distinction that matters: MCP and REST aren’t competitors. Most MCP servers are a thin translation layer sitting on top of an API you already have. You’re not rebuilding anything. You’re describing what already exists in a way an agent can reason about.

What you need before you start

Three things make this go faster, and skipping them is where most first attempts stall.

A working API with a clear contract. If you have an OpenAPI or Swagger spec, tool definitions can be mapped almost directly from it. If you don’t, you’ll be writing those definitions by hand, which is fine for two or three endpoints and painful past that.

A decision on authentication, even a temporary one. For an internal prototype talking to your own systems, a static API token is enough to get started. The moment the server needs to act on behalf of different users, or gets exposed outside your team, that decision changes to OAuth. Deciding this upfront saves a rewrite later.

An SDK. Anthropic publishes official MCP SDKs for Python and TypeScript. The examples below use Python, because the syntax stays readable even for someone who isn’t in the codebase daily. The TypeScript SDK works the same way if that’s closer to your existing stack.

Building a minimal MCP server step by step

Consider a simple internal ticketing API with one endpoint worth exposing first: fetching a ticket by ID. Here’s what a minimal MCP server around it looks like using the Python SDK.

from mcp.server.fastmcp import FastMCP
import httpx

mcp = FastMCP("internal-tickets")

API_BASE = "https://internal-api.example.com"
API_TOKEN = "your-service-token"

@mcp.tool()
async def get_ticket(ticket_id: str) -> dict:
    """Fetch a support ticket by its ID, including status, assignee, and last update."""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{API_BASE}/tickets/{ticket_id}",
            headers={"Authorization": f"Bearer {API_TOKEN}"},
        )
        response.raise_for_status()
        return response.json()

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

A few things worth noticing here, because they’re the parts that separate a tool an agent uses correctly from one it misuses.

The docstring is not a comment for other developers. It’s what the agent reads to decide whether and how to call this tool. Vague descriptions produce vague agent behavior: write “fetch a ticket” and the agent may call it for the wrong kind of request. Write what the tool returns and when it’s the right choice, and the agent’s calls get noticeably more accurate.

The function signature defines the tool’s inputs. Keep types explicit and narrow. ticket_id: str is unambiguous. A loosely typed params: dict forces the agent to guess at structure, and guessing is where errors creep in.

Once this file runs locally, connecting it to Claude Desktop is a matter of adding it to the MCP server configuration, pointing at the script’s path. Cursor and OpenCode follow the same pattern: a config entry naming the server and how to start it. This is the part of MCP’s value that’s easy to undersell. Add a second agent to your team’s stack next quarter, and this same server works with it, without touching the code again.

Testing it before you trust it

A tool that looks correct in isolation can still behave badly once an agent is calling it with real, imperfect inputs. Before any MCP server goes near production data, it needs to be exercised the way an agent will actually use it: unexpected parameter combinations, malformed IDs, missing fields in the API response.

This is what tools like MCP Farmer, an open-source MCP audit CLI Boldare built and publishes on NPM, are for. It scaffolds new servers, connects to an existing one to explore its tools interactively, and can generate AI-driven test inputs to probe how a tool responds under conditions you didn’t think to test manually. Running a server through something like this before connecting it to a live agent catches the failure modes that only show up once someone other than the person who wrote the code starts using it.

Skipping this step is how a “working” MCP server ends up calling the wrong endpoint with a plausible-looking but invalid ticket ID, and returning a confidently wrong answer instead of an error.

Where the afternoon prototype stops and production starts

The single-tool server above is genuinely something you can build and connect in an afternoon. That’s a real, useful outcome, and it’s worth doing even if it never goes further, because it tells you fast whether MCP is solving the problem you think it’s solving.

What it isn’t yet is something you’d hand to a customer-facing agent, or leave running unattended against a system with write access. A few things change once the server needs to hold up under real usage instead of a demo:

Authentication stops being a static token. Anything acting on behalf of individual users, or reachable by agents outside your team, needs OAuth 2.0 and proper access scoping. This is also the point at which a public-facing MCP server becomes possible. Boldare runs its own public MCP server that handles RFQ intake, letting an AI agent submit a project brief directly instead of filling out a form. That’s the same underlying pattern as the ticket-fetching example, built out for external traffic and write operations.

Tool definitions need to survive edge cases, not just the happy path. What happens when the ticket doesn’t exist, when the API times out, when a field the tool expects is null? Each of those needs handling that doesn’t just crash the agent’s turn.

Someone owns what happens when the underlying API changes. An endpoint gets renamed, a field gets deprecated, a new authentication requirement rolls out. Without a maintenance plan, the tool definitions silently drift out of sync with the API, and the server degrades in ways that are hard to notice until an agent starts failing quietly.

None of this means the prototype was wasted effort. It means the gap between “a tool an agent can call” and “a tool an agent can rely on in production” is real, and closing it is a different kind of work than the first afternoon. Boldare’s own MCP Server Development work treats that gap explicitly: a 48-hour architecture review before any build starts, a single-API server built to production standards in 2–3 weeks, tested with MCP Farmer before it goes near a live agent. If you’re at the point where the prototype proved the case and now needs to hold up under real traffic, that’s the point worth having a conversation about.

If you’re earlier than that, the ticket example above is enough to find out whether MCP is worth the investment for your stack at all, and the Claude Code, Copilot, or Cursor comparison is a useful next read for deciding which agent your team standardizes around before building more tools on top of it.

FAQ

  1. Do I need OAuth for an MCP server that only my own team uses? Not necessarily. For an internal-only server talking to systems your team already has credentials for, a static API token is a reasonable starting point. OAuth becomes the right call once the server acts on behalf of different individual users or gets exposed to agents outside your organization.
  2. Does this work with Cursor and OpenCode, or only Claude? MCP is an open standard, not a Claude-specific feature. Claude, Cursor, and OpenCode all support it, along with a growing list of other AI development tools. A server built once works with any of them, and with whatever your team adopts next without additional integration work.
  3. Can I build an MCP server on top of an API I already have, without changing it? Yes, and that’s the most common path. The MCP server acts as a translation layer between your existing API and the agent. If your API follows an OpenAPI or Swagger spec, mapping endpoints to tool definitions is considerably faster.
  4. What actually breaks first when the underlying API changes? Tool definitions are the first thing to drift. A renamed field or deprecated endpoint doesn’t throw an obvious error. It shows up as an agent quietly getting worse answers, which is harder to catch than an outright failure.
  5. How long does a real, production-grade MCP server take to build? For a single API with standard authentication, roughly 2–3 weeks from architecture sign-off to deployment. A multi-source server with OAuth and connections across several internal systems runs 4–6 weeks. A prototype covering one endpoint, like the example in this tutorial, can be working in an afternoon.
  6. Do developers need to write code every time they want to use the MCP server once it’s live? No. Once it’s connected to an agent, interacting with it happens in natural language through the agent itself. The code lives inside the server; using it doesn’t require writing any.