Authoring an MCP server
Developing an MCP worker in Speedwave involves writing an isolated microservice process exposing domain capabilities (such as GitLab, Slack, or OS APIs) as Model Context Protocol (MCP) tools. Claude communicates exclusively with the central Tool Gateway, which routes JSON-RPC tool invocations over internal Docker network bridges (http://mcp-{name}:{port}) to the target worker container.
The mcp-servers/gitlab package serves as the canonical reference implementation.
Using @speedwave/mcp-shared
Section titled “Using @speedwave/mcp-shared”All MCP workers must import @speedwave/mcp-shared rather than implementing custom HTTP bootstrap logic. The package provides:
bootWorker: Standardized server initialization lifecycle.Tool: TypeScript interface definition with JSON Schema validation.withClientValidation/withResultValidation: Execution guards for unconfigured or errored states.sanitize: Secret redaction utility for log streams.
Bootstrapping a worker service
Section titled “Bootstrapping a worker service”Initialize your server entrypoint using bootWorker():
import { bootWorker, ts, makeStandardHealthCheck } from '@speedwave/mcp-shared';import { initializeGitLabClient, type GitLabClient } from './client.js';import { createToolDefinitions } from './tools/index.js';
bootWorker<GitLabClient>({ serverName: 'mcp-gitlab', version: '1.0.0', displayName: 'GitLab', authTokenEnv: 'MCP_GITLAB_AUTH_TOKEN', host: '0.0.0.0', initClient: initializeGitLabClient, makeTools: (client) => createToolDefinitions(client), makeHealthCheck: (client) => makeStandardHealthCheck(client),}).catch((error) => { console.error(`${ts()} Fatal bootstrap error:`, error); process.exit(1);});bootWorker parses PORT allocations dynamically, handles token file validation, and manages retry backoffs.
Credential mounting
Section titled “Credential mounting”Workers must mount only scoped secrets at /tokens/ in read-only mode (see Credentials management):
Directory/tokens/
- token API access bearer token
- host_url Optional custom instance endpoint
If tokens are missing or invalid, initialize the client as null. Handlers will return structured “Service not configured” errors rather than crashing the container.
Defining MCP tools
Section titled “Defining MCP tools”Define tools using the Tool schema interface:
import { Tool, READ_ONLY_ANNOTATIONS } from '@speedwave/mcp-shared';
export const listBranchesTool: Tool = { name: 'listBranches', description: 'List branches in a repository', annotations: READ_ONLY_ANNOTATIONS, _meta: { deferLoading: true }, keywords: ['gitlab', 'branches', 'list', 'git', 'refs'], example: 'const branches = await gitlab.listBranches({ project_id: "speedwave/core" })', inputSchema: { type: 'object', properties: { project_id: { type: ['string', 'number'], description: 'Project ID or slug' }, search: { type: 'string', description: 'Search term for branch filtering' }, limit: { type: 'number', description: 'Maximum records to return (default 20)' }, }, required: ['project_id'], },};Tool annotations communicate safety boundaries to Claude:
| Annotation constant | readOnlyHint | destructiveHint | Intended operation type |
|---|---|---|---|
READ_ONLY_ANNOTATIONS | true | false | Read queries (listBranches, getIssue). |
WRITE_ANNOTATIONS | false | false | Resource creation/updates (createBranch). |
DESTRUCTIVE_ANNOTATIONS | false | true | Irreversible actions (deleteBranch). |
Wrapping tool handlers
Section titled “Wrapping tool handlers”Pair tool declarations with implementation handlers in functional factory modules (createBranchTools(client)):
- Use
withClientValidationwhen wrapping functions that throw API exceptions. - Use
withResultValidationwhen wrapping functions returning structured{ success, data, error }result payloads. - Filter diagnostic log payloads through
sanitize(data)to prevent credential leakage.
Testing guidelines
Section titled “Testing guidelines”Validate tools by invoking factory functions against mocked API adapters using Vitest:
- Verify metadata schema completeness (names, descriptions, annotations, keywords).
- Test standard execution with mocked client responses.
- Test missing or malformed parameter validation.
- Test error propagation and unconfigured client handling.
Execute make test-mcp and verify 100% test coverage with make coverage-mcp.