pocketsmith-mcp
Version:
MCP server for managing budgets via PocketSmith API
137 lines (136 loc) • 6.83 kB
JavaScript
/**
* @fileoverview Main entry point for the MCP (Model Context Protocol) server.
* This file orchestrates the server's lifecycle:
* 1. Initializes the core `McpServer` instance (from `@modelcontextprotocol/sdk`) with its identity and capabilities.
* 2. Registers available resources and tools, making them discoverable and usable by clients.
* 3. Selects and starts the appropriate communication transport (stdio or Streamable HTTP)
* based on configuration.
* 4. Handles top-level error management during startup.
*
* MCP Specification References:
* - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx
* - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx
* - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
* @module src/mcp-server/server
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { config, environment } from "../config/index.js";
import { ErrorHandler, logger, requestContextService } from "../utils/index.js";
import { BaseErrorCode } from "../types-global/errors.js";
import { registerEchoResource } from "./resources/echoResource/index.js";
import { registerCatFactFetcherTool } from "./tools/catFactFetcher/index.js";
import { registerEchoTool } from "./tools/echoTool/index.js";
import { registerFetchImageTestTool } from "./tools/imageTest/index.js";
import { registerGetAccountsTool, registerGetTransactionsTool, registerGetTransactionTool, registerCreateTransactionTool, registerUpdateTransactionTool, registerDeleteTransactionTool, registerGetCategoresTool, registerCreateCategoryTool, registerUpdateCategoryTool, registerGetBudgetsTool, registerGetUserSummaryTool, registerGetTransactionAttachmentsTool, registerGetUserAttachmentsTool, registerCreateTransactionAttachmentTool, registerGetCategoryRulesTool, registerCreateCategoryRuleTool, registerGetRecurringEventsTool, registerGetAccountTransactionsTool, registerGetBudgetSummaryTool, registerGetCurrenciesTool, registerGetInstitutionsTool, registerGetTransactionAccountsTool, } from "./tools/pocketsmith/index.js";
import { startHttpTransport } from "./transports/httpTransport.js";
import { connectStdioTransport } from "./transports/stdioTransport.js";
/**
* Creates and configures a new instance of the `McpServer`.
*
* @returns A promise resolving with the configured `McpServer` instance.
* @throws {McpError} If any resource or tool registration fails.
* @private
*/
async function createMcpServerInstance() {
const context = requestContextService.createRequestContext({
operation: "createMcpServerInstance",
});
logger.info("Initializing MCP server instance", context);
requestContextService.configure({
appName: config.mcpServerName,
appVersion: config.mcpServerVersion,
environment,
});
const server = new McpServer({ name: config.mcpServerName, version: config.mcpServerVersion }, {
capabilities: {
logging: {},
resources: { listChanged: true },
tools: { listChanged: true },
},
});
await ErrorHandler.tryCatch(async () => {
logger.debug("Registering resources and tools...", context);
await registerEchoResource(server);
await registerEchoTool(server);
await registerCatFactFetcherTool(server);
await registerFetchImageTestTool(server);
// Register PocketSmith tools
await registerGetAccountsTool(server);
await registerGetTransactionsTool(server);
await registerGetTransactionTool(server);
await registerCreateTransactionTool(server);
await registerUpdateTransactionTool(server);
await registerDeleteTransactionTool(server);
await registerGetCategoresTool(server);
await registerCreateCategoryTool(server);
await registerUpdateCategoryTool(server);
await registerGetBudgetsTool(server);
await registerGetUserSummaryTool(server);
await registerGetTransactionAttachmentsTool(server);
await registerGetUserAttachmentsTool(server);
await registerCreateTransactionAttachmentTool(server);
await registerGetCategoryRulesTool(server);
await registerCreateCategoryRuleTool(server);
await registerGetRecurringEventsTool(server);
await registerGetAccountTransactionsTool(server);
await registerGetBudgetSummaryTool(server);
await registerGetCurrenciesTool(server);
await registerGetInstitutionsTool(server);
await registerGetTransactionAccountsTool(server);
logger.info("Resources and tools registered successfully", context);
}, {
operation: "registerAllCapabilities",
context,
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
critical: true,
});
return server;
}
/**
* Selects, sets up, and starts the appropriate MCP transport layer based on configuration.
*
* @returns Resolves with `McpServer` for 'stdio', `http.Server` for 'http', or `void`.
* @throws {Error} If transport type is unsupported or setup fails.
* @private
*/
async function startTransport() {
const transportType = config.mcpTransportType;
const context = requestContextService.createRequestContext({
operation: "startTransport",
transport: transportType,
});
logger.info(`Starting transport: ${transportType}`, context);
if (transportType === "http") {
return startHttpTransport(createMcpServerInstance, context);
}
if (transportType === "stdio") {
const server = await createMcpServerInstance();
await connectStdioTransport(server, context);
return server;
}
throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
}
/**
* Main application entry point. Initializes and starts the MCP server.
*/
export async function initializeAndStartServer() {
const context = requestContextService.createRequestContext({
operation: "initializeAndStartServer",
});
logger.info("MCP Server initialization sequence started.", context);
try {
const result = await startTransport();
logger.info("MCP Server initialization sequence completed successfully.", context);
return result;
}
catch (err) {
ErrorHandler.handleError(err, {
operation: "initializeAndStartServer",
context: context,
critical: true,
rethrow: false,
});
logger.info("Exiting process due to critical initialization error.", context);
process.exit(1);
}
}