├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── src ├── tools │ ├── codeReview.ts │ ├── screenshot.ts │ └── architect.ts └── index.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | .out/ 5 | .next/ 6 | .env 7 | .env.local 8 | .env.development 9 | .env.production 10 | .env.test 11 | .env.*.local 12 | logs/ 13 | *.log 14 | .vscode/ 15 | .idea/ 16 | *.swp 17 | *.swo 18 | coverage/ 19 | *.lcov 20 | src/env/ 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-server", 3 | "version": "2.0.1", 4 | "description": "MCP Server with three Cursor Tools: Screenshot, Architect, and Code Review", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 8 | "start": "node build/index.js" 9 | }, 10 | "bin": { 11 | "cursor-tools": "./build/index.js" 12 | }, 13 | "dependencies": { 14 | "@modelcontextprotocol/sdk": "^1.4.1", 15 | "openai": "^4.82.0", 16 | "puppeteer": "^24.1.1", 17 | "zod": "^3.24.1" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^22.13.0", 21 | "typescript": "^5.7.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 kleneway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/tools/codeReview.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { execSync } from "child_process"; 3 | 4 | /** 5 | * CodeReview tool 6 | * - Takes in a file path and runs "git diff main -- " 7 | * - Returns the diff along with instructions to review and fix issues 8 | */ 9 | 10 | export const codeReviewToolName = "code-review"; 11 | export const codeReviewToolDescription = 12 | "Run a git diff against main on a specified file and provide instructions to review/fix issues."; 13 | 14 | export const CodeReviewToolSchema = z.object({ 15 | folderPath: z.string().min(1, "A folder path is required."), 16 | }); 17 | 18 | export async function runCodeReviewTool( 19 | args: z.infer, 20 | ) { 21 | const { folderPath } = args; 22 | 23 | let diffOutput = ""; 24 | try { 25 | diffOutput = execSync(`git -C "${folderPath}" diff`, { 26 | encoding: "utf-8", 27 | }); 28 | } catch (error) { 29 | // If there's an error (e.g., no git repo, or the file doesn't exist), include it in the response. 30 | diffOutput = `Error running git diff: ${error}`; 31 | } 32 | 33 | const instructions = 34 | "Review this diff for any obvious issues. Fix them if found, then finalize the changes."; 35 | 36 | const message = `Git Diff Output:\n${diffOutput}\n\nInstructions:\n${instructions}`; 37 | 38 | return { 39 | content: [ 40 | { 41 | type: "text", 42 | text: message, 43 | }, 44 | ], 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/tools/screenshot.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import { z } from "zod"; 3 | import path from "path"; 4 | import fs from "fs"; 5 | /** 6 | * Screenshot tool 7 | * - Takes in either "url" (a full URL) or "relativePath" to open on localhost:3000 8 | * - Returns a base64-encoded PNG screenshot 9 | */ 10 | 11 | export const screenshotToolName = "screenshot"; 12 | export const screenshotToolDescription = 13 | "Take a screenshot of a URL or a local path (relative URL appended to http://localhost:3000)."; 14 | 15 | export const ScreenshotToolSchema = z.object({ 16 | url: z.string().optional(), 17 | relativePath: z.string().optional(), 18 | fullPathToScreenshot: z.string(), 19 | }); 20 | 21 | export async function runScreenshotTool( 22 | args: z.infer, 23 | ) { 24 | // Determine final URL 25 | let finalUrl = args.url; 26 | if (!finalUrl) { 27 | if (!args.relativePath) { 28 | throw new Error("Must provide either 'url' or 'relativePath'"); 29 | } 30 | finalUrl = `http://localhost:3000/${args.relativePath.replace(/^\//, "")}`; 31 | } 32 | const fullPathToScreenshot = path.resolve(args.fullPathToScreenshot); 33 | 34 | // Launch Puppeteer 35 | const browser = await puppeteer.launch(); 36 | const page = await browser.newPage(); 37 | await page.goto(finalUrl); 38 | const screenshotBuffer = (await page.screenshot({ 39 | fullPage: true, 40 | })) as Buffer; 41 | await browser.close(); 42 | await fs.promises.writeFile(fullPathToScreenshot, screenshotBuffer); 43 | // Return the base64 representation 44 | return { 45 | content: [ 46 | { 47 | type: "text", 48 | text: `Screenshot saved to ${fullPathToScreenshot}. Before continuing, you MUST ask the user to drag and drop the screenshot into the chat window. 49 | The path to the screenshot is ${fullPathToScreenshot}.`, 50 | }, 51 | ], 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/tools/architect.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import OpenAI from "openai"; 3 | import { OPENAI_API_KEY } from "../env/keys.js"; 4 | 5 | /** 6 | * Architect tool 7 | * - Calls an OpenAI model (o3-mini-01-31-24) to generate a series of steps 8 | * - Input: 'task' (description of the task), 'code' (one or more code files concatenated) 9 | */ 10 | 11 | export const architectToolName = "architect"; 12 | export const architectToolDescription = 13 | "Analyzes a task description plus some code, then outlines steps for an AI coding agent."; 14 | 15 | export const ArchitectToolSchema = z.object({ 16 | task: z.string().min(1, "Task description is required."), 17 | code: z 18 | .string() 19 | .min(1, "Code string is required (one or more files concatenated)."), 20 | }); 21 | 22 | export async function runArchitectTool( 23 | args: z.infer, 24 | ) { 25 | // Instantiate the new OpenAI client 26 | const openai = new OpenAI({ 27 | apiKey: OPENAI_API_KEY, 28 | }); 29 | 30 | const { task, code } = args; 31 | const systemPrompt = `You are an expert software architect. Given a task and some code, outline the steps that an AI coding agent should take to complete or improve the code.`; 32 | 33 | // We'll prompt the model with both the task and code 34 | const userPrompt = `Task: ${task}\n\nCode:\n${code}\n\nPlease provide a step-by-step plan.`; 35 | 36 | try { 37 | const response = await openai.chat.completions.create({ 38 | model: "o3-mini-2025-01-31", 39 | messages: [ 40 | { role: "system", content: systemPrompt }, 41 | { role: "user", content: userPrompt }, 42 | ], 43 | }); 44 | 45 | // Extract the content from the assistant's message (if available) 46 | const assistantMessage = 47 | response.choices?.[0]?.message?.content ?? "No response from model."; 48 | 49 | return { 50 | content: [ 51 | { 52 | type: "text", 53 | text: assistantMessage, 54 | }, 55 | ], 56 | }; 57 | } catch (error: any) { 58 | // If the request fails, return the error as text 59 | return { 60 | content: [ 61 | { 62 | type: "text", 63 | text: `OpenAI Error: ${error.message || error}`, 64 | }, 65 | ], 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 AI Development Assistant MCP Server 2 | 3 | Welcome to your AI-powered development toolkit, designed as a Model Context Protocol (MCP) server for Cursor! This project provides intelligent coding assistance through custom AI tools. Note that this is mostly a tutorial demo, and not a production-ready tool. 4 | 5 | ## ✨ Features 6 | 7 | ### 🎨 Code Architect 8 | 9 | Call advanced reasoning LLMs to generate plans and instructions for coding agents. 10 | 11 | ### 📸 Screenshot Buddy 12 | 13 | Take UI design screenshots and use them with the composer agent. 14 | 15 | ### 🔍 Code Review 16 | 17 | Use git diffs to trigger code reviews. 18 | 19 | ## 🚀 Getting Started 20 | 21 | ### 1. Environment Setup 22 | 23 | First, you'll need to set up your environment variables. Create a file at `src/env/keys.ts`: 24 | 25 | ```typescript 26 | export const OPENAI_API_KEY = "your_key_here"; 27 | // Add any other keys you need 28 | ``` 29 | 30 | > ⚠️ **Security Note**: Storing API keys directly in source code is not recommended for production environments. This is only for local development and learning purposes. You can set the env var inline in the Cursor MCP interface as well. 31 | 32 | ### 2. Installation 33 | 34 | ```bash 35 | npm install 36 | # or 37 | yarn install 38 | ``` 39 | 40 | ### 3. Build the Server 41 | 42 | ```bash 43 | npm run build 44 | ``` 45 | 46 | ### 4. Adding to Cursor 47 | 48 | This project is designed to be used as an MCP server in Cursor. Here's how to set it up: 49 | 50 | 1. Open Cursor 51 | 2. Go to `Cursor Settings > Features > MCP` 52 | 3. Click `+ Add New MCP Server` 53 | 4. Fill out the form: 54 | - **Name**: AI Development Assistant 55 | - **Type**: stdio 56 | - **Command**: `node /path/to/your/project/dist/index.js` 57 | 58 | > 📘 **Pro Tip**: You might need to use the full path to your project's built index.js file. 59 | 60 | After adding the server, you should see your tools listed under "Available Tools". If not, try clicking the refresh button in the top right corner of the MCP server section. 61 | 62 | For more details about MCP setup, check out the [Cursor MCP Documentation](https://docs.cursor.com/advanced/model-context-protocol). 63 | 64 | ## 🛠️ Using the Tools 65 | 66 | Once configured, you can use these tools directly in Cursor's Composer. The AI will automatically suggest using relevant tools, or you can explicitly request them by name or description. 67 | 68 | For example, try typing in Composer: 69 | 70 | - "Review this code for best practices" 71 | - "Help me architect a new feature" 72 | - "Analyze this UI screenshot" 73 | 74 | The agent will ask for your approval before making any tool calls. 75 | 76 | > 📘 **Pro Tip**: You can update your .cursorrules file with instructions on how to use the tools for certain scenarios, and the agent will use the tools automatically. 77 | 78 | ## 📁 Project Structure 79 | 80 | ``` 81 | src/ 82 | ├── tools/ 83 | │ ├── architect.ts # Code structure generator 84 | │ ├── screenshot.ts # Screenshot analysis tool 85 | │ └── codeReview.ts # Code review tool 86 | ├── env/ 87 | │ └── keys.ts # Environment configuration (add your API keys here!) 88 | └── index.ts # Main entry point 89 | ``` 90 | 91 | ## 🤝 Contributing 92 | 93 | Contributions welcome! Please feel free to submit a Pull Request. 94 | 95 | ## 📝 License 96 | 97 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 98 | 99 | ## 🐛 Issues & Support 100 | 101 | Found a bug or need help? Open an issue with: 102 | 103 | 1. What you were trying to do 104 | 2. What happened instead 105 | 3. Steps to reproduce 106 | 4. Your environment details 107 | 108 | --- 109 | 110 | I'll be honest though, this is a tutorial demo, and not a production-ready tool so I likely won't be fixing issues. But feel free to fork it and make it your own! 111 | 112 | Made with ❤️ by developers, for developers 113 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { 4 | CallToolRequestSchema, 5 | ListToolsRequestSchema, 6 | } from "@modelcontextprotocol/sdk/types.js"; 7 | 8 | import { 9 | screenshotToolName, 10 | screenshotToolDescription, 11 | ScreenshotToolSchema, 12 | runScreenshotTool, 13 | } from "./tools/screenshot.js"; 14 | 15 | import { 16 | architectToolName, 17 | architectToolDescription, 18 | ArchitectToolSchema, 19 | runArchitectTool, 20 | } from "./tools/architect.js"; 21 | 22 | import { 23 | codeReviewToolName, 24 | codeReviewToolDescription, 25 | CodeReviewToolSchema, 26 | runCodeReviewTool, 27 | } from "./tools/codeReview.js"; 28 | 29 | /** 30 | * A minimal MCP server providing three Cursor Tools: 31 | * 1) Screenshot 32 | * 2) Architect 33 | * 3) CodeReview 34 | */ 35 | 36 | // 1. Create an MCP server instance 37 | const server = new Server( 38 | { 39 | name: "cursor-tools", 40 | version: "2.0.1", 41 | }, 42 | { 43 | capabilities: { 44 | tools: {}, 45 | }, 46 | }, 47 | ); 48 | 49 | // 2. Define the list of tools 50 | server.setRequestHandler(ListToolsRequestSchema, async () => { 51 | return { 52 | tools: [ 53 | { 54 | name: screenshotToolName, 55 | description: screenshotToolDescription, 56 | inputSchema: { 57 | type: "object", 58 | properties: { 59 | url: { 60 | type: "string", 61 | description: "Full URL to screenshot", 62 | }, 63 | relativePath: { 64 | type: "string", 65 | description: "Relative path appended to http://localhost:3000", 66 | }, 67 | fullPathToScreenshot: { 68 | type: "string", 69 | description: 70 | "Path to where the screenshot file should be saved. This should be a cwd-style full path to the file (not relative to the current working directory) including the file name and extension.", 71 | }, 72 | }, 73 | required: [], 74 | }, 75 | }, 76 | { 77 | name: architectToolName, 78 | description: architectToolDescription, 79 | inputSchema: { 80 | type: "object", 81 | properties: { 82 | task: { 83 | type: "string", 84 | description: "Description of the task", 85 | }, 86 | code: { 87 | type: "string", 88 | description: "Concatenated code from one or more files", 89 | }, 90 | }, 91 | required: ["task", "code"], 92 | }, 93 | }, 94 | { 95 | name: codeReviewToolName, 96 | description: codeReviewToolDescription, 97 | inputSchema: { 98 | type: "object", 99 | properties: { 100 | folderPath: { 101 | type: "string", 102 | description: 103 | "Path to the full root directory of the repository to diff against main", 104 | }, 105 | }, 106 | required: ["folderPath"], 107 | }, 108 | }, 109 | ], 110 | }; 111 | }); 112 | 113 | // 3. Implement the tool call logic 114 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 115 | const { name, arguments: args } = request.params; 116 | 117 | switch (name) { 118 | case screenshotToolName: { 119 | const validated = ScreenshotToolSchema.parse(args); 120 | return await runScreenshotTool(validated); 121 | } 122 | case architectToolName: { 123 | const validated = ArchitectToolSchema.parse(args); 124 | return await runArchitectTool(validated); 125 | } 126 | case codeReviewToolName: { 127 | const validated = CodeReviewToolSchema.parse(args); 128 | return await runCodeReviewTool(validated); 129 | } 130 | default: 131 | throw new Error(`Unknown tool: ${name}`); 132 | } 133 | }); 134 | 135 | // 4. Start the MCP server with a stdio transport 136 | async function main() { 137 | const transport = new StdioServerTransport(); 138 | await server.connect(transport); 139 | console.error("Cursor Tools MCP Server running on stdio"); 140 | } 141 | 142 | main().catch((error) => { 143 | console.error("Fatal error:", error); 144 | process.exit(1); 145 | }); 146 | --------------------------------------------------------------------------------