Build Your Own Server
Create a custom MCP server that integrates with CodeSpar's session model, billing, and tool routing.
Build Your Own Server
CodeSpar's 57 MCP servers cover the most common Latin American commerce APIs. But if you need to integrate a proprietary API, internal service, or unsupported provider, you can build your own MCP server and plug it into the CodeSpar ecosystem.
What you're building
An MCP server is a process that exposes tools over the Model Context Protocol. Each tool is a function with:
- A name (e.g.,
myapi_create_order) - A description (natural language, shown to LLMs)
- An input_schema (JSON Schema for parameters)
- A handler (the function that calls your API)
When your agent calls the tool, CodeSpar routes the request to your server, which executes the handler and returns the result.
Prerequisites
npm install @modelcontextprotocol/sdkStep-by-step
Define your tools
Start by listing the operations your API supports. Each operation becomes a tool:
import { z } from "zod";
export const tools = {
create_order: {
description: "Create a new order in MyAPI",
inputSchema: z.object({
customer_id: z.string().describe("Customer identifier"),
items: z.array(z.object({
product_id: z.string(),
quantity: z.number().int().positive(),
})).describe("Order line items"),
notes: z.string().optional().describe("Optional order notes"),
}),
},
get_order: {
description: "Get order details by ID",
inputSchema: z.object({
order_id: z.string().describe("Order ID to look up"),
}),
},
list_orders: {
description: "List orders with optional filters",
inputSchema: z.object({
status: z.enum(["pending", "paid", "shipped", "delivered"]).optional(),
from: z.string().optional().describe("Start date (ISO 8601)"),
to: z.string().optional().describe("End date (ISO 8601)"),
limit: z.number().int().max(100).default(50),
}),
},
};Implement the handlers
Each handler receives the validated input and calls your API:
import { MyAPIClient } from "./client";
const client = new MyAPIClient({
baseUrl: process.env.MYAPI_BASE_URL!,
apiKey: process.env.MYAPI_API_KEY!,
});
export async function createOrder(input: {
customer_id: string;
items: { product_id: string; quantity: number }[];
notes?: string;
}) {
const order = await client.orders.create({
customerId: input.customer_id,
items: input.items,
notes: input.notes,
});
return {
order_id: order.id,
status: order.status,
total: order.total,
created_at: order.createdAt,
};
}
export async function getOrder(input: { order_id: string }) {
return client.orders.get(input.order_id);
}
export async function listOrders(input: {
status?: string;
from?: string;
to?: string;
limit: number;
}) {
return client.orders.list({
status: input.status,
dateFrom: input.from,
dateTo: input.to,
limit: input.limit,
});
}Create the MCP server
Wire tools and handlers together into an MCP server:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { tools } from "./tools";
import { createOrder, getOrder, listOrders } from "./handlers";
const server = new Server(
{ name: "myapi", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "myapi_create_order",
description: tools.create_order.description,
inputSchema: tools.create_order.inputSchema,
},
{
name: "myapi_get_order",
description: tools.get_order.description,
inputSchema: tools.get_order.inputSchema,
},
{
name: "myapi_list_orders",
description: tools.list_orders.description,
inputSchema: tools.list_orders.inputSchema,
},
],
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "myapi_create_order":
return { content: [{ type: "text", text: JSON.stringify(await createOrder(args)) }] };
case "myapi_get_order":
return { content: [{ type: "text", text: JSON.stringify(await getOrder(args)) }] };
case "myapi_list_orders":
return { content: [{ type: "text", text: JSON.stringify(await listOrders(args)) }] };
default:
throw new Error(`Unknown tool: ${name}`);
}
});
const transport = new StdioServerTransport();
await server.connect(transport);Package and publish
Create a package.json and publish to npm:
{
"name": "@yourorg/mcp-myapi",
"version": "1.0.0",
"type": "module",
"bin": { "mcp-myapi": "./dist/server.js" },
"files": ["dist"],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
}
}npm publish --access publicUse with CodeSpar
Once published, your server can be used in CodeSpar sessions:
const session = await codespar.sessions.create({
servers: ["myapi", "stripe", "correios"],
});
const result = await session.execute("myapi_create_order", {
customer_id: "cus_123",
items: [{ product_id: "prod_abc", quantity: 2 }],
});To register your server in the CodeSpar catalog so other users can discover it, open an issue on GitHub or contact us at enterprise@codespar.dev.
Best practices for LLM-friendly tools
-
Write clear descriptions. The LLM reads
descriptionto decide when to call the tool. Be specific: "Create a new order with line items and shipping address" is better than "Create order". -
Use descriptive parameter names.
customer_idis better thancid. The LLM maps natural language to parameter names. -
Add
.describe()to every field. Zod's.describe()becomes thedescriptionfield in JSON Schema, which the LLM uses to understand what to pass. -
Return structured data. Return objects with clear field names, not raw strings. The LLM can reason better about
{ "order_id": "ord_123", "status": "paid" }than"Order created successfully". -
Prefix tool names with your server name. Use
myapi_create_order, notcreate_order. This avoids collisions when multiple servers are loaded in the same session. -
Handle errors gracefully. Return error objects instead of throwing. The LLM can reason about
{ "error": "Customer not found" }and try a different approach. -
Keep input schemas simple. Avoid deeply nested objects. Flat schemas with clear types produce better LLM tool-calling accuracy.
Using the MCP Generator instead
If you have an existing REST API with Express, Fastify, or Next.js routes, the MCP Generator can automatically scan your source code and generate the tool definitions and server code. This is faster than writing tools by hand for large APIs.
Next steps
- MCP Generator — Auto-generate servers from existing APIs
- Servers & Toolkits — Browse the existing catalog
- Tools & Meta-Tools — How tools work in CodeSpar
- How CodeSpar Works — Architecture overview