NTFY MCP Server

by cyanheads
Verified
# MCP-TS-TEMPLATE DEVELOPER CHEAT SHEET # ntfy-mcp-server - Directory Structure Generated on: 2025-03-21 10:39:58 ``` ntfy-mcp-server ├── docs └── tree.md ├── logs ├── scripts ├── clean.ts └── tree.ts ├── src ├── config │ ├── envConfig.ts │ ├── index.ts │ └── mcpConfig.ts ├── mcp-server │ ├── resources │ │ └── echoResource │ │ │ ├── getEchoMessage.ts │ │ │ ├── index.ts │ │ │ ├── README.md │ │ │ └── types.ts │ ├── tools │ │ └── ntfyTool │ │ │ ├── index.ts │ │ │ ├── ntfyMessage.ts │ │ │ └── types.ts │ ├── utils │ │ └── registrationHelper.ts │ ├── README.md │ └── server.ts ├── services │ └── ntfy │ │ ├── constants.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── publisher.ts │ │ ├── subscriber.ts │ │ ├── types.ts │ │ └── utils.ts ├── types-global │ ├── errors.ts │ ├── mcp.ts │ └── tool.ts ├── utils │ ├── errorHandler.ts │ ├── idGenerator.ts │ ├── index.ts │ ├── logger.ts │ ├── rateLimiter.ts │ ├── requestContext.ts │ ├── sanitization.ts │ └── security.ts └── index.ts ├── .clinerules ├── .clinerules-code ├── .env.example ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── tsconfig.json └── tsconfig.scripts.json ``` _Note: This tree excludes files and directories matched by .gitignore and common patterns like node_modules._ ## 🚀 Quick Start Commands ```bash npm run build # Compile TypeScript to JavaScript npm run clean # Clean build artifacts npm run rebuild # Clean and rebuild project npm run tree # Generate directory tree npm run start # Start the MCP server ``` ## 🛠️ Server Setup & Lifecycle ### Main Entry Point (src/index.ts) ```typescript // Start the server and spawn MCP subservers import { createMcpServer } from "./mcp-server/server.js"; // Main startup function const start = async () => { // Create startup context const startupContext = createRequestContext({ operation: "ServerStartup", appName: "mcp-ts-template", environment: envConfig().environment, }); // Spawn MCP servers const mcpShutdownFn = await spawnMcpServers(); // Create main server const server = await createMcpServer(); // Register signal handlers process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); }; // Start the application start(); ``` ### Server Creation (src/mcp-server/server.ts) ```typescript // Create a new MCP server with tools and resources export const createMcpServer = async () => { // Load package info const packageInfo = await loadPackageInfo(); // Create server instance const server = new McpServer({ name: packageInfo.name, version: packageInfo.version, }); // Register tools and resources in parallel await Promise.allSettled([ registerEchoTool(server), registerEchoResource(server), ]); // Connect using stdio transport await server.connect(new StdioServerTransport()); return server; }; ``` ## 🧩 Creating MCP Components ### Resource Registration (src/mcp-server/resources/echoResource/index.ts) ```typescript // Register an echo resource that responds to URIs like "echo://hello" export const registerEchoResource = async ( server: McpServer ): Promise<void> => { return registerResource( server, { name: "echo-resource" }, async (server, resourceLogger: ChildLogger) => { // Create resource template const template = new ResourceTemplate("echo://{message}", { list: async () => ({ resources: [ { uri: "echo://hello", name: "Default Echo Message", description: "A simple echo resource example", }, ], }), complete: {}, }); // Register with full configuration server.resource( "echo-resource", template, { name: "Echo Message", description: "A simple echo resource that returns a message", mimeType: "application/json", querySchema: z.object({ /*...*/ }), examples: [ /*...*/ ], }, async (uri, params) => { // Resource handler implementation return await ErrorHandler.tryCatch(/*...*/); } ); } ); }; ``` ### Tool Registration (src/mcp-server/tools/echoTool/index.ts) ```typescript // Register an echo tool that processes and formats messages export const registerEchoTool = async (server: McpServer): Promise<void> => { return registerTool( server, { name: "echo_message" }, async (server, toolLogger: ChildLogger) => { // Register the tool with simplified SDK pattern server.tool( "echo_message", { message: z .string() .min(1) .max(1000) .describe("The message to echo back (1-1000 characters)"), mode: z .enum(ECHO_MODES) .optional() .default("standard") .describe( "How to format the echoed message: standard (as-is), uppercase, or lowercase" ), repeat: z.number().int().min(1).max(10).optional().default(1), timestamp: z.boolean().optional().default(true), }, async (params) => { return await ErrorHandler.tryCatch( async () => { const response = processEchoMessage(params); return { content: [ { type: "text", text: JSON.stringify(response, null, 2) }, ], }; }, { /* error handling options */ } ); } ); } ); }; ``` ### Registration Helper (src/mcp-server/utils/registrationHelper.ts) ```typescript // Use these helpers for consistent registration pattern import { registerTool } from "../../utils/registrationHelper.js"; import { registerResource } from "../../utils/registrationHelper.js"; // Example usage: registerTool(server, { name: "your_tool_name" }, async (server, logger) => { // Tool registration logic }); ``` ## 🔒 Error Handling ### Using ErrorHandler (src/utils/errorHandler.ts) ```typescript import { ErrorHandler } from "./utils/errorHandler.js"; // Try/catch pattern const result = await ErrorHandler.tryCatch( async () => { // Operation that might fail return await someAsyncOperation(); }, { operation: "operation name", context: { additionalContext: "value" }, input: { param1: "value1" }, errorCode: BaseErrorCode.INTERNAL_ERROR, errorMapper: (error) => new McpError( BaseErrorCode.VALIDATION_ERROR, `Custom error message: ${ error instanceof Error ? error.message : "Unknown error" }` ), } ); // If result instanceof Error, handle the error ``` ### Custom Errors (src/types-global/errors.ts) ```typescript import { BaseErrorCode, McpError } from "./types-global/errors.js"; throw new McpError(BaseErrorCode.INVALID_REQUEST, "Error message"); ``` ## ⚙️ Configuration ### Environment Config (src/config/envConfig.ts) ```typescript import { envConfig } from "./config/envConfig.js"; // Access environment variables const environment = envConfig().environment; const logLevel = envConfig().logLevel; const rateLimitSettings = { windowMs: envConfig().rateLimit.windowMs || 60000, maxRequests: envConfig().rateLimit.maxRequests || 100, }; ``` ### MCP Server Config (src/config/mcpConfig.ts) ```typescript import { enabledMcpServers } from "./config/mcpConfig.js"; // Get configured MCP servers const mcpServers = enabledMcpServers(); ``` ## 📝 Logging ### Using Logger (src/utils/logger.ts) ```typescript import { logger } from "./utils/logger.js"; // Basic logging logger.info("Information message", { context: "value" }); logger.error("Error message", { error: errorObj }); // Child loggers for components const serverLogger = logger.createChildLogger({ service: "MCPServer", serverId: idGenerator.generateRandomString(8), environment: envConfig().environment, }); // Component-specific logger const toolLogger = logger.createChildLogger({ module: "EchoTool", operation: "registration", }); toolLogger.debug("Debug message"); ``` ## 🛡️ Security ### Input Sanitization (src/utils/security.ts) ```typescript import { sanitizeInput } from "./utils/security.js"; // Sanitize user inputs const safeName = sanitizeInput.string(name); const safePath = sanitizeInput.path(pkgPath); const safeHtml = sanitizeInput.html(userHtml); ``` ### Request Context (src/utils/requestContext.ts) ```typescript import { createRequestContext } from "./utils/security.js"; // Create context for operation tracking const context = createRequestContext({ operation: "OperationName", userId: "user-id", }); ``` ## 🔄 Process Management ### Spawning MCP Servers (src/index.ts) ```typescript // Load configured MCP servers const mcpServers = enabledMcpServers(); // Spawn child processes const childProc = spawn(serverConfig.command, serverConfig.args, { env: { ...process.env, ...serverConfig.env }, stdio: ["pipe", "pipe", "pipe"], }); // Handle process events childProc.stdout?.on("data", (data: Buffer) => { const output = data.toString().trim(); if (output) { processLogger.debug(`stdout:`, { output: output.substring(0, 500) }); } }); childProc.on("exit", (code: number | null, signal: string | null) => { // Handle process exit }); ``` ### Graceful Shutdown (src/index.ts) ```typescript // Register signal handlers process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); // Implement shutdown function const shutdown = async (signal: string) => { // Close MCP servers if (mcpServerProcesses.size > 0) { await shutdownMcpServers(); } // Close main server if (server) { await server.close(); } process.exit(0); }; ``` ## 🧩 MCP SDK Integration ### SDK Dependencies (package.json) - `@modelcontextprotocol/sdk`: Main MCP SDK for server/client implementation - Version used: ^1.7.0 - Key schemas: `ListResourcesRequestSchema`, `ReadResourceRequestSchema`, etc. ### Schema Types (from @modelcontextprotocol/sdk/types.js) ```typescript import { CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; ``` ### Input Validation with Zod ```typescript // Define input schema with zod { message: z.string().min(1).max(1000).describe( 'The message to echo back (1-1000 characters)' ), mode: z.enum(['standard', 'uppercase', 'lowercase']).optional().default('standard') } ```