On this page
There are two ways to create an MCP server. You can write TypeScript or Python using the MCP SDK. Or you can declare your API in YAML and let paso generate the server.
Both work. This post compares them honestly.
The manual approach
The MCP SDK gives you full control. You define tool schemas, write request handlers, manage transport, and handle errors yourself.
Here’s a minimal MCP server for a weather API with two tools:
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',
'Get weather forecast for a city',
{
city: z.string().describe('City name'),
days: z.number().min(1).max(7).default(3).describe('Number of days'),
},
async ({ city, days }) => {
const res = await fetch(
`https://api.weather.example/v1/forecast?city=${encodeURIComponent(city)}&days=${days}`,
{ headers: { 'X-API-Key': process.env.WEATHER_API_KEY! } }
);
if (!res.ok) {
return {
content: [{ type: 'text', text: `Error: ${res.status} ${res.statusText}` }],
isError: true,
};
}
const data = await res.json();
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
}
);
server.tool(
'get_alerts',
'Get active weather alerts for a region',
{
region: z.string().describe('Region code (e.g. US-CA)'),
},
async ({ region }) => {
const res = await fetch(
`https://api.weather.example/v1/alerts?region=${encodeURIComponent(region)}`,
{ headers: { 'X-API-Key': process.env.WEATHER_API_KEY! } }
);
if (!res.ok) {
return {
content: [{ type: 'text', text: `Error: ${res.status} ${res.statusText}` }],
isError: true,
};
}
const data = await res.json();
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
That’s ~50 lines for two read-only tools. No input validation beyond Zod types. No permission model. No consent gates. No rate limiting.
A production server with five tools, error handling, auth forwarding, and permission checks typically runs 200-400 lines.
The paso approach
The same two tools as a paso declaration:
version: "1.0"
service:
name: Weather
description: Weather forecasts and alerts
base_url: https://api.weather.example/v1
auth:
type: api-key
header: X-API-Key
capabilities:
- name: get_forecast
description: Get weather forecast for a city
method: GET
path: /forecast
permission: read
inputs:
city:
type: string
required: true
description: City name
in: query
days:
type: integer
default: 3
description: Number of days (1-7)
in: query
constraints:
max_value: 7
- name: get_alerts
description: Get active weather alerts for a region
method: GET
path: /alerts
permission: read
inputs:
region:
type: string
required: true
description: Region code (e.g. US-CA)
in: query
Run usepaso serve. You have the same MCP server with automatic protocol compliance, auth forwarding, input validation, and error formatting.
The comparison
| Dimension | Manual (MCP SDK) | paso (YAML declaration) |
|---|---|---|
| Lines of code | 200-400 for 5 tools | 0 (YAML only) |
| Time to first server | 1-4 hours | 5-15 minutes |
| Auth handling | You write it | Automatic from auth block |
| Input validation | Zod/Pydantic schemas in code | Declared in YAML |
| Permission model | You design and implement it | Built-in (read/write/admin tiers) |
| Consent gates | You implement UI hooks | consent_required: true |
| Rate limiting | You implement it | max_per_hour constraint |
| Error formatting | You format responses | Automatic |
| Custom logic | Full control | Not supported |
| Database queries | Yes | No |
| Multi-step workflows | Yes | No |
| Protocol updates | Rewrite handlers | Update paso |
| Maintenance | Per-tool, per-API | One YAML file |
When to write by hand
Use the MCP SDK directly when your server needs:
- Custom business logic. A tool that queries a database, runs computations, or calls multiple APIs in sequence before returning a result.
- Stateful interactions. Tools that maintain session state across calls (multi-turn workflows, shopping carts, document editing).
- Non-REST backends. GraphQL APIs, gRPC services, WebSocket connections, or local file system access.
- Complex transformations. When the API response needs significant processing before it’s useful to an agent.
The MCP SDK is the right tool for these cases. paso doesn’t replace it.
When to use paso
Use paso when your MCP server maps to a REST API:
- REST endpoint mapping. Each tool corresponds to one HTTP endpoint. Most SaaS APIs fit this pattern.
- Multiple APIs. You want to expose 3-5 APIs to agents. Writing and maintaining 3-5 manual servers is a multiplication problem.
- Rapid prototyping. You want to test whether agents can use your API before committing to a full implementation.
- Team handoff. A YAML file is easier to review, modify, and hand off than a TypeScript codebase.
- OpenAPI import. You already have an OpenAPI spec and want an MCP server without writing code.
The maintenance argument
The initial build time is one cost. Maintenance is the ongoing cost.
When the MCP SDK releases a breaking change, manual servers need handler updates. paso servers need a npm update usepaso. When you add an endpoint to your API, a manual server needs a new handler function with schema, auth, error handling, and tests. A paso server needs a new YAML block.
For one API, this is manageable. For three APIs with 30 capabilities total, the maintenance difference compounds.
They’re not mutually exclusive
You can use both. paso for the REST APIs that map cleanly to YAML declarations. Manual servers for the tools that need custom logic.
A team might run a paso server for their Stripe, Slack, and Notion integrations, and a hand-written server for their internal data pipeline that requires multi-step orchestration.
The MCP client doesn’t care how the server was built. It cares that the tools are well-described and the responses are correct.
Related:
- The Complete Guide to MCP Servers. the full reference
- How to Create an MCP Server for step-by-step instructions for both approaches
- Five Ways to Test Before You Ship to verify your paso server
- Why We Built paso for the motivation behind the project