diff --git a/.gitignore b/.gitignore index 7a1537b..2fd83a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea node_modules +dist diff --git a/.serena/memories/code_style_conventions.md b/.serena/memories/code_style_conventions.md new file mode 100644 index 0000000..9810270 --- /dev/null +++ b/.serena/memories/code_style_conventions.md @@ -0,0 +1,58 @@ +# Code Style and Conventions + +## General Principles +- **Object-Oriented Design**: Use idiomatic, object-oriented style with explicit abstractions +- **Strategy Pattern**: Prefer explicitly typed interfaces over bare functions for non-trivial functionality +- **Clean Architecture**: Tools implement a common interface for consistent registration and execution + +## TypeScript Configuration +- **Strict Mode**: All strict TypeScript options enabled +- **Target**: ES2022 +- **Module System**: CommonJS +- **Declaration Files**: Generated with source maps + +## Naming Conventions +- **Classes**: PascalCase (e.g., `HelloWorldTool`, `PenpotMcpServer`) +- **Interfaces**: PascalCase (e.g., `Tool`) +- **Methods**: camelCase (e.g., `execute`, `registerTools`) +- **Constants**: camelCase for readonly properties (e.g., `definition`) +- **Files**: PascalCase for classes (e.g., `HelloWorldTool.ts`) + +## Documentation Style +- **JSDoc**: Use comprehensive JSDoc comments for classes, methods, and interfaces +- **Description Format**: Initial elliptical phrase defines *what* it is, followed by details +- **Comment Style**: Start with lowercase for code blocks unless lengthy explanation with multiple sentences + +## Code Organization +- **Separation of Concerns**: Interfaces in separate directory from implementations +- **Tool Pattern**: All tools implement the `Tool` interface with `definition` and `execute` methods +- **Error Handling**: Comprehensive error handling with descriptive messages +- **Import Style**: Use explicit .js extensions for imports (required for ES modules) + +## Examples +```typescript +/** + * A simple demonstration tool that returns a greeting message. + * + * This tool serves as a basic example of the Tool interface implementation + * and provides a minimal "Hello, World!" functionality for testing purposes. + */ +export class HelloWorldTool implements Tool { + /** + * The tool definition as required by the MCP protocol. + */ + readonly definition: MCPTool = { + // configuration + }; + + /** + * Executes the hello world functionality. + * + * @param args - Tool arguments validated against schema + * @returns A promise resolving to the execution result + */ + async execute(args: unknown): Promise { + // implementation + } +} +``` diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md new file mode 100644 index 0000000..7e87e77 --- /dev/null +++ b/.serena/memories/project_overview.md @@ -0,0 +1,31 @@ +# Penpot MCP Project Overview + +## Purpose +This project is a Model Context Protocol (MCP) server for Penpot integration. It provides a TypeScript-based server that can be used to extend Penpot's functionality through custom tools. + +## Tech Stack +- **Language**: TypeScript +- **Runtime**: Node.js +- **Framework**: MCP SDK (@modelcontextprotocol/sdk) +- **Build Tool**: TypeScript Compiler (tsc) +- **Package Manager**: npm + +## Project Structure +``` +penpot-mcp/ +├── mcp-server/ # Main MCP server implementation +│ ├── src/ +│ │ ├── index.ts # Main server entry point +│ │ ├── interfaces/ # Type definitions and contracts +│ │ │ └── Tool.ts # Tool interface definition +│ │ └── tools/ # Tool implementations +│ │ └── HelloWorldTool.ts +│ ├── package.json # Dependencies and scripts +│ └── tsconfig.json # TypeScript configuration +└── penpot-plugin/ # Penpot plugin (currently empty) +``` + +## Key Components +- **PenpotMcpServer**: Main server class that manages tool registration and MCP protocol handling +- **Tool Interface**: Abstraction for all tool implementations +- **HelloWorldTool**: Example tool implementation demonstrating the pattern diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md new file mode 100644 index 0000000..7aafed2 --- /dev/null +++ b/.serena/memories/suggested_commands.md @@ -0,0 +1,70 @@ +# Suggested Commands + +## Development Commands +```bash +# Navigate to MCP server directory +cd mcp-server + +# Install dependencies +npm install + +# Build the TypeScript project +npm run build + +# Start the server (production) +npm start + +# Start the server in development mode +npm run dev +``` + +## Testing and Development +```bash +# Run TypeScript compiler in watch mode +npx tsc --watch + +# Check TypeScript compilation without emitting files +npx tsc --noEmit +``` + +## Windows-Specific Commands +```cmd +# Directory navigation +cd mcp-server +dir # List directory contents +type package.json # Display file contents + +# Git operations +git status +git add . +git commit -m "message" +git push + +# File operations +copy src\file.ts backup\file.ts # Copy files +del dist\* # Delete files +mkdir new-directory # Create directory +rmdir /s directory # Remove directory recursively +``` + +## Project Structure Navigation +```bash +# Key directories +cd mcp-server\src # Source code +cd mcp-server\src\tools # Tool implementations +cd mcp-server\src\interfaces # Type definitions +cd mcp-server\dist # Compiled output +``` + +## Common Utilities +```cmd +# Search for text in files +findstr /s /i "HelloWorld" *.ts + +# Find files by name +dir /s /b *Tool.ts + +# Process management +tasklist | findstr node # Find Node.js processes +taskkill /f /im node.exe # Kill Node.js processes +``` diff --git a/.serena/memories/task_completion_guidelines.md b/.serena/memories/task_completion_guidelines.md new file mode 100644 index 0000000..44391b3 --- /dev/null +++ b/.serena/memories/task_completion_guidelines.md @@ -0,0 +1,52 @@ +# Task Completion Guidelines + +## After Making Code Changes + +### 1. Build and Test +```bash +cd mcp-server +npm run build +``` + +### 2. Verify TypeScript Compilation +```bash +npx tsc --noEmit +``` + +### 3. Test the Server +```bash +# Start in development mode to test changes +npm run dev +``` + +### 4. Code Quality Checks +- Ensure all code follows the established conventions +- Verify JSDoc comments are complete and accurate +- Check that error handling is appropriate +- Ensure imports use correct .js extensions +- Validate that tool interfaces are properly implemented + +### 5. Integration Testing +- Test tool registration in the main server +- Verify MCP protocol compliance +- Ensure tool definitions match implementation + +## Before Committing Changes +1. **Build Successfully**: `npm run build` completes without errors +2. **No TypeScript Errors**: `npx tsc --noEmit` passes +3. **Documentation Updated**: JSDoc comments reflect changes +4. **Tool Registry Updated**: New tools added to `registerTools()` method +5. **Interface Compliance**: All tools implement the `Tool` interface correctly + +## File Organization +- Place new tools in `src/tools/` directory +- Add interfaces to `src/interfaces/` if needed +- Update main server registration in `src/index.ts` +- Follow existing naming conventions + +## Common Patterns +- All tools must implement the `Tool` interface +- Use readonly properties for tool definitions +- Include comprehensive error handling +- Follow the established documentation style +- Import with .js extensions for ES module compatibility diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json new file mode 100644 index 0000000..9731ffb --- /dev/null +++ b/mcp-server/package-lock.json @@ -0,0 +1,344 @@ +{ + "name": "penpot-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "penpot-mcp-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.4.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.4.0.tgz", + "integrity": "sha512-79gx8xh4o9YzdbtqMukOe5WKzvEZpvBA1x8PAgJWL7J5k06+vJx8NK2kWzOazPgqnfDego7cNEO8tjai/nOPAA==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", + "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/mcp-server/package.json b/mcp-server/package.json new file mode 100644 index 0000000..57af149 --- /dev/null +++ b/mcp-server/package.json @@ -0,0 +1,26 @@ +{ + "name": "penpot-mcp-server", + "version": "1.0.0", + "description": "MCP server for Penpot integration", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "node --loader ts-node/esm src/index.ts" + }, + "keywords": ["mcp", "penpot", "server"], + "author": "", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.4.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + }, + "ts-node": { + "esm": true + } +} \ No newline at end of file diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts new file mode 100644 index 0000000..5697183 --- /dev/null +++ b/mcp-server/src/index.ts @@ -0,0 +1,135 @@ +#!/usr/bin/env node + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +import { Tool } from './interfaces/Tool.js'; +import { HelloWorldTool } from './tools/HelloWorldTool.js'; + +/** + * Main MCP server implementation for Penpot integration. + * + * This server manages tool registration and execution using a clean + * abstraction pattern that allows for easy extension with new tools. + */ +class PenpotMcpServer { + private readonly server: Server; + private readonly tools: Map; + + /** + * Creates a new Penpot MCP server instance. + */ + constructor() { + this.server = new Server( + { + name: 'penpot-mcp-server', + version: '1.0.0', + capabilities: { + tools: {}, + }, + } + ); + + this.tools = new Map(); + this.setupHandlers(); + this.registerTools(); + } + + /** + * Registers all available tools with the server. + * + * This method instantiates tool implementations and adds them to + * the internal registry for later execution. + */ + private registerTools(): void { + const toolInstances: Tool[] = [ + new HelloWorldTool(), + ]; + + for (const tool of toolInstances) { + this.tools.set(tool.definition.name, tool); + } + } + + /** + * Sets up the MCP protocol request handlers. + * + * Configures handlers for tool listing and execution requests + * according to the MCP specification. + */ + private setupHandlers(): void { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: Array.from(this.tools.values()).map(tool => tool.definition), + }; + }); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + const tool = this.tools.get(name); + if (!tool) { + throw new Error(`Tool "${name}" not found`); + } + + try { + return await tool.execute(args); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + throw new Error(`Tool execution failed: ${errorMessage}`); + } + }); + } + + /** + * Starts the MCP server using stdio transport. + * + * This method establishes the communication channel and begins + * listening for MCP protocol messages. + */ + async start(): Promise { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('Penpot MCP Server started successfully'); + } +} + +/** + * Application entry point. + * + * Creates and starts the MCP server instance, handling any startup errors + * gracefully and ensuring proper process termination. + */ +async function main(): Promise { + try { + const server = new PenpotMcpServer(); + await server.start(); + + // Keep the process alive + process.on('SIGINT', () => { + console.error('Received SIGINT, shutting down gracefully...'); + process.exit(0); + }); + + process.on('SIGTERM', () => { + console.error('Received SIGTERM, shutting down gracefully...'); + process.exit(0); + }); + + } catch (error) { + console.error('Failed to start MCP server:', error); + process.exit(1); + } +} + +// Start the server if this file is run directly +if (import.meta.url.endsWith(process.argv[1]) || process.argv[1].endsWith('index.js')) { + main().catch((error) => { + console.error('Unhandled error in main:', error); + process.exit(1); + }); +} \ No newline at end of file diff --git a/mcp-server/src/interfaces/Tool.ts b/mcp-server/src/interfaces/Tool.ts new file mode 100644 index 0000000..5e59c35 --- /dev/null +++ b/mcp-server/src/interfaces/Tool.ts @@ -0,0 +1,26 @@ +import { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Defines the contract for MCP tool implementations. + * + * This interface abstracts the common operations required for all tools + * in the MCP server, providing a standardized way to register and execute + * tool functionality. + */ +export interface Tool { + /** + * The tool's unique identifier and metadata definition. + * + * This property contains the tool's name, description, and input schema + * as required by the MCP protocol. + */ + readonly definition: MCPTool; + + /** + * Executes the tool's primary functionality with provided arguments. + * + * @param args - The arguments passed to the tool, validated against the input schema + * @returns A promise that resolves to the tool's execution result + */ + execute(args: unknown): Promise<{ content: Array<{ type: string; text: string }> }>; +} diff --git a/mcp-server/src/tools/HelloWorldTool.ts b/mcp-server/src/tools/HelloWorldTool.ts new file mode 100644 index 0000000..ef4f130 --- /dev/null +++ b/mcp-server/src/tools/HelloWorldTool.ts @@ -0,0 +1,53 @@ +import { Tool } from '../interfaces/Tool.js'; +import { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * A simple demonstration tool that returns a personalized greeting message. + * + * This tool serves as a basic example of the Tool interface implementation + * and provides personalized "Hello, !" functionality for testing purposes. + */ +export class HelloWorldTool implements Tool { + /** + * The tool definition as required by the MCP protocol. + */ + readonly definition: MCPTool = { + name: 'hello_world', + description: 'Returns a personalized greeting message with the provided name', + inputSchema: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The name to include in the greeting message' + } + }, + required: ['name'], + additionalProperties: false + } + }; + + /** + * Executes the hello world functionality with personalized greeting. + * + * @param args - The tool arguments containing the name parameter + * @returns A promise resolving to the personalized greeting message + */ + async execute(args: unknown): Promise<{ content: Array<{ type: string; text: string }> }> { + // Validate and extract the name from arguments + const typedArgs = args as { name?: string }; + + if (!typedArgs.name || typeof typedArgs.name !== 'string') { + throw new Error('Name parameter is required and must be a string'); + } + + return { + content: [ + { + type: 'text', + text: `Hello, ${typedArgs.name}!` + } + ] + }; + } +} diff --git a/mcp-server/tsconfig.json b/mcp-server/tsconfig.json new file mode 100644 index 0000000..7ee78fc --- /dev/null +++ b/mcp-server/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}