Architecture
This page describes the project structure, key patterns, and the request flow from MCP tool invocation through to the VirtFusion API.
Project Structure
virtfusion-mcp/
src/
index.ts Entry point: env validation, McpServer + StdioServerTransport setup
client.ts VirtFusionClient: fetch wrapper with Bearer auth, query param filtering
errors.ts VirtFusionApiError class + formatErrorResponse() for MCP error results
types.ts Shared interfaces (PaginatedResponse, QueryParams)
tools/
index.ts Barrel: registerAllTools() wires all 17 modules to McpServer
general.ts 1 tool (test connection)
hypervisors.ts 2 tools
hypervisor-groups.ts 3 tools
servers.ts 18 tools (largest module)
servers-power.ts 4 tools
servers-network.ts 5 tools
servers-firewall.ts 4 tools
servers-traffic.ts 4 tools
ip-blocks.ts 3 tools
backups.ts 1 tool
dns.ts 1 tool
media.ts 2 tools
packages.ts 2 tools
queue.ts 1 tool
ssh-keys.ts 4 tools
users.ts 7 tools
self-service.ts 19 tools
scripts/
extract-endpoints.ts Parse openapi.yaml into JSON endpoint manifest
check-endpoint-drift.ts Compare spec vs manifest for drift detection
openapi.yaml VirtFusion Admin API OpenAPI specification
endpoint-manifest.json Snapshot of all known endpoints (84 total)
.gitea/workflows/ CI/CD workflow definitions
Core Modules
Entry Point (src/index.ts)
The entry point performs three tasks:
- Environment validation -- Checks for
VIRTFUSION_API_URLandVIRTFUSION_API_TOKEN, exiting with a descriptive error if either is missing. - Initialization -- Creates a
VirtFusionClientinstance and anMcpServerinstance, then callsregisterAllTools()to wire up all 84 tools. - Transport -- Connects the MCP server to a
StdioServerTransportfor communication with the MCP client.
HTTP Client (src/client.ts)
VirtFusionClient is a lightweight fetch wrapper that handles:
- Bearer authentication -- Automatically adds
Authorization: Bearer <token>to every request. - URL construction -- Combines the base URL with the path and query parameters. Trailing slashes on the base URL are stripped.
- Query parameter filtering -- Any query parameter with an
undefinedvalue is silently omitted. - Content-Type handling -- Only sets
Content-Type: application/jsonwhen a body is present. - 204 responses -- Returns
{ success: true }for No Content responses. - Error propagation -- Non-OK responses throw a
VirtFusionApiErrorwith the status code, status text, and parsed response body.
The client exposes four public methods: get(), post(), put(), and delete(). All share the signature (path, body?, query?).
Error Handling (src/errors.ts)
Two exports:
VirtFusionApiError-- AnErrorsubclass carryingstatusCode,statusText, anderrorBody. Thrown by the client on non-OK responses.formatErrorResponse()-- Converts any caught error into an MCP-compatibleCallToolResultwithisError: true. ForVirtFusionApiErrorinstances, it includes structured status and error detail information. For other errors, it includes the error message.
Shared Types (src/types.ts)
PaginatedResponse<T>-- Interface matching VirtFusion's paginated response format (current_page, data, links, total, etc.).QueryParams-- Type alias forRecord<string, string | number | boolean | undefined>.
Tool Module Pattern
Every tool module in src/tools/ follows the same pattern:
-
Export a single registration function with the signature:
export function registerXxxTools(server: McpServer, client: VirtFusionClient): void -
Register tools by calling
server.tool()with four arguments:- Tool name (snake_case)
- Human-readable description
- Zod schema object for parameter validation
- Async handler function
-
Handler structure -- Every handler follows this pattern:
async (params) => { try { // Build body/query from params const result = await client.get|post|put|delete(path, body?, query?); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return formatErrorResponse(error); } } -
Optional parameters -- When a tool has optional body parameters, the handler conditionally adds them to the body object only when defined.
Tool Registration
The barrel module src/tools/index.ts imports all 17 registration functions and exposes a single registerAllTools() function. The entry point calls this once during startup to wire everything up.
Request Flow
MCP Client (Claude, VS Code, etc.)
|
| MCP protocol (stdio)
v
McpServer (src/index.ts)
|
| Zod validates parameters
v
Tool Handler (src/tools/*.ts)
|
| Builds request body/query from validated params
v
VirtFusionClient (src/client.ts)
|
| fetch() with Bearer auth headers
v
VirtFusion REST API
|
| JSON response
v
VirtFusionClient
|
| Parses response; throws VirtFusionApiError on non-OK
v
Tool Handler
|
| Wraps result in MCP content format, or catches error
v
McpServer
|
| MCP protocol (stdio)
v
MCP Client
CI/CD Workflows
The project uses Gitea Actions with four workflows:
| Workflow | File | Trigger | Purpose |
|---|---|---|---|
| CI | ci.yaml |
Push, PR | Build and type-check |
| Endpoint Sync | endpoint-sync.yaml |
Weekly, openapi.yaml changes |
Detect API drift, create issue |
| Release | release.yaml |
Semver tag push | Create Gitea release with changelog |
| Version Check | version-check.yaml |
PRs changing src/ |
Warn if package.json version not bumped |
Key Design Decisions
- No runtime dependencies beyond MCP SDK and Zod -- The client uses Node.js built-in
fetch(available in Node 22+). - Flat tool namespace -- All 84 tools share a single namespace with
snake_casenaming convention{category}_{action}_{noun}. - Server-side safety -- The
servers_deletetool enforces a minimum 300-second delay to prevent accidental destruction. - All responses as JSON text -- Every tool returns its API response as pretty-printed JSON in a text content block, keeping the interface uniform.