← Home
Development Guide
Best practices and guidelines for developing MCP servers built from the Template-MCP template.
Prerequisites
- Node.js 20.0.0 or later
- npm (included with Node.js)
- TypeScript knowledge (strict mode, ES modules)
- A REST API to bridge (with documentation and credentials)
Getting Started
1. Create a Repo from the Template
On Gitea, click Use this template on the Template-MCP page.
Name your repo following the convention:
mcp-moko{service}for service-specific servers (e.g.,mcp-mokowaas,mcp-mokocrm){function}-mcpfor function-specific servers (e.g.,ssh-mcp,deploy-mcp)
2. Clone and Install
git clone git@git.mokoconsulting.tech:MokoConsulting/my-mcp.git
cd my-mcp
npm install
3. Update Project Metadata
| File | What to Change |
|---|---|
package.json |
name, description, version |
src/config.ts |
Config file name (default: ~/.<project>.json) |
scripts/setup.mjs |
Prompts and defaults for the setup wizard |
README.md |
Project overview and tool documentation |
Project Structure
my-mcp/
├── src/
│ ├── index.ts # Server entry point -- tool registration
│ ├── client.ts # HTTP client (ApiClient class)
│ ├── config.ts # Config loader (multi-connection)
│ └── types.ts # TypeScript interfaces
├── scripts/
│ └── setup.mjs # Interactive setup wizard
├── dist/ # Compiled output (gitignored)
├── package.json
├── tsconfig.json
└── Makefile
Adding MCP Tools
Tools are registered in src/index.ts. Each tool maps to one or more REST API operations.
1. Define the Tool
server.tool(
'resource_list', // Tool name (snake_case)
'List all resources with optional search', // Description
{
search: z.string().optional().describe('Search query'),
limit: z.number().optional().describe('Max results'),
...PaginationParams,
...ConnectionParam,
},
async ({ search, limit, page, connection }) => {
const client = getClient(connection);
const params: Record<string, string> = {};
if (search) params.search = search;
if (limit) params.limit = String(limit);
if (page !== undefined) params.page = String(page);
const result = await client.get('/api/resources', params);
return formatResponse(result);
}
);
2. Use Shared Helpers
The template provides these reusable patterns:
| Helper | Purpose |
|---|---|
ConnectionParam |
Zod spread for optional connection parameter |
PaginationParams |
Zod spread for limit and page parameters |
formatResponse() |
Normalizes API responses into MCP text content |
paginationQuery() |
Builds pagination query params from input |
getClient() |
Resolves a named connection to an ApiClient instance |
3. Naming Conventions
| Pattern | Example |
|---|---|
| List resources | resource_list |
| Get single resource | resource_get |
| Create resource | resource_create |
| Update resource | resource_update |
| Delete resource | resource_delete |
| Custom actions | resource_action_name |
The ApiClient
src/client.ts handles all HTTP communication:
const client = new ApiClient(connection);
// GET with query params
const result = await client.get('/api/endpoint', { key: 'value' });
// POST with JSON body
const result = await client.post('/api/endpoint', { name: 'New Resource' });
// PUT, PATCH, DELETE
const result = await client.put('/api/endpoint/1', { name: 'Updated' });
const result = await client.patch('/api/endpoint/1', { status: 'active' });
const result = await client.delete('/api/endpoint/1');
The client uses node:https / node:http (not fetch) for reliable self-signed certificate support. Set "insecure": true in the connection config to skip TLS verification.
Configuration System
The multi-connection config at ~/.<project>.json:
{
"defaultConnection": "production",
"connections": {
"production": {
"baseUrl": "https://api.example.com",
"apiKey": "prod-key"
},
"staging": {
"baseUrl": "https://api-staging.example.com",
"apiKey": "staging-key",
"insecure": true
}
}
}
Every tool accepts an optional connection parameter. If omitted, the default connection is used. This lets users target different environments from the same MCP server.
Build and Run
npm run build # Compile TypeScript to dist/
npm start # Run the MCP server (stdio)
npm run dev # Watch mode for development
npm run clean # Remove dist/
npm run lint # Run ESLint
npm run setup # Interactive config setup wizard
Or use Make:
make build # npm run build
make dev # npm run dev
make clean # npm run clean
make lint # npm run lint
Testing
Manual Testing with Claude Code
- Build the server:
npm run build - Register it in
.mcp.jsonor~/.claude.json:
{
"mcpServers": {
"my-mcp": {
"type": "stdio",
"command": "node",
"args": ["/path/to/my-mcp/dist/index.js"]
}
}
}
- Restart Claude Code and test the tools interactively
Testing API Responses
Use npm run dev for watch mode. Make changes to src/index.ts, and the server recompiles automatically.
Branching Strategy
| Branch | Purpose |
|---|---|
main |
Stable release |
dev |
Integration branch |
feature/* |
Feature work (merges to dev) |
Common Patterns
Handling Paginated APIs
server.tool(
'items_list',
'List items with pagination',
{
...PaginationParams,
...ConnectionParam,
},
async ({ limit, page, connection }) => {
const client = getClient(connection);
const params = paginationQuery({ limit, page });
const result = await client.get('/api/items', params);
return formatResponse(result);
}
);
Handling File Uploads
// For APIs that accept multipart form data,
// extend ApiClient with a custom method or use
// the raw api_request tool for complex cases.
Error Handling
formatResponse() handles error normalization. For custom error handling:
const result = await client.get('/api/resource/' + id);
if (result.statusCode === 404) {
return { content: [{ type: 'text', text: `Resource ${id} not found` }] };
}
return formatResponse(result);
Related
- Installation -- prerequisites and setup
- API Reference -- tool reference and connection parameters
- Architecture -- design decisions and data flow
- MokoStandards -- central standards wiki
Repo: Template-MCP · MokoStandards
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-19 | Moko Consulting | Initial version |