How to Create an MCP Server

On this page

An MCP server exposes your API as tools that AI agents can call. The Model Context Protocol defines the transport and schema. You provide the tools.

There are two ways to build one.

The manual way

Install the SDK and write TypeScript:

npm install @modelcontextprotocol/sdk
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: 'sentry',
  version: '1.0.0',
});

server.tool(
  'list_issues',
  'List issues for a project',
  {
    organization_slug: z.string(),
    project_slug: z.string(),
  },
  async ({ organization_slug, project_slug }) => {
    const res = await fetch(
      `https://sentry.io/api/0/projects/${organization_slug}/${project_slug}/issues/`,
      { headers: { Authorization: `Bearer ${process.env.SENTRY_TOKEN}` } }
    );
    const data = await res.json();
    return { content: [{ type: 'text', text: JSON.stringify(data) }] };
  }
);

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

That’s one tool. For six tools with input validation, error handling, auth forwarding, and permission checks, you’re looking at a few hundred lines. For each API you want to expose.

The paso way

Write YAML. Run one command.

npm install -g usepaso
usepaso init --name "Sentry"

This creates a usepaso.yaml file. Fill in your capabilities:

version: "1.0"

service:
  name: Sentry
  description: Error monitoring for software teams
  base_url: https://sentry.io/api/0
  auth:
    type: bearer

capabilities:
  - name: list_issues
    description: List issues for a project
    method: GET
    path: /projects/{organization_slug}/{project_slug}/issues/
    permission: read
    inputs:
      organization_slug:
        type: string
        required: true
        description: The organization slug
        in: path
      project_slug:
        type: string
        required: true
        description: The project slug
        in: path

Validate it:

usepaso validate
valid (Sentry, 1 capability, 0 regrets)

Start the server:

USEPASO_AUTH_TOKEN=your-sentry-token usepaso serve
usepaso serving "Sentry" (1 capability). Agents welcome.

That’s a production MCP server. paso handles protocol compliance, request routing, auth forwarding, input validation, and error formatting.

What you don’t write

No TypeScript request handlers. No Zod schema definitions. No transport setup. No auth forwarding logic. No error formatting.

Each new capability is a few lines of YAML:

  - name: resolve_issue
    description: Resolve an issue
    method: PUT
    path: /issues/{issue_id}/
    permission: write
    consent_required: true
    inputs:
      issue_id:
        type: string
        required: true
        description: The issue ID
        in: path
      status:
        type: enum
        required: true
        values: [resolved, unresolved, ignored]
        description: New status for the issue

Six capabilities. One YAML file. No TypeScript to maintain.

Side-by-side

Manual MCP serverpaso
LanguageTypeScript or PythonYAML
Lines of code100-500+ per API0
Schema definitionsZod / Pydantic in codeDeclared in YAML
Auth handlingYou write itAutomatic
Input validationYou write itAutomatic
New endpointNew handler functionNew YAML block
Protocol changesRewrite handlersUpdate paso
Custom logicFull controlNot supported

Already have an OpenAPI spec?

Generate the declaration from it:

usepaso init --from-openapi ./openapi.json --name "Sentry"

paso reads the spec, generates the YAML, and you edit it to curate which endpoints agents can access.

Connect to Claude Desktop

usepaso connect claude-desktop

paso writes the config for you. Restart Claude Desktop. Your API is now agent-ready.

Verify before you ship

paso gives you five checks:

usepaso validate --strict    # Structure + best practices
usepaso inspect              # Review what agents will see
usepaso test --all --dry-run # Verify all requests build correctly
usepaso doctor               # End-to-end setup check
usepaso serve                # Ship it

The tradeoff

The manual approach gives you full control over every handler. You can run database queries, call multiple APIs in sequence, or apply complex business logic before returning a result. paso gives you an MCP server in minutes with no protocol code. If your API follows REST conventions and you want agents to call it, paso is faster. If your MCP server needs custom orchestration logic, write it by hand.

Questions

Does paso work with Cursor and other MCP clients? Yes. paso generates a standard MCP server. Any MCP client that supports the Model Context Protocol can connect to it: Claude Desktop, Cursor, Windsurf, and others.

Can I use paso with Python? Yes. pip install usepaso. Same CLI, same YAML format, same output. Read more about Python support.

What if my API needs custom logic per request? paso handles REST endpoint mapping. If a capability requires database queries, multi-step orchestration, or custom transformations, write that handler manually using the MCP SDK.

Related: