# How to Extend the API with New Modules in the MCP-OpenAPI Server: Equipment Example

> Extend the MCP-OpenAPI server with new modules like Equipment. Add REST controllers and update the OpenAPI spec to create new callable tools easily.

- Repository: [Lucas Zanoni⠀⠀⠀⠀⠀ ⠀╱|、 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ (˚ˎ 。7 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |、˜〵 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ じしˍ,)ノ/tcc](https://github.com/castrozan/tcc)
- Tags: how-to-guide
- Published: 2026-02-27

---

**You can extend the MCP-OpenAPI server with new modules by adding REST controllers to the dummy application and ensuring the OpenAPI specification reflects the new endpoints—the MCP server automatically converts these into callable tools without requiring additional MCP-specific code.**

The `castrozan/tcc` repository implements a generic MCP (Model-Context-Protocol) server that transforms any OpenAPI 3.x specification into conversational tools. This guide demonstrates how to extend the API with new modules—using the existing **Equipment** domain as a working example—by leveraging the declarative architecture of the OpenAPI specification and the automated tool generation provided by `OpenAPISpecLoader` and `ToolsManager`.

## Architecture Overview

The system follows a declarative pattern where the OpenAPI specification serves as the single source of truth. When you add new endpoints to the underlying REST API, the MCP layer automatically exposes them as tools.

### Key Components

| Component | Role | Source File |
|-----------|------|-------------|
| **OpenAPISpecLoader** | Retrieves and parses the OpenAPI spec, handling `$ref` inlining and type inference. | [[`mcp-openapi-server/src/openapi-loader.ts`](https://github.com/castrozan/tcc/blob/main/mcp-openapi-server/src/openapi-loader.ts)](https://github.com/castrozan/tcc/blob/master/mcp-openapi-server/src/openapi-loader.ts) |
| **ToolsManager** | Converts each OpenAPI operation into an MCP `Tool` with proper input schemas. Supports filtering via tags or tool names. | [[`mcp-openapi-server/src/tools-manager.ts`](https://github.com/castrozan/tcc/blob/main/mcp-openapi-server/src/tools-manager.ts)](https://github.com/castrozan/tcc/blob/master/mcp-openapi-server/src/tools-manager.ts) |
| **OpenAPIServer** | Core MCP server that registers handlers for `listTools` and `callTool`, delegating execution to the API client. | [[`mcp-openapi-server/src/server.ts`](https://github.com/castrozan/tcc/blob/main/mcp-openapi-server/src/server.ts)](https://github.com/castrozan/tcc/blob/master/mcp-openapi-server/src/server.ts) |
| **Dummy Application** | Hono.js REST API providing the actual endpoints (Equipment CRUD). Auto-generates OpenAPI documentation via `chanfana`. | [[`equipments-dummy-app/src/infrastructure/web/open-api/server.ts`](https://github.com/castrozan/tcc/blob/main/equipments-dummy-app/src/infrastructure/web/open-api/server.ts)](https://github.com/castrozan/tcc/blob/master/equipments-dummy-app/src/infrastructure/web/open-api/server.ts) |

## Step-by-Step Guide to Adding a New Module

Follow these steps to extend the API with a new domain module. The Equipment implementation serves as the reference pattern.

### Step 1: Create the REST Controller

Extend `OpenAPIRoute` from `chanfana` to define the endpoint schema and handler. The schema automatically generates the OpenAPI documentation.

```typescript
// File: equipments-dummy-app/src/presentation/controllers/equipments/CreateEquipmentController.ts
import { CreateEquipmentUseCase } from 'application/use-cases/equipments/CreateEquipmentUseCase';
import { Bool, OpenAPIRoute } from 'chanfana';
import equipmentRepository from 'infrastructure/database/repositories/equipments';
import { withErrorHandling } from 'presentation/decorators';
import { z } from 'zod';

export class CreateEquipmentController extends OpenAPIRoute {
    schema = {
        tags: ['Equipments'],
        summary: 'Create a new Equipment',
        security: [{ bearerAuth: [] }],
        request: { 
            body: { 
                content: { 
                    'application/json': { 
                        schema: z.object({
                            name: z.string().min(1),
                            description: z.string().min(1),
                            imageUrl: z.string().url().optional(),
                            type: z.string().optional(),
                        })
                    }
                }
            }
        },
        responses: {
            '201': { 
                description: 'Equipment created', 
                content: { 
                    'application/json': { 
                        schema: z.object({
                            success: Bool(),
                            result: z.object({
                                id: z.number(),
                                name: z.string(),
                                description: z.string(),
                                imageUrl: z.string().nullable(),
                                createdAt: z.string(),
                                type: z.string().nullable(),
                            })
                        })
                    }
                }
            }
        }
    };

    @withErrorHandling
    async handle(): Promise<object> {
        const data = await this.getValidatedData<typeof this.schema>();
        const { name, description, imageUrl, type } = data.body;
        const useCase = new CreateEquipmentUseCase(equipmentRepository);
        const equipment = await useCase.execute({ name, description, imageUrl, type });
        return {
            success: true,
            result: {
                id: equipment.id,
                name: equipment.name,
                description: equipment.description,
                imageUrl: equipment.imageUrl,
                createdAt: equipment.createdAt.toISOString(),
                type: equipment.type,
            },
        };
    }
}

```

Create analogous controllers for **Read** (`FindByIdEquipmentController`), **Update** (`UpdateEquipmentController`), **Delete** (`DeleteEquipmentController`), and **List** (`FindAllEquipmentController`) following the same pattern.

### Step 2: Implement Use Cases and Repository

The controller delegates business logic to use cases and persistence to repositories. For Equipment, the repository uses SQLite:

```typescript
// File: equipments-dummy-app/src/infrastructure/database/repositories/equipments/SQLiteEquipmentRepository.ts
// Implements CRUD operations for Equipment entities

```

The use cases (`CreateEquipmentUseCase`, `FindAllEquipmentUseCase`, etc.) reside in `equipments-dummy-app/src/application/use-cases/equipments/`.

### Step 3: Register Routes in the Dummy App

Bind the controllers to HTTP endpoints in the Hono server:

```typescript
// File: equipments-dummy-app/src/infrastructure/web/open-api/server.ts
import { fromHono } from 'chanfana';
import { Hono } from 'hono';
import { config } from 'dotenv';
import {
    CreateEquipmentController,
    DeleteEquipmentController,
    FindAllEquipmentController,
    FindByIdEquipmentController,
    UpdateEquipmentController,
} from 'presentation/controllers/equipments';

const app = new Hono();
const openapi = fromHono(app, { docs_url: '/', schema: { security: [{ bearerAuth: [] }] } });

openapi.registry.registerComponent('securitySchemes', 'bearerAuth', {
    type: 'http',
    scheme: 'bearer',
});

config();

// Register CRUD routes
openapi.get('/public/equipment', FindAllEquipmentController);
openapi.get('/public/equipment/:id', FindByIdEquipmentController);
openapi.put('/equipment', UpdateEquipmentController);
openapi.post('/equipment', CreateEquipmentController);
openapi.delete('/equipment/:id', DeleteEquipmentController);

export default app;

```

### Step 4: Expose the OpenAPI Specification

The `chanfana` library auto-generates the OpenAPI specification at the root path (`/`). Ensure the dummy app is running:

```bash
cd equipments-dummy-app
npm install
npm run dev

# OpenAPI JSON available at http://localhost:3000/openapi.json

```

### Step 5: Restart the MCP Server

Point the MCP server to the new specification:

```bash
cd mcp-openapi-server
node src/index.js \
  --transport http \
  --port 4000 \
  --host 127.0.0.1 \
  --path /mcp \
  --api-base-url http://localhost:3000 \
  --openapi-spec http://localhost:3000/openapi.json

```

The `OpenAPISpecLoader` parses the spec, and `ToolsManager` automatically creates tools for each operation (e.g., `POST-/equipment` becomes tool ID `POST-equipment`).

## Filtering and Customizing Tools

If you need to expose only specific tools from the new module, use the filtering options in `ToolsManager`:

```bash
node src/index.js \
  --openapi-spec http://localhost:3000/openapi.json \
  --tag Equipments \
  --tool create-equipment \
  --tool get-equipment

```

The `includeTools` and `includeTags` logic in `ToolsManager.initialize()` (lines 85-130) applies these filters when building the final `Map<string, Tool>`.

## Summary

- **Extend the API with new modules** by adding REST controllers to the dummy application using the `chanfana` library.
- The **OpenAPI specification** serves as the single source of truth; the MCP server requires no code changes to support new endpoints.
- **Restart the MCP server** with the updated `--openapi-spec` path to automatically generate tools for the new module.
- Use **filtering options** (`--tag`, `--tool`) to control which endpoints are exposed as MCP tools.

## Frequently Asked Questions

### Do I need to modify the MCP server code to add new modules?

No. The MCP server in `castrozan/tcc` is designed to be generic. As long as your REST API exposes a valid OpenAPI 3.x specification, the `OpenAPISpecLoader` and `ToolsManager` will automatically convert new endpoints into MCP tools without requiring changes to [`mcp-openapi-server/src/server.ts`](https://github.com/castrozan/tcc/blob/main/mcp-openapi-server/src/server.ts) or related files.

### How does the MCP server handle authentication for new modules?

The MCP server forwards authentication headers transparently. In the dummy app, controllers specify `security: [{ bearerAuth: [] }]` in their schema, and the route registration in [`server.ts`](https://github.com/castrozan/tcc/blob/main/server.ts) registers the bearerAuth component. When calling tools through the MCP server, include the authorization token in the request context; the `ApiClient` will pass it through to the underlying REST endpoints.

### Can I use a YAML file instead of the auto-generated JSON specification?

Yes. The `OpenAPISpecLoader` supports multiple sources including local files (YAML or JSON), URLs, stdin, or inline strings. You can point the MCP server to a static YAML file:

```bash
node src/index.js --openapi-spec ./my-api-spec.yaml --api-base-url http://localhost:3000

```

The loader will parse the YAML and apply the same tool generation logic as with JSON specifications.

### What if I only want to expose specific operations from a large module?

Use the filtering capabilities built into `ToolsManager`. You can include or exclude tools based on tags or specific operation IDs using CLI flags:

```bash
node src/index.js \
  --openapi-spec http://localhost:3000/openapi.json \
  --tag Equipments \
  --exclude-tool delete-equipment

```

This allows you to granularly control which CRUD operations are available to MCP clients without modifying the underlying REST API.