How to Extend the API with New Modules in the MCP-OpenAPI Server: Equipment Example
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/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/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/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/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.
// 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:
// 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:
// 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:
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:
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:
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
chanfanalibrary. - 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-specpath 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 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 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:
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:
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →