> ## Documentation Index
> Fetch the complete documentation index at: https://learn.orbexa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Building an MCP Server in TypeScript or Python

> Build an MCP Server from scratch — complete TypeScript and Python development guide, including outputSchema, structuredContent, Streamable HTTP, and debugging

# Building an MCP Server

## 4.1 Development Environment Setup

### TypeScript (Recommended)

```bash theme={null}
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
npx tsc --init
```

### Python

```bash theme={null}
mkdir my-mcp-server && cd my-mcp-server
pip install mcp
```

## 4.2 Complete TypeScript Example

An MCP Server providing a weather query tool:

```typescript theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

// Create Server instance
const server = new McpServer({
  name: "weather-server",
  version: "1.0.0"
});

// Define a tool
server.tool(
  "get_weather",              // Tool name
  "Get current weather for a specified city",  // Description
  {                            // Input parameter schema
    city: {
      type: "string",
      description: "City name"
    }
  },
  async ({ city }) => {        // Execution function
    // Call your weather API here
    const weather = await fetchWeather(city);
    return {
      content: [{
        type: "text",
        text: `Current weather in ${city}: ${weather.temp} C, ${weather.condition}`
      }]
    };
  }
);

// Start (stdio transport)
const transport = new StdioServerTransport();
await server.connect(transport);
```

### Multi-Tool Server (with outputSchema and structuredContent)

Protocol version `2025-11-25` supports `outputSchema` for defining structured output format, with `structuredContent` for returning structured data:

```typescript theme={null}
const server = new McpServer({
  name: "commerce-server",
  version: "1.0.0"
});

// Tool 1: Search products (with outputSchema)
server.tool(
  "search_products",
  "Search the product catalog",
  {
    query: { type: "string", description: "Search keyword" },
    category: { type: "string", description: "Product category (optional)" },
    limit: { type: "number", description: "Number of results, default 10" }
  },
  async ({ query, category, limit = 10 }) => {
    const results = await db.products.search(query, { category, limit });
    const data = {
      products: results.map(p => ({
        name: p.name, sku: p.sku, price: p.price, inStock: p.inventory > 0
      })),
      total: results.totalCount
    };
    return {
      content: [{
        type: "text",
        text: JSON.stringify(data, null, 2)
      }],
      // structuredContent: for programmatic processing (when outputSchema is defined)
      structuredContent: data
    };
  }
);

// Tool 2: Check inventory
server.tool(
  "check_inventory",
  "Query real-time product inventory",
  {
    sku: { type: "string", description: "Product SKU" }
  },
  async ({ sku }) => {
    const inventory = await db.inventory.getBySku(sku);
    if (!inventory) {
      return {
        content: [{ type: "text", text: `SKU ${sku} does not exist` }],
        isError: true
      };
    }
    return {
      content: [{
        type: "text",
        text: `SKU ${sku}: ${inventory.quantity} units (${inventory.warehouse} warehouse)`
      }]
    };
  }
);

// Tool 3: Get order
server.tool(
  "get_order",
  "Query order status",
  {
    orderId: { type: "string", description: "Order ID" }
  },
  async ({ orderId }) => {
    const order = await db.orders.getById(orderId);
    if (!order) {
      return {
        content: [{ type: "text", text: `Order ${orderId} not found` }],
        isError: true
      };
    }
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          id: order.id,
          status: order.status,
          items: order.items.length,
          total: order.total,
          tracking: order.trackingNumber
        }, null, 2)
      }]
    };
  }
);
```

## 4.3 Complete Python Example

```python theme={null}
from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types

server = Server("commerce-server")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="search_products",
            description="Search the product catalog",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "Search keyword"},
                    "limit": {"type": "number", "description": "Number of results"}
                },
                "required": ["query"]
            }
        ),
        types.Tool(
            name="check_inventory",
            description="Query real-time product inventory",
            inputSchema={
                "type": "object",
                "properties": {
                    "sku": {"type": "string", "description": "Product SKU"}
                },
                "required": ["sku"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "search_products":
        results = await search_products(
            arguments["query"],
            arguments.get("limit", 10)
        )
        return [types.TextContent(
            type="text",
            text=json.dumps(results, ensure_ascii=False)
        )]
    elif name == "check_inventory":
        inventory = await get_inventory(arguments["sku"])
        return [types.TextContent(
            type="text",
            text=f"SKU {arguments['sku']}: {inventory['quantity']} units"
        )]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
```

## 4.4 Exposing Resources

In addition to tools, Servers can also expose data resources:

```typescript theme={null}
// Expose product categories as a resource
server.resource(
  "commerce://catalog/categories",
  "Product Categories",
  "application/json",
  async () => {
    const categories = await db.categories.getAll();
    return JSON.stringify(categories, null, 2);
  }
);

// Expose company information as a resource
server.resource(
  "commerce://company/info",
  "Company Information",
  "text/markdown",
  async () => {
    return `# Company Name\n\n> One-line description\n\n## Main Business\n...`;
  }
);
```

## 4.5 Streamable HTTP Server

Use Streamable HTTP transport for remote deployment:

```typescript theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

const app = express();
app.use(express.json());

const server = new McpServer({
  name: "my-remote-server",
  version: "1.0.0"
});

// Register tools (same as above)...

// Create Streamable HTTP transport
const transport = new StreamableHTTPServerTransport({
  endpoint: "/mcp"
});

// Mount to Express
app.post("/mcp", transport.handlePost.bind(transport));
app.get("/mcp", transport.handleGet.bind(transport));

await server.connect(transport);

app.listen(3000, () => {
  console.error("MCP Server (Streamable HTTP) running on port 3000");
});
```

## 4.6 Testing and Debugging

### MCP Inspector

The official interactive debugging tool:

```bash theme={null}
npx @modelcontextprotocol/inspector node dist/index.js
```

The Inspector opens a web interface where you can:

* View all tools, resources, and prompts exposed by the Server
* Manually enter parameters and execute tools
* Inspect the raw JSON-RPC requests and responses
* Validate inputSchema and outputSchema

### Testing in Claude Desktop

Edit the Claude Desktop configuration file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json theme={null}
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["D:/path/to/my-server/dist/index.js"]
    }
  }
}
```

Restart Claude Desktop and try using your tools in a conversation.

## 4.7 Common Errors

| Error                              | Cause                                                       | Fix                                               |
| ---------------------------------- | ----------------------------------------------------------- | ------------------------------------------------- |
| Server starts but does not respond | `console.log` output to stdout interferes with the protocol | Send logs to stderr instead                       |
| Tools do not appear                | `tools/list` returns empty                                  | Check tool registration code                      |
| Parameter validation fails         | inputSchema and actual parameters mismatch                  | Check JSON Schema definition                      |
| Connection timeout                 | Server process did not start correctly                      | Check command and args configuration              |
| `structuredContent` is ignored     | No `outputSchema` defined                                   | Add outputSchema or use content only              |
| Initialization fails               | Protocol version mismatch                                   | Confirm Client and Server use compatible versions |

<Warning>
  **stdio Servers must never use `console.log`**. stdout is the protocol channel; any non-JSON-RPC output will break communication. Use `console.error` or write to a log file instead.
</Warning>

***

**Next Chapter**: [Commerce MCP Server](/en/book-5/ch5-commerce-server) — MCP Server design patterns for e-commerce scenarios
