---
title: Build Your Own Server
description: Create a custom MCP server that integrates with CodeSpar's session model, billing, and tool routing.
---

import { Callout } from "fumadocs-ui/components/callout";
import { Step, Steps } from "fumadocs-ui/components/steps";

# Build Your Own Server

CodeSpar's MCP server catalog covers every major Latin American commerce API. 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](https://modelcontextprotocol.io). 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

```bash
npm install @modelcontextprotocol/sdk
```

## Step-by-step

<Steps>
<Step>
### Define your tools

Start by listing the operations your API supports. Each operation becomes a tool:

```typescript title="tools.ts"
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),
    }),
  },
};
```
</Step>
<Step>
### Implement the handlers

Each handler receives the validated input and calls your API:

```typescript title="handlers.ts"
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,
  });
}
```
</Step>
<Step>
### Create the MCP server

Wire tools and handlers together into an MCP server:

```typescript title="server.ts"
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);
```
</Step>
<Step>
### Package and publish

Create a `package.json` and publish to npm:

```json title="package.json"
{
  "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"
  }
}
```

```bash
npm publish --access public
```
</Step>
<Step>
### Use with CodeSpar

Once published, your server can be used in CodeSpar sessions:

```typescript
const session = await codespar.create("user_123", {
  servers: ["myapi", "stripe", "correios"],
});

const result = await session.execute("myapi_create_order", {
  customer_id: "cus_123",
  items: [{ product_id: "prod_abc", quantity: 2 }],
});
```

<Callout type="info">
To register your server in the CodeSpar catalog so other users can discover it, [open an issue on GitHub](https://github.com/codespar/codespar-core/issues) or contact us at enterprise@codespar.dev.
</Callout>
</Step>
</Steps>

## Best practices for LLM-friendly tools

1. **Write clear descriptions.** The LLM reads `description` to decide when to call the tool. Be specific: "Create a new order with line items and shipping address" is better than "Create order".

2. **Use descriptive parameter names.** `customer_id` is better than `cid`. The LLM maps natural language to parameter names.

3. **Add `.describe()` to every field.** Zod's `.describe()` becomes the `description` field in JSON Schema, which the LLM uses to understand what to pass.

4. **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"`.

5. **Prefix tool names with your server name.** Use `myapi_create_order`, not `create_order`. This avoids collisions when multiple servers are loaded in the same session.

6. **Handle errors gracefully.** Return error objects instead of throwing. The LLM can reason about `{ "error": "Customer not found" }` and try a different approach.

7. **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, the [MCP Generator](/docs/mcp-generator) can scan it — from pasted Express/Fastify/Next.js source, an OpenAPI 3.x spec, or a GitHub repo — and generate the tool definitions and server code for you. The no-code dashboard wizard (**Develop › Generate**) goes further: it onboards the result as a routable, governed rail in your project. Faster than writing tools by hand for large APIs.

## Next steps

<NextStepsGrid items={[
  { label: "BUILD", title: "MCP Generator", description: "Auto-generate servers from existing REST APIs.", href: "/docs/mcp-generator" },
  { label: "SERVERS", title: "Servers & Toolkits", description: "Browse the existing MCP catalog before building your own.", href: "/docs/servers" },
  { label: "CONCEPT", title: "Tools & Meta-Tools", description: "How tools work in CodeSpar end-to-end.", href: "/docs/concepts/tools" },
  { label: "ARCHITECTURE", title: "How CodeSpar Works", description: "Request lifecycle and the full stack diagram.", href: "/docs/how-it-works" },
]} />
