← Home
Best practices and guidelines for developing MCP servers built from the Template-MCP template.
On MokoGitea, 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-mokosuite, mcp-mokocrm){function}-mcp for function-specific servers (e.g., ssh-mcp, deploy-mcp)git clone git@git.mokoconsulting.tech:MokoConsulting/my-mcp.git
cd my-mcp
npm install
| 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 |
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
Tools are registered in src/index.ts. Each tool maps to one or more REST API operations.
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);
}
);
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 |
| 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 |
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.
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.
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
npm run build.mcp.json or ~/.claude.json:{
"mcpServers": {
"my-mcp": {
"type": "stdio",
"command": "node",
"args": ["/path/to/my-mcp/dist/index.js"]
}
}
}
Use npm run dev for watch mode. Make changes to src/index.ts, and the server recompiles automatically.
| Branch | Purpose |
|---|---|
main |
Stable release |
dev |
Integration branch |
feature/* |
Feature work (merges to dev) |
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);
}
);
// For APIs that accept multipart form data,
// extend ApiClient with a custom method or use
// the raw api_request tool for complex cases.
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);
Repo: Template-MCP · MokoStandards
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-19 | Moko Consulting | Initial version |
Printed from wiki · development