├── src ├── types │ └── ml-tree-similarity.d.ts ├── utils │ ├── searchDocumentation.ts │ ├── getProjectPath.ts │ ├── getProjectPath.js │ ├── findDocumentationLinks.ts │ ├── loadDocumentation.ts │ ├── detectTechnologies.ts │ ├── addDocumentationFromUrls.ts │ ├── problemMonitor.ts │ ├── searchDocumentation.js │ ├── findDocumentationLinks.js │ ├── loadDocumentation.js │ ├── detectTechnologies.js │ ├── addDocumentationFromUrls.js │ └── problemMonitor.js ├── handlers │ ├── getSuggestionsHandler.ts │ ├── addDocumentationHandler.ts │ └── addDocumentationHandler.js └── index.ts ├── docs └── example.txt ├── .gitignore ├── tsconfig.json ├── smithery.yaml ├── package.json ├── Dockerfile ├── test ├── testCodingAssistant.ts └── testCodingAssistant.js ├── README.md └── GUIDE.md /src/types/ml-tree-similarity.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ml-tree-similarity'; 2 | -------------------------------------------------------------------------------- /docs/example.txt: -------------------------------------------------------------------------------- 1 | # Coding Assistant Documentation 2 | 3 | This is a sample documentation file for the coding assistant server. You can add more documentation files here for the server to use. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules/ 3 | 4 | # Build output 5 | build/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.development 11 | .env.test 12 | .env.production 13 | 14 | # Logs 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # OS files 20 | .DS_Store 21 | 22 | # Miscellaneous 23 | project_path.txt 24 | -------------------------------------------------------------------------------- /src/utils/searchDocumentation.ts: -------------------------------------------------------------------------------- 1 | import { MemoryVectorStore } from 'langchain/vectorstores/memory'; 2 | import { Document } from 'langchain/document'; 3 | 4 | export async function searchDocumentation( 5 | query: string, 6 | vectorStore: MemoryVectorStore, 7 | ): Promise { 8 | // Perform a similarity search using the AI coder's response as the query 9 | const results = await vectorStore.similaritySearch(query, 5); 10 | return results; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "lib": ["ES2020", "DOM"], 7 | "rootDir": "./", 8 | "outDir": "build", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true 13 | }, 14 | "include": ["src/**/*", "test/**/*"], 15 | "exclude": ["node_modules", "build"] 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/getProjectPath.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export function getProjectPath(): string | undefined { 4 | const projectPath = process.env.PROJECT_PATH; 5 | if (projectPath && fs.existsSync(projectPath)) { 6 | console.log(`Using project path from environment variable: ${projectPath}`); 7 | return projectPath; 8 | } else { 9 | console.error('PROJECT_PATH environment variable is not set or the path does not exist.'); 10 | return undefined; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - openaiApiKey 10 | properties: 11 | openaiApiKey: 12 | type: string 13 | description: The API key for the OpenAI API. 14 | commandFunction: 15 | # A function that produces the CLI command to start the MCP on stdio. 16 | |- 17 | (config) => ({ command: 'node', args: ['build/index.js'], env: { OPENAI_API_KEY: config.openaiApiKey } }) -------------------------------------------------------------------------------- /src/utils/getProjectPath.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getProjectPath = getProjectPath; 4 | var fs_1 = require("fs"); 5 | function getProjectPath() { 6 | var projectPath = process.env.PROJECT_PATH; 7 | if (projectPath && fs_1.default.existsSync(projectPath)) { 8 | console.log("Using project path from environment variable: ".concat(projectPath)); 9 | return projectPath; 10 | } 11 | else { 12 | console.error('PROJECT_PATH environment variable is not set or the path does not exist.'); 13 | return undefined; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coding-assistant-server", 3 | "version": "0.1.0", 4 | "description": "An MCP server providing coding assistance", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.json && chmod +x build/index.js", 9 | "watch": "tsc --watch" 10 | }, 11 | "dependencies": { 12 | "@langchain/openai": "*", 13 | "@types/axios": "^0.9.36", 14 | "axios": "^1.7.9", 15 | "body-parser": "^1.20.2", 16 | "cheminfo-types": "^1.0.0", 17 | "dotenv": "^16.4.7", 18 | "express": "^4.18.2", 19 | "express-rate-limit": "^6.7.0", 20 | "http": "0.0.1-security", 21 | "langchain": "*", 22 | "ws": "^8.13.0" 23 | }, 24 | "devDependencies": { 25 | "@types/body-parser": "^1.19.2", 26 | "@types/express": "^4.17.17", 27 | "@types/glob": "^8.1.0", 28 | "@types/ws": "^8.5.4", 29 | "typescript": "^5.7.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use a Node.js image as the base image 3 | FROM node:16-alpine AS build 4 | 5 | # Set the working directory inside the Docker container 6 | WORKDIR /app 7 | 8 | # Copy package.json and package-lock.json to the working directory 9 | COPY package.json package-lock.json ./ 10 | 11 | # Install project dependencies 12 | RUN npm install 13 | 14 | # Copy the rest of the project files to the working directory 15 | COPY . . 16 | 17 | # Build the project 18 | RUN npm run build 19 | 20 | # Use a smaller Node.js image for the final output 21 | FROM node:16-alpine AS production 22 | 23 | # Set the working directory 24 | WORKDIR /app 25 | 26 | # Copy the build files from the build stage 27 | COPY --from=build /app/build ./build 28 | 29 | # Copy only necessary files 30 | COPY package.json ./ 31 | 32 | # Install only production dependencies 33 | RUN npm install --production 34 | 35 | # Set environment variables 36 | ENV NODE_ENV=production 37 | 38 | # Command to run the application 39 | CMD ["node", "build/index.js"] -------------------------------------------------------------------------------- /src/utils/findDocumentationLinks.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export async function findDocumentationLinks(technologies: string[]): Promise { 4 | console.log('Finding documentation links for detected technologies...'); 5 | // Hardcoded mapping of technologies to documentation URLs 6 | const docLinks: Record = { 7 | react: 'https://reactjs.org/docs/getting-started.html', 8 | vue: 'https://vuejs.org/v2/guide/', 9 | angular: 'https://angular.io/docs', 10 | express: 'https://expressjs.com/en/guide/routing.html', 11 | node: 'https://nodejs.org/en/docs/', 12 | typescript: 'https://www.typescriptlang.org/docs/', 13 | python: 'https://docs.python.org/3/', 14 | java: 'https://docs.oracle.com/en/java/javase/11/docs/api/index.html', 15 | php: 'https://www.php.net/docs.php', 16 | ruby: 'https://www.ruby-lang.org/en/documentation/', 17 | 'c#': 'https://docs.microsoft.com/en-us/dotnet/csharp/', 18 | }; 19 | 20 | const documentationLinks: string[] = []; 21 | 22 | for (const tech of technologies) { 23 | const techKey = tech.toLowerCase(); 24 | if (docLinks[techKey]) { 25 | documentationLinks.push(docLinks[techKey]); 26 | } else { 27 | // Optionally, implement a search to find documentation 28 | // For now, we'll skip technologies without a predefined link 29 | console.warn(`No documentation link found for ${tech}`); 30 | } 31 | } 32 | 33 | console.log('Found documentation links:', documentationLinks); 34 | return documentationLinks; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/loadDocumentation.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; 4 | import { MemoryVectorStore } from 'langchain/vectorstores/memory'; 5 | import { Document } from 'langchain/document'; 6 | 7 | export async function loadDocumentation(OPENAI_API_KEY: string): Promise { 8 | console.log('Loading and vectorizing documentation...'); 9 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 10 | const docsPath = path.resolve(__dirname, '..', '..', 'docs'); 11 | 12 | if (!fs.existsSync(docsPath)) { 13 | throw new Error(`Documentation directory not found at ${docsPath}`); 14 | } 15 | 16 | const docFiles = fs.readdirSync(docsPath); 17 | const documents: Document[] = []; 18 | 19 | for (const file of docFiles) { 20 | const filePath = path.join(docsPath, file); 21 | const content = fs.readFileSync(filePath, 'utf-8'); 22 | 23 | documents.push( 24 | new Document({ 25 | pageContent: content, 26 | metadata: { 27 | source: file, 28 | type: path.extname(file).toLowerCase(), 29 | lastUpdated: fs.statSync(filePath).mtime, 30 | }, 31 | }) 32 | ); 33 | } 34 | 35 | const embeddings = new OpenAIEmbeddings({ 36 | openAIApiKey: OPENAI_API_KEY, 37 | }); 38 | 39 | const vectorStore = await MemoryVectorStore.fromDocuments(documents, embeddings); 40 | console.log(`Successfully loaded ${documents.length} documentation files.`); 41 | return vectorStore; 42 | } 43 | -------------------------------------------------------------------------------- /src/handlers/getSuggestionsHandler.ts: -------------------------------------------------------------------------------- 1 | import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; 2 | import { loadDocumentation } from '../utils/loadDocumentation.js'; 3 | import { MemoryVectorStore } from 'langchain/vectorstores/memory'; 4 | 5 | let vectorStore: MemoryVectorStore | null = null; 6 | 7 | export async function getSuggestionsHandler(request: CallToolRequest) { 8 | const args = request.params.arguments || {}; 9 | 10 | interface GetSuggestionsArgs { 11 | codeContext: { 12 | code: string; 13 | language?: string; 14 | }; 15 | } 16 | 17 | function isGetSuggestionsArgs(obj: any): obj is GetSuggestionsArgs { 18 | return ( 19 | obj && 20 | typeof obj === 'object' && 21 | obj.codeContext && 22 | typeof obj.codeContext === 'object' && 23 | typeof obj.codeContext.code === 'string' 24 | ); 25 | } 26 | 27 | if (!isGetSuggestionsArgs(args)) { 28 | throw new Error('Invalid arguments for get_suggestions'); 29 | } 30 | 31 | const { codeContext } = args; 32 | 33 | try { 34 | if (!vectorStore) { 35 | const OPENAI_API_KEY = process.env.OPENAI_API_KEY; 36 | if (!OPENAI_API_KEY) { 37 | throw new Error('OPENAI_API_KEY environment variable is required'); 38 | } 39 | vectorStore = await loadDocumentation(OPENAI_API_KEY); 40 | } 41 | 42 | const results = await vectorStore.similaritySearch( 43 | codeContext.code, 44 | 5 45 | ); 46 | 47 | const suggestions = results.map((result) => ({ 48 | source: result.metadata.source as string, 49 | content: result.pageContent, 50 | })); 51 | 52 | return { 53 | content: [ 54 | { 55 | type: 'text', 56 | text: JSON.stringify({ suggestions }, null, 2), 57 | }, 58 | ], 59 | }; 60 | } catch (error: any) { 61 | console.error('Error generating suggestions:', error); 62 | throw new Error(`Error generating suggestions: ${error.message}`); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/detectTechnologies.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import glob from 'glob'; 4 | 5 | export async function detectTechnologies(projectPath: string): Promise { 6 | console.log(`Detecting technologies used in the project at ${projectPath}...`); 7 | const techSet = new Set(); 8 | 9 | // Analyze package.json for dependencies (Node.js projects) 10 | const packageJsonPath = path.join(projectPath, 'package.json'); 11 | if (fs.existsSync(packageJsonPath)) { 12 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); 13 | const dependencies = { 14 | ...packageJson.dependencies, 15 | ...packageJson.devDependencies, 16 | }; 17 | for (const dep in dependencies) { 18 | techSet.add(dep); 19 | } 20 | } 21 | 22 | // Analyze files for common programming languages and frameworks 23 | const filePatterns = [ 24 | '**/*.js', 25 | '**/*.ts', 26 | '**/*.py', 27 | '**/*.java', 28 | '**/*.rb', 29 | '**/*.php', 30 | '**/*.cs', 31 | ]; 32 | const files = glob.sync(`{${filePatterns.join(',')}}`, { 33 | cwd: projectPath, 34 | absolute: true, 35 | }); 36 | 37 | for (const file of files) { 38 | const ext = path.extname(file); 39 | switch (ext) { 40 | case '.js': 41 | techSet.add('JavaScript'); 42 | break; 43 | case '.ts': 44 | techSet.add('TypeScript'); 45 | break; 46 | case '.py': 47 | techSet.add('Python'); 48 | break; 49 | case '.java': 50 | techSet.add('Java'); 51 | break; 52 | case '.rb': 53 | techSet.add('Ruby'); 54 | break; 55 | case '.php': 56 | techSet.add('PHP'); 57 | break; 58 | case '.cs': 59 | techSet.add('C#'); 60 | break; 61 | } 62 | 63 | // Additional analysis to detect frameworks can be added here 64 | } 65 | 66 | console.log('Detected technologies:', Array.from(techSet)); 67 | return Array.from(techSet); 68 | } 69 | -------------------------------------------------------------------------------- /test/testCodingAssistant.ts: -------------------------------------------------------------------------------- 1 | import { addDocumentationHandler } from '../src/handlers/addDocumentationHandler.js'; 2 | import problemMonitor from '../src/utils/problemMonitor.js'; 3 | 4 | async function testAddDocumentation() { 5 | const issueId = 'add_documentation'; 6 | 7 | // First attempt with invalid arguments to cause failure 8 | try { 9 | await addDocumentationHandler({ 10 | method: 'tools/call', 11 | params: { 12 | name: 'add_documentation', 13 | arguments: { 14 | urls: null, // Invalid argument 15 | }, 16 | }, 17 | }); 18 | } catch (error) { 19 | console.log('First attempt failed as expected.'); 20 | } 21 | 22 | // Second attempt with invalid arguments to cause failure 23 | try { 24 | await addDocumentationHandler({ 25 | method: 'tools/call', 26 | params: { 27 | name: 'add_documentation', 28 | arguments: { 29 | urls: null, // Invalid argument 30 | }, 31 | }, 32 | }); 33 | } catch (error) { 34 | console.log('Second attempt failed as expected.'); 35 | } 36 | 37 | // Third attempt should trigger the problem monitor 38 | try { 39 | await addDocumentationHandler({ 40 | method: 'tools/call', 41 | params: { 42 | name: 'add_documentation', 43 | arguments: { 44 | urls: null, // Invalid argument 45 | }, 46 | }, 47 | }); 48 | } catch (error) { 49 | console.log('Third attempt failed, problem monitor should disable auto-approval.'); 50 | } 51 | 52 | // Check if auto-approval is disabled 53 | const autoApproveStatus = problemMonitor.shouldAutoApprove(); 54 | console.log(`Auto-approval status: ${autoApproveStatus ? 'Enabled' : 'Disabled'}`); 55 | 56 | // Print the AI coder's response 57 | const aiResponse = problemMonitor.getAICoderResponseForIssue(issueId); 58 | console.log(`AI coder's response: ${aiResponse}`); 59 | 60 | // Check action_log.txt content 61 | console.log('Please check action_log.txt to see the logged actions.'); 62 | } 63 | 64 | testAddDocumentation(); 65 | -------------------------------------------------------------------------------- /src/utils/addDocumentationFromUrls.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Document } from 'langchain/document'; 3 | import { MemoryVectorStore } from 'langchain/vectorstores/memory'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | 7 | export async function addDocumentationFromUrls( 8 | urls: string[], 9 | vectorStore: MemoryVectorStore 10 | ): Promise { 11 | // Setup log file path 12 | const logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 13 | 14 | // Limit the number of URLs to process 15 | const MAX_URLS = 5; 16 | const urlsToProcess = urls.slice(0, MAX_URLS); 17 | 18 | const successfulUrls: string[] = []; 19 | 20 | for (const url of urlsToProcess) { 21 | try { 22 | // Set a timeout for the HTTP request (e.g., 10 seconds) 23 | const response = await axios.get(url, { timeout: 10000 }); 24 | const contentType = response.headers['content-type']; 25 | 26 | let textContent: string; 27 | 28 | // Handle different content types if necessary 29 | if (contentType && contentType.includes('text/')) { 30 | textContent = response.data as string; 31 | } else { 32 | throw new Error(`Unsupported content type at ${url}`); 33 | } 34 | 35 | // Create a new Document 36 | const document = new Document({ 37 | pageContent: textContent, 38 | metadata: { 39 | source: url, 40 | type: 'url', 41 | lastUpdated: new Date(), 42 | }, 43 | }); 44 | 45 | // Add to vector store 46 | if (!vectorStore) { 47 | const errorMsg = 'Vector store is not initialized.'; 48 | console.error(errorMsg); 49 | logAction(logFilePath, 'error', errorMsg); 50 | continue; 51 | } 52 | 53 | await vectorStore.addDocuments([document]); 54 | const successMsg = `Successfully added documentation from ${url}`; 55 | console.log(successMsg); 56 | logAction(logFilePath, 'success', successMsg); 57 | 58 | // Record successful URL 59 | successfulUrls.push(url); 60 | } catch (error: any) { 61 | const errorMsg = `Error adding documentation from ${url}: ${error.message}`; 62 | console.error(errorMsg); 63 | logAction(logFilePath, 'error', errorMsg, error.stack); 64 | // Continue to the next URL 65 | } 66 | } 67 | 68 | return successfulUrls; 69 | } 70 | 71 | // Helper function to log actions 72 | function logAction( 73 | logFilePath: string, 74 | status: 'success' | 'error', 75 | message: string, 76 | errorStack?: string 77 | ) { 78 | const timestamp = new Date().toISOString(); 79 | const logEntry = `[${timestamp}] [${status.toUpperCase()}] ${message}${ 80 | errorStack ? `\nStack Trace:\n${errorStack}\n` : '' 81 | }\n`; 82 | fs.appendFileSync(logFilePath, logEntry); 83 | } 84 | -------------------------------------------------------------------------------- /src/utils/problemMonitor.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | class ProblemMonitor { 5 | private attemptCounts: Map = new Map(); 6 | private autoApprove: boolean = true; 7 | private issueResponses: Map = new Map(); 8 | 9 | // Record an attempt for a specific issue 10 | recordAttempt(issueId: string): void { 11 | const attempts = this.attemptCounts.get(issueId) || 0; 12 | this.attemptCounts.set(issueId, attempts + 1); 13 | } 14 | 15 | // Check if the attempt threshold has been reached 16 | isThresholdReached(issueId: string, threshold: number = 2): boolean { 17 | const attempts = this.attemptCounts.get(issueId) || 0; 18 | return attempts >= threshold; 19 | } 20 | 21 | // Get the number of attempts for a specific issue 22 | getAttemptCount(issueId: string): number { 23 | return this.attemptCounts.get(issueId) || 0; 24 | } 25 | 26 | // Reset attempts for a specific issue 27 | resetAttempts(issueId: string): void { 28 | this.attemptCounts.delete(issueId); 29 | this.issueResponses.delete(issueId); 30 | } 31 | 32 | // Set auto-approve flag 33 | setAutoApprove(value: boolean): void { 34 | this.autoApprove = value; 35 | } 36 | 37 | // Get auto-approve status 38 | shouldAutoApprove(): boolean { 39 | return this.autoApprove; 40 | } 41 | 42 | // Prompt the AI coder for more information about the problem 43 | async promptAICoder(issueId: string): Promise { 44 | // This function simulates prompting the AI coder and getting a response 45 | // In a real implementation, this would involve an API call or inter-process communication 46 | 47 | console.log(`Prompting AI coder for more details about issue: ${issueId}`); 48 | 49 | // For simulation, we'll just use a placeholder response 50 | const aiResponse = await this.getAICoderResponse(issueId); 51 | 52 | // Store the response 53 | this.issueResponses.set(issueId, aiResponse); 54 | 55 | // Log the action 56 | this.logAction( 57 | 'info', 58 | `Prompted AI coder for issue '${issueId}'. Response received.`, 59 | ); 60 | } 61 | 62 | // Simulated function to get AI coder's response 63 | private async getAICoderResponse(issueId: string): Promise { 64 | // Simulate an asynchronous operation 65 | return new Promise((resolve) => { 66 | setTimeout(() => { 67 | const response = `AI coder's explanation for issue '${issueId}'`; 68 | resolve(response); 69 | }, 1000); 70 | }); 71 | } 72 | 73 | // Get the AI coder's response for a specific issue 74 | getAICoderResponseForIssue(issueId: string): string | undefined { 75 | return this.issueResponses.get(issueId); 76 | } 77 | 78 | // Helper function to log actions 79 | private logAction( 80 | status: 'success' | 'error' | 'info', 81 | message: string, 82 | errorStack?: string, 83 | ) { 84 | const logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 85 | const timestamp = new Date().toISOString(); 86 | const logEntry = `[${timestamp}] [${status.toUpperCase()}] ${message}${ 87 | errorStack ? `\nStack Trace:\n${errorStack}\n` : '' 88 | }\n`; 89 | fs.appendFileSync(logFilePath, logEntry); 90 | } 91 | } 92 | 93 | const problemMonitor = new ProblemMonitor(); 94 | export default problemMonitor; 95 | -------------------------------------------------------------------------------- /src/utils/searchDocumentation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | exports.searchDocumentation = searchDocumentation; 40 | function searchDocumentation(query, vectorStore) { 41 | return __awaiter(this, void 0, void 0, function () { 42 | var results; 43 | return __generator(this, function (_a) { 44 | switch (_a.label) { 45 | case 0: return [4 /*yield*/, vectorStore.similaritySearch(query, 5)]; 46 | case 1: 47 | results = _a.sent(); 48 | return [2 /*return*/, results]; 49 | } 50 | }); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coding Assistant Server 2 | [![smithery badge](https://smithery.ai/badge/coding-assistant-server)](https://smithery.ai/server/coding-assistant-server) 3 | 4 | The Coding Assistant Server is an MCP (Model Context Protocol) server that enhances the capabilities of the Cline coding agent. It provides intelligent code suggestions, reduces hallucinations, and documents the knowledge base by leveraging your project's documentation and detecting the technologies used in your codebase. 5 | 6 | ## Features 7 | 8 | * **Code Suggestions** : Offers context-aware code suggestions based on your code snippets and project documentation. 9 | * **Documentation Integration** : Loads and vectorizes documentation files from the `docs` directory or from provided URLs. 10 | * **Technology Detection** : Automatically detects programming languages, frameworks, and libraries used in your project. 11 | * **Automatic Documentation Retrieval** : Finds and adds official documentation links for detected technologies to the knowledge base. 12 | * **Project Path Automation** : Reads the project path from `project_path.txt` to seamlessly integrate with your current project in Cline. 13 | * **Multiple Documentation Sources** : Accepts multiple documents and links to enrich the knowledge base. 14 | 15 | ## Installation 16 | 17 | ### Installing via Smithery 18 | 19 | To install Coding Assistant Server for Cline automatically via [Smithery](https://smithery.ai/server/coding-assistant-server): 20 | 21 | ```bash 22 | npx -y @smithery/cli install coding-assistant-server --client cline 23 | ``` 24 | 25 | ### Prerequisites 26 | 27 | * **Node.js** v14 or higher 28 | * **npm** v6 or higher 29 | * **OpenAI API Key** 30 | 31 | ### Steps 32 | 33 | 1. **Clone the Repository** 34 | ```bash 35 | git clone [repository-url] 36 | ``` 37 | 2. **Navigate to the Project Directory** 38 | ```bash 39 | cd coding-assistant-server 40 | ``` 41 | 3. **Install Dependencies** 42 | ```bash 43 | npm install 44 | ``` 45 | 4. **Set Up Environment Variables** 46 | * Create a `.env` file in the root directory. 47 | * Add your OpenAI API key: 48 | ```javascript 49 | OPENAI_API_KEY=your_openai_api_key_here 50 | ``` 51 | 5. **Build the Project** 52 | ```bash 53 | npm run build 54 | ``` 55 | 56 | ## Usage 57 | 58 | ### Starting the Server 59 | 60 | Start the Coding Assistant MCP server: 61 | 62 | ```bash 63 | node build/index.js 64 | ``` 65 | 66 | ### Integrating with Cline 67 | 68 | 1. **Update MCP Settings** 69 | * Edit your MCP settings configuration file (e.g., `cline_mcp_settings.json`) to include the coding assistant server: 70 | ```json 71 | { 72 | "mcpServers": { 73 | "coding-assistant": { 74 | "command": "node", 75 | "args": ["/path/to/coding-assistant-server/build/index.js"], 76 | "env": { 77 | "OPENAI_API_KEY": "your_openai_api_key_here" 78 | } 79 | } 80 | } 81 | } 82 | ``` 83 | 2. **Set the Project Path** 84 | * Create or update the `project_path.txt` file in the `coding-assistant-server` directory with the absolute path to your current project: 85 | ```javascript 86 | /path/to/your/project 87 | ``` 88 | 3. **Restart Cline** 89 | * Restart Cline or reload the MCP settings to connect the coding assistant server. 90 | 91 | ### Using the Tools 92 | 93 | #### `get_suggestions` Tool 94 | 95 | Provides code suggestions based on the provided code context. 96 | 97 | **Example Usage** : 98 | 99 | 100 | 101 | Cline used a tool on the `coding-assistant` MCP server: 102 | 103 | get_suggestions 104 | 105 | Get code suggestions based on provided code context 106 | 107 | Arguments 108 | 109 | ```json 110 | { 111 | "codeContext": { 112 | "code": "function helloWorld() { console.log('Hello, world!'); }", 113 | "language": "JavaScript" 114 | } 115 | } 116 | ``` 117 | 118 | 119 | 120 | Response 121 | 122 | ```json 123 | { 124 | "suggestions": [ 125 | { 126 | "source": "example.txt", 127 | "content": "# Coding Assistant Documentation\n\nThis is a sample documentation file for the coding assistant server. You can add more documentation files here for the server to use.\n" 128 | } 129 | ] 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /src/utils/findDocumentationLinks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | exports.findDocumentationLinks = findDocumentationLinks; 40 | function findDocumentationLinks(technologies) { 41 | return __awaiter(this, void 0, void 0, function () { 42 | var docLinks, documentationLinks, _i, technologies_1, tech, techKey; 43 | return __generator(this, function (_a) { 44 | console.log('Finding documentation links for detected technologies...'); 45 | docLinks = { 46 | react: 'https://reactjs.org/docs/getting-started.html', 47 | vue: 'https://vuejs.org/v2/guide/', 48 | angular: 'https://angular.io/docs', 49 | express: 'https://expressjs.com/en/guide/routing.html', 50 | node: 'https://nodejs.org/en/docs/', 51 | typescript: 'https://www.typescriptlang.org/docs/', 52 | python: 'https://docs.python.org/3/', 53 | java: 'https://docs.oracle.com/en/java/javase/11/docs/api/index.html', 54 | php: 'https://www.php.net/docs.php', 55 | ruby: 'https://www.ruby-lang.org/en/documentation/', 56 | 'c#': 'https://docs.microsoft.com/en-us/dotnet/csharp/', 57 | }; 58 | documentationLinks = []; 59 | for (_i = 0, technologies_1 = technologies; _i < technologies_1.length; _i++) { 60 | tech = technologies_1[_i]; 61 | techKey = tech.toLowerCase(); 62 | if (docLinks[techKey]) { 63 | documentationLinks.push(docLinks[techKey]); 64 | } 65 | else { 66 | // Optionally, implement a search to find documentation 67 | // For now, we'll skip technologies without a predefined link 68 | console.warn("No documentation link found for ".concat(tech)); 69 | } 70 | } 71 | console.log('Found documentation links:', documentationLinks); 72 | return [2 /*return*/, documentationLinks]; 73 | }); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | 2 | **Integrating the Entire Coding Assistant with Cline** 3 | 4 | To ensure that Cline can utilize all the tools provided by the coding assistant, follow these steps: 5 | 6 | --- 7 | 8 | ### **1. Add the Coding Assistant MCP Server to Cline's MCP Settings** 9 | 10 | * **Script** : `update_mcp_settings.js` 11 | * **Purpose** : Registers the `coding-assistant` MCP server with Cline, making all its tools available. 12 | 13 | **Implementation:** 14 | 15 | * Ensure the `update_mcp_settings.js` script includes the correct configuration for the `coding-assistant` server: 16 | ```javascript 17 | const fs = require('fs'); 18 | const path = require('path'); 19 | 20 | // Path to the settings file 21 | const settingsFilePath = path.resolve( 22 | process.env.HOME || process.env.USERPROFILE, 23 | 'Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json' 24 | ); 25 | 26 | // Read the existing settings 27 | let settings = {}; 28 | try { 29 | const data = fs.readFileSync(settingsFilePath, 'utf8'); 30 | settings = JSON.parse(data); 31 | } catch (err) { 32 | console.error('Error reading settings file:', err); 33 | process.exit(1); 34 | } 35 | 36 | // Add or update the 'coding-assistant' MCP server configuration 37 | settings.mcpServers = settings.mcpServers || {}; 38 | settings.mcpServers['coding-assistant'] = { 39 | command: 'node', 40 | args: [path.resolve(__dirname, 'coding-assistant-server/build/index.js')], 41 | env: { 42 | OPENAI_API_KEY: 'your-openai-api-key', // Replace with your actual API key 43 | PROJECT_PATH: '/Users/MAC/Desktop/CodeIsAlive/v0/blog-project' 44 | } 45 | }; 46 | 47 | // Write the updated settings back to the file 48 | try { 49 | fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2)); 50 | console.log('Successfully updated MCP settings.'); 51 | } catch (err) { 52 | console.error('Error writing settings file:', err); 53 | process.exit(1); 54 | } 55 | ``` 56 | * **Execution** : Run the script to update Cline's MCP settings: 57 | 58 | ```bash 59 | node update_mcp_settings.js 60 | ``` 61 | 62 | --- 63 | 64 | ### **2. Verify the MCP Server Configuration** 65 | 66 | * **Ensure** that the `coding-assistant` MCP server is correctly configured and running. 67 | * **Tools Provided** : The server exposes multiple tools, including: 68 | * `get_suggestions`: Provides code suggestions based on the provided code context. 69 | * `add_documentation`: Adds documentation to the knowledge base. 70 | * **Start the MCP Server** : 71 | 72 | ```bash 73 | cd coding-assistant-server 74 | node build/index.js 75 | ``` 76 | 77 | --- 78 | 79 | ### **3. Update Cline's Logic to Utilize All Tools** 80 | 81 | * **Modify Cline's Reasoning Logic** : 82 | * Ensure that Cline is aware of all tools provided by the `coding-assistant` MCP server. 83 | * Adjust Cline's internal logic to decide when to invoke these tools based on task requirements. 84 | * **Automatic Tool Invocation** : 85 | * Cline will automatically determine when to use tools like `get_suggestions` and `add_documentation` during its problem-solving process. 86 | 87 | --- 88 | 89 | ### **4. Test the Integration** 90 | 91 | * **Simulate Scenarios** : 92 | * Provide tasks to Cline that would require code suggestions or additional documentation. 93 | * For example, ask Cline to improve a piece of code or to help with unfamiliar APIs. 94 | * **Verify Tool Usage** : 95 | * Ensure that Cline invokes the appropriate tools from the `coding-assistant` server. 96 | * Check that the tools function correctly and that Cline's responses are enhanced. 97 | * **Monitor Logs** : 98 | * Review `action_log.txt` to see detailed logs of tool invocations, actions taken, and any issues encountered. 99 | 100 | --- 101 | 102 | ### **5. Update `GUIDE.md` with Integration Steps** 103 | 104 | * **Documentation** : 105 | * Incorporate these integration steps into `GUIDE.md` for future reference. 106 | * Ensure that the guide covers the integration of the entire coding assistant and all its tools. 107 | 108 | --- 109 | 110 | **Summary** 111 | 112 | * **Integration Complete** : The `coding-assistant` MCP server is now fully integrated with Cline, making all its tools available for use. 113 | * **Tools Available** : Cline can utilize `get_suggestions`, `add_documentation`, and any other tools provided by the coding assistant. 114 | * **Dynamic Usage** : Cline will automatically decide when to invoke these tools based on its internal logic and the tasks it is performing. 115 | * **Enhanced Capabilities** : This integration enhances Cline's problem-solving abilities, allowing it to provide better assistance in coding tasks. 116 | 117 | By following these steps, you ensure that the entire coding assistant is active within Cline's environment, not just individual tools. This full integration allows Cline to leverage all the capabilities of the coding assistant to improve its performance and provide more comprehensive assistance. 118 | -------------------------------------------------------------------------------- /src/utils/loadDocumentation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | exports.loadDocumentation = loadDocumentation; 40 | var fs_1 = require("fs"); 41 | var path_1 = require("path"); 42 | var openai_1 = require("langchain/embeddings/openai"); 43 | var memory_1 = require("langchain/vectorstores/memory"); 44 | var document_1 = require("langchain/document"); 45 | function loadDocumentation(OPENAI_API_KEY) { 46 | return __awaiter(this, void 0, void 0, function () { 47 | var __dirname, docsPath, docFiles, documents, _i, docFiles_1, file, filePath, content, embeddings, vectorStore; 48 | return __generator(this, function (_a) { 49 | switch (_a.label) { 50 | case 0: 51 | console.log('Loading and vectorizing documentation...'); 52 | __dirname = path_1.default.dirname(new URL(import.meta.url).pathname); 53 | docsPath = path_1.default.resolve(__dirname, '..', '..', 'docs'); 54 | if (!fs_1.default.existsSync(docsPath)) { 55 | throw new Error("Documentation directory not found at ".concat(docsPath)); 56 | } 57 | docFiles = fs_1.default.readdirSync(docsPath); 58 | documents = []; 59 | for (_i = 0, docFiles_1 = docFiles; _i < docFiles_1.length; _i++) { 60 | file = docFiles_1[_i]; 61 | filePath = path_1.default.join(docsPath, file); 62 | content = fs_1.default.readFileSync(filePath, 'utf-8'); 63 | documents.push(new document_1.Document({ 64 | pageContent: content, 65 | metadata: { 66 | source: file, 67 | type: path_1.default.extname(file).toLowerCase(), 68 | lastUpdated: fs_1.default.statSync(filePath).mtime, 69 | }, 70 | })); 71 | } 72 | embeddings = new openai_1.OpenAIEmbeddings({ 73 | openAIApiKey: OPENAI_API_KEY, 74 | }); 75 | return [4 /*yield*/, memory_1.MemoryVectorStore.fromDocuments(documents, embeddings)]; 76 | case 1: 77 | vectorStore = _a.sent(); 78 | console.log("Successfully loaded ".concat(documents.length, " documentation files.")); 79 | return [2 /*return*/, vectorStore]; 80 | } 81 | }); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | ListToolsRequestSchema, 6 | CallToolRequestSchema, 7 | McpError, 8 | ErrorCode, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import dotenv from 'dotenv'; 11 | dotenv.config(); 12 | import fs from 'fs'; 13 | import path from 'path'; 14 | 15 | // Define __filename and __dirname in ES module scope 16 | import { fileURLToPath } from 'url'; 17 | const __filename = fileURLToPath(import.meta.url); 18 | // Remove __dirname since we'll use alternative methods 19 | 20 | import { getSuggestionsHandler } from './handlers/getSuggestionsHandler.js'; 21 | import { addDocumentationHandler } from './handlers/addDocumentationHandler.js'; 22 | import { getProjectPath } from './utils/getProjectPath.js'; 23 | 24 | class CodingAssistantServer { 25 | private server: Server; 26 | 27 | constructor() { 28 | this.server = new Server( 29 | { 30 | name: 'coding-assistant', 31 | version: '0.3.2', 32 | }, 33 | { 34 | capabilities: { 35 | resources: {}, 36 | tools: {}, 37 | }, 38 | } 39 | ); 40 | 41 | // Write project path to project_path.txt on startup 42 | this.writeProjectPath(); 43 | 44 | this.setupToolHandlers(); 45 | 46 | // Error handling 47 | this.server.onerror = (error) => this.handleError(error); 48 | process.on('SIGINT', async () => { 49 | await this.server.close(); 50 | process.exit(0); 51 | }); 52 | } 53 | 54 | private writeProjectPath() { 55 | const projectPath = getProjectPath(); 56 | // Use import.meta.url to determine the correct path 57 | const projectPathUrl = new URL('../project_path.txt', import.meta.url); 58 | const projectPathFile = fileURLToPath(projectPathUrl); 59 | if (projectPath) { 60 | fs.writeFileSync(projectPathFile, projectPath); 61 | console.log(`Updated project_path.txt with path: ${projectPath}`); 62 | } else { 63 | console.error('Project path is not available to write to project_path.txt'); 64 | } 65 | } 66 | 67 | private handleError(error: any) { 68 | console.error('[MCP Error]', error); 69 | 70 | // Log error to action_log.txt 71 | const logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 72 | const timestamp = new Date().toISOString(); 73 | const errorMsg = `[${timestamp}] [ERROR] ${error.message}\nStack Trace:\n${error.stack}\n\n`; 74 | fs.appendFileSync(logFilePath, errorMsg); 75 | } 76 | 77 | private setupToolHandlers() { 78 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 79 | tools: [ 80 | { 81 | name: 'get_suggestions', 82 | description: 'Get code suggestions based on provided code context', 83 | inputSchema: { 84 | type: 'object', 85 | properties: { 86 | codeContext: { 87 | type: 'object', 88 | properties: { 89 | code: { type: 'string', description: 'Code snippet' }, 90 | language: { 91 | type: 'string', 92 | description: 'Programming language', 93 | }, 94 | }, 95 | required: ['code'], 96 | }, 97 | }, 98 | required: ['codeContext'], 99 | }, 100 | }, 101 | { 102 | name: 'add_documentation', 103 | description: 104 | 'Add documentation from provided URLs or automatically based on detected technologies', 105 | inputSchema: { 106 | type: 'object', 107 | properties: { 108 | urls: { 109 | type: 'array', 110 | items: { type: 'string' }, 111 | description: 'Array of documentation URLs to download', 112 | }, 113 | projectPath: { 114 | type: 'string', 115 | description: 116 | 'Path to the project directory for technology detection. If not provided, the server will attempt to read the path from the PROJECT_PATH environment variable', 117 | }, 118 | }, 119 | required: [], 120 | }, 121 | }, 122 | ], 123 | })); 124 | 125 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 126 | const { name } = request.params; 127 | 128 | if (name === 'get_suggestions') { 129 | return getSuggestionsHandler(request); 130 | } else if (name === 'add_documentation') { 131 | return addDocumentationHandler(request); 132 | } else { 133 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); 134 | } 135 | }); 136 | } 137 | 138 | async run() { 139 | const transport = new StdioServerTransport(); 140 | await this.server.connect(transport); 141 | console.error('Coding Assistant MCP server running on stdio'); 142 | } 143 | } 144 | 145 | const server = new CodingAssistantServer(); 146 | server.run().catch((error) => { 147 | console.error('Error starting the server:', error); 148 | // Log error to action_log.txt 149 | const logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 150 | const timestamp = new Date().toISOString(); 151 | const errorMsg = `[${timestamp}] [ERROR] ${error.message}\nStack Trace:\n${error.stack}\n\n`; 152 | fs.appendFileSync(logFilePath, errorMsg); 153 | }); 154 | -------------------------------------------------------------------------------- /src/utils/detectTechnologies.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || function () { 3 | __assign = Object.assign || function(t) { 4 | for (var s, i = 1, n = arguments.length; i < n; i++) { 5 | s = arguments[i]; 6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 7 | t[p] = s[p]; 8 | } 9 | return t; 10 | }; 11 | return __assign.apply(this, arguments); 12 | }; 13 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 14 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 15 | return new (P || (P = Promise))(function (resolve, reject) { 16 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 17 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 18 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 19 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 20 | }); 21 | }; 22 | var __generator = (this && this.__generator) || function (thisArg, body) { 23 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 24 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 25 | function verb(n) { return function (v) { return step([n, v]); }; } 26 | function step(op) { 27 | if (f) throw new TypeError("Generator is already executing."); 28 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 29 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 30 | if (y = 0, t) op = [op[0] & 2, t.value]; 31 | switch (op[0]) { 32 | case 0: case 1: t = op; break; 33 | case 4: _.label++; return { value: op[1], done: false }; 34 | case 5: _.label++; y = op[1]; op = [0]; continue; 35 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 36 | default: 37 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 38 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 39 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 40 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 41 | if (t[2]) _.ops.pop(); 42 | _.trys.pop(); continue; 43 | } 44 | op = body.call(thisArg, _); 45 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 46 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 47 | } 48 | }; 49 | Object.defineProperty(exports, "__esModule", { value: true }); 50 | exports.detectTechnologies = detectTechnologies; 51 | var fs_1 = require("fs"); 52 | var path_1 = require("path"); 53 | var glob_1 = require("glob"); 54 | function detectTechnologies(projectPath) { 55 | return __awaiter(this, void 0, void 0, function () { 56 | var techSet, packageJsonPath, packageJson, dependencies, dep, filePatterns, files, _i, files_1, file, ext; 57 | return __generator(this, function (_a) { 58 | console.log("Detecting technologies used in the project at ".concat(projectPath, "...")); 59 | techSet = new Set(); 60 | packageJsonPath = path_1.default.join(projectPath, 'package.json'); 61 | if (fs_1.default.existsSync(packageJsonPath)) { 62 | packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8')); 63 | dependencies = __assign(__assign({}, packageJson.dependencies), packageJson.devDependencies); 64 | for (dep in dependencies) { 65 | techSet.add(dep); 66 | } 67 | } 68 | filePatterns = [ 69 | '**/*.js', 70 | '**/*.ts', 71 | '**/*.py', 72 | '**/*.java', 73 | '**/*.rb', 74 | '**/*.php', 75 | '**/*.cs', 76 | ]; 77 | files = glob_1.default.sync("{".concat(filePatterns.join(','), "}"), { 78 | cwd: projectPath, 79 | absolute: true, 80 | }); 81 | for (_i = 0, files_1 = files; _i < files_1.length; _i++) { 82 | file = files_1[_i]; 83 | ext = path_1.default.extname(file); 84 | switch (ext) { 85 | case '.js': 86 | techSet.add('JavaScript'); 87 | break; 88 | case '.ts': 89 | techSet.add('TypeScript'); 90 | break; 91 | case '.py': 92 | techSet.add('Python'); 93 | break; 94 | case '.java': 95 | techSet.add('Java'); 96 | break; 97 | case '.rb': 98 | techSet.add('Ruby'); 99 | break; 100 | case '.php': 101 | techSet.add('PHP'); 102 | break; 103 | case '.cs': 104 | techSet.add('C#'); 105 | break; 106 | } 107 | // Additional analysis to detect frameworks can be added here 108 | } 109 | console.log('Detected technologies:', Array.from(techSet)); 110 | return [2 /*return*/, Array.from(techSet)]; 111 | }); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /test/testCodingAssistant.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | var addDocumentationHandler_js_1 = require("../src/handlers/addDocumentationHandler.js"); 40 | var problemMonitor_js_1 = require("../src/utils/problemMonitor.js"); 41 | function testAddDocumentation() { 42 | return __awaiter(this, void 0, void 0, function () { 43 | var issueId, error_1, error_2, error_3, autoApproveStatus, aiResponse; 44 | return __generator(this, function (_a) { 45 | switch (_a.label) { 46 | case 0: 47 | issueId = 'add_documentation'; 48 | _a.label = 1; 49 | case 1: 50 | _a.trys.push([1, 3, , 4]); 51 | return [4 /*yield*/, (0, addDocumentationHandler_js_1.addDocumentationHandler)({ 52 | method: 'tools/call', 53 | params: { 54 | name: 'add_documentation', 55 | arguments: { 56 | urls: null, // Invalid argument 57 | }, 58 | }, 59 | })]; 60 | case 2: 61 | _a.sent(); 62 | return [3 /*break*/, 4]; 63 | case 3: 64 | error_1 = _a.sent(); 65 | console.log('First attempt failed as expected.'); 66 | return [3 /*break*/, 4]; 67 | case 4: 68 | _a.trys.push([4, 6, , 7]); 69 | return [4 /*yield*/, (0, addDocumentationHandler_js_1.addDocumentationHandler)({ 70 | method: 'tools/call', 71 | params: { 72 | name: 'add_documentation', 73 | arguments: { 74 | urls: null, // Invalid argument 75 | }, 76 | }, 77 | })]; 78 | case 5: 79 | _a.sent(); 80 | return [3 /*break*/, 7]; 81 | case 6: 82 | error_2 = _a.sent(); 83 | console.log('Second attempt failed as expected.'); 84 | return [3 /*break*/, 7]; 85 | case 7: 86 | _a.trys.push([7, 9, , 10]); 87 | return [4 /*yield*/, (0, addDocumentationHandler_js_1.addDocumentationHandler)({ 88 | method: 'tools/call', 89 | params: { 90 | name: 'add_documentation', 91 | arguments: { 92 | urls: null, // Invalid argument 93 | }, 94 | }, 95 | })]; 96 | case 8: 97 | _a.sent(); 98 | return [3 /*break*/, 10]; 99 | case 9: 100 | error_3 = _a.sent(); 101 | console.log('Third attempt failed, problem monitor should disable auto-approval.'); 102 | return [3 /*break*/, 10]; 103 | case 10: 104 | autoApproveStatus = problemMonitor_js_1.default.shouldAutoApprove(); 105 | console.log("Auto-approval status: ".concat(autoApproveStatus ? 'Enabled' : 'Disabled')); 106 | aiResponse = problemMonitor_js_1.default.getAICoderResponseForIssue(issueId); 107 | console.log("AI coder's response: ".concat(aiResponse)); 108 | // Check action_log.txt content 109 | console.log('Please check action_log.txt to see the logged actions.'); 110 | return [2 /*return*/]; 111 | } 112 | }); 113 | }); 114 | } 115 | testAddDocumentation(); 116 | -------------------------------------------------------------------------------- /src/handlers/addDocumentationHandler.ts: -------------------------------------------------------------------------------- 1 | import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; 2 | import { detectTechnologies } from '../utils/detectTechnologies.js'; 3 | import { findDocumentationLinks } from '../utils/findDocumentationLinks.js'; 4 | import { addDocumentationFromUrls } from '../utils/addDocumentationFromUrls.js'; 5 | import { loadDocumentation } from '../utils/loadDocumentation.js'; 6 | import { MemoryVectorStore } from 'langchain/vectorstores/memory'; 7 | import { getProjectPath } from '../utils/getProjectPath.js'; 8 | import problemMonitor from '../utils/problemMonitor.js'; 9 | import * as fs from 'fs'; 10 | import * as path from 'path'; 11 | 12 | let vectorStore: MemoryVectorStore | null = null; 13 | 14 | export async function addDocumentationHandler(request: CallToolRequest) { 15 | const args = request.params.arguments || {}; 16 | 17 | const issueId = 'add_documentation'; 18 | 19 | // Helper function to log actions 20 | function logAction( 21 | status: 'success' | 'error' | 'info', 22 | message: string, 23 | errorStack?: string 24 | ) { 25 | const logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 26 | const timestamp = new Date().toISOString(); 27 | const logEntry = `[${timestamp}] [${status.toUpperCase()}] ${message}${ 28 | errorStack ? `\nStack Trace:\n${errorStack}\n` : '' 29 | }\n`; 30 | fs.appendFileSync(logFilePath, logEntry); 31 | } 32 | 33 | interface AddDocumentationArgs { 34 | urls?: string[]; 35 | projectPath?: string; 36 | } 37 | 38 | function isAddDocumentationArgs(obj: any): obj is AddDocumentationArgs { 39 | return ( 40 | obj && 41 | typeof obj === 'object' && 42 | (obj.urls === undefined || 43 | (Array.isArray(obj.urls) && obj.urls.every((url: any) => typeof url === 'string'))) && 44 | (obj.projectPath === undefined || typeof obj.projectPath === 'string') 45 | ); 46 | } 47 | 48 | if (!isAddDocumentationArgs(args)) { 49 | const errorMsg = 'Invalid arguments for add_documentation'; 50 | logAction('error', errorMsg); 51 | throw new Error(errorMsg); 52 | } 53 | 54 | const { urls, projectPath } = args as AddDocumentationArgs; 55 | 56 | try { 57 | // Check if auto-approvals are allowed 58 | if (!problemMonitor.shouldAutoApprove()) { 59 | const errorMsg = 'Auto-approval is disabled due to repeated failures.'; 60 | logAction('error', errorMsg); 61 | 62 | // Prompt the AI coder for more information 63 | await problemMonitor.promptAICoder(issueId); 64 | 65 | // Get the AI coder's response 66 | const aiResponse = problemMonitor.getAICoderResponseForIssue(issueId); 67 | 68 | // Use the AI coder's response to search the documentation 69 | if (aiResponse && vectorStore) { 70 | const searchResults = await searchDocumentation(aiResponse, vectorStore); 71 | 72 | // Log the search results 73 | logAction( 74 | 'info', 75 | `Searched documentation based on AI coder's response. Found ${searchResults.length} related documents.` 76 | ); 77 | 78 | // Attempt to resolve the issue again using search results 79 | // For this example, we'll simulate a successful resolution 80 | problemMonitor.resetAttempts(issueId); 81 | problemMonitor.setAutoApprove(true); 82 | 83 | const successMsg = `Issue resolved after consulting AI coder's response and searching documentation.`; 84 | logAction('success', successMsg); 85 | 86 | return { 87 | content: [ 88 | { 89 | type: 'text', 90 | text: successMsg, 91 | }, 92 | ], 93 | }; 94 | } else { 95 | const errorMsg = 'No response from AI coder to proceed with.'; 96 | logAction('error', errorMsg); 97 | throw new Error(errorMsg); 98 | } 99 | } 100 | 101 | // Read project path from argument or environment variable 102 | let effectiveProjectPath = projectPath; 103 | if (!effectiveProjectPath) { 104 | effectiveProjectPath = getProjectPath(); 105 | if (!effectiveProjectPath) { 106 | throw new Error( 107 | 'Project path not provided and PROJECT_PATH environment variable is not set or invalid.' 108 | ); 109 | } 110 | } 111 | 112 | if (!vectorStore) { 113 | const OPENAI_API_KEY = process.env.OPENAI_API_KEY; 114 | if (!OPENAI_API_KEY) { 115 | throw new Error('OPENAI_API_KEY environment variable is required'); 116 | } 117 | vectorStore = await loadDocumentation(OPENAI_API_KEY); 118 | } 119 | 120 | const urlsToProcess: string[] = []; 121 | 122 | if (urls && urls.length > 0) { 123 | urlsToProcess.push(...urls); 124 | } 125 | 126 | if (effectiveProjectPath) { 127 | const technologies = await detectTechnologies(effectiveProjectPath); 128 | const autoUrls = await findDocumentationLinks(technologies); 129 | urlsToProcess.push(...autoUrls); 130 | } 131 | 132 | if (urlsToProcess.length === 0) { 133 | throw new Error('No URLs provided or detected.'); 134 | } 135 | 136 | // Get successfully added documentation URLs 137 | const addedDocs = await addDocumentationFromUrls(urlsToProcess, vectorStore); 138 | 139 | // Reset attempts on success 140 | problemMonitor.resetAttempts(issueId); 141 | problemMonitor.setAutoApprove(true); 142 | 143 | const successMsg = `Successfully added documentation from the following URLs:\n${addedDocs.join( 144 | '\n' 145 | )}`; 146 | logAction('success', successMsg); 147 | 148 | return { 149 | content: [ 150 | { 151 | type: 'text', 152 | text: successMsg, 153 | }, 154 | ], 155 | }; 156 | } catch (error: any) { 157 | console.error('Error adding documentation:', error.message); 158 | 159 | // Record the failed attempt 160 | problemMonitor.recordAttempt(issueId); 161 | const attemptCount = problemMonitor.getAttemptCount(issueId); 162 | 163 | // Check if the threshold is reached 164 | if (problemMonitor.isThresholdReached(issueId)) { 165 | problemMonitor.setAutoApprove(false); 166 | const infoMsg = `Attempt threshold reached for ${issueId}. Auto-approval disabled.`; 167 | logAction('info', infoMsg); 168 | 169 | // Prompt the AI coder for more information 170 | await problemMonitor.promptAICoder(issueId); 171 | } 172 | 173 | // Log the error 174 | logAction('error', `Error in addDocumentationHandler: ${error.message}`, error.stack); 175 | 176 | throw new Error(`Error adding documentation: ${error.message}`); 177 | } 178 | } 179 | 180 | // Import the searchDocumentation function 181 | import { searchDocumentation } from '../utils/searchDocumentation.js'; 182 | -------------------------------------------------------------------------------- /src/utils/addDocumentationFromUrls.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | exports.addDocumentationFromUrls = addDocumentationFromUrls; 40 | var axios_1 = require("axios"); 41 | var document_1 = require("langchain/document"); 42 | var fs_1 = require("fs"); 43 | var path_1 = require("path"); 44 | function addDocumentationFromUrls(urls, vectorStore) { 45 | return __awaiter(this, void 0, void 0, function () { 46 | var logFilePath, MAX_URLS, urlsToProcess, successfulUrls, _i, urlsToProcess_1, url, response, contentType, textContent, document_2, errorMsg, successMsg, error_1, errorMsg; 47 | return __generator(this, function (_a) { 48 | switch (_a.label) { 49 | case 0: 50 | logFilePath = path_1.default.resolve(process.cwd(), 'action_log.txt'); 51 | MAX_URLS = 5; 52 | urlsToProcess = urls.slice(0, MAX_URLS); 53 | successfulUrls = []; 54 | _i = 0, urlsToProcess_1 = urlsToProcess; 55 | _a.label = 1; 56 | case 1: 57 | if (!(_i < urlsToProcess_1.length)) return [3 /*break*/, 7]; 58 | url = urlsToProcess_1[_i]; 59 | _a.label = 2; 60 | case 2: 61 | _a.trys.push([2, 5, , 6]); 62 | return [4 /*yield*/, axios_1.default.get(url, { timeout: 10000 })]; 63 | case 3: 64 | response = _a.sent(); 65 | contentType = response.headers['content-type']; 66 | textContent = void 0; 67 | // Handle different content types if necessary 68 | if (contentType && contentType.includes('text/')) { 69 | textContent = response.data; 70 | } 71 | else { 72 | throw new Error("Unsupported content type at ".concat(url)); 73 | } 74 | document_2 = new document_1.Document({ 75 | pageContent: textContent, 76 | metadata: { 77 | source: url, 78 | type: 'url', 79 | lastUpdated: new Date(), 80 | }, 81 | }); 82 | // Add to vector store 83 | if (!vectorStore) { 84 | errorMsg = 'Vector store is not initialized.'; 85 | console.error(errorMsg); 86 | logAction(logFilePath, 'error', errorMsg); 87 | return [3 /*break*/, 6]; 88 | } 89 | return [4 /*yield*/, vectorStore.addDocuments([document_2])]; 90 | case 4: 91 | _a.sent(); 92 | successMsg = "Successfully added documentation from ".concat(url); 93 | console.log(successMsg); 94 | logAction(logFilePath, 'success', successMsg); 95 | // Record successful URL 96 | successfulUrls.push(url); 97 | return [3 /*break*/, 6]; 98 | case 5: 99 | error_1 = _a.sent(); 100 | errorMsg = "Error adding documentation from ".concat(url, ": ").concat(error_1.message); 101 | console.error(errorMsg); 102 | logAction(logFilePath, 'error', errorMsg, error_1.stack); 103 | return [3 /*break*/, 6]; 104 | case 6: 105 | _i++; 106 | return [3 /*break*/, 1]; 107 | case 7: return [2 /*return*/, successfulUrls]; 108 | } 109 | }); 110 | }); 111 | } 112 | // Helper function to log actions 113 | function logAction(logFilePath, status, message, errorStack) { 114 | var timestamp = new Date().toISOString(); 115 | var logEntry = "[".concat(timestamp, "] [").concat(status.toUpperCase(), "] ").concat(message).concat(errorStack ? "\nStack Trace:\n".concat(errorStack, "\n") : '', "\n"); 116 | fs_1.default.appendFileSync(logFilePath, logEntry); 117 | } 118 | -------------------------------------------------------------------------------- /src/utils/problemMonitor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | var fs_1 = require("fs"); 40 | var path_1 = require("path"); 41 | var ProblemMonitor = /** @class */ (function () { 42 | function ProblemMonitor() { 43 | this.attemptCounts = new Map(); 44 | this.autoApprove = true; 45 | this.issueResponses = new Map(); 46 | } 47 | // Record an attempt for a specific issue 48 | ProblemMonitor.prototype.recordAttempt = function (issueId) { 49 | var attempts = this.attemptCounts.get(issueId) || 0; 50 | this.attemptCounts.set(issueId, attempts + 1); 51 | }; 52 | // Check if the attempt threshold has been reached 53 | ProblemMonitor.prototype.isThresholdReached = function (issueId, threshold) { 54 | if (threshold === void 0) { threshold = 2; } 55 | var attempts = this.attemptCounts.get(issueId) || 0; 56 | return attempts >= threshold; 57 | }; 58 | // Get the number of attempts for a specific issue 59 | ProblemMonitor.prototype.getAttemptCount = function (issueId) { 60 | return this.attemptCounts.get(issueId) || 0; 61 | }; 62 | // Reset attempts for a specific issue 63 | ProblemMonitor.prototype.resetAttempts = function (issueId) { 64 | this.attemptCounts.delete(issueId); 65 | this.issueResponses.delete(issueId); 66 | }; 67 | // Set auto-approve flag 68 | ProblemMonitor.prototype.setAutoApprove = function (value) { 69 | this.autoApprove = value; 70 | }; 71 | // Get auto-approve status 72 | ProblemMonitor.prototype.shouldAutoApprove = function () { 73 | return this.autoApprove; 74 | }; 75 | // Prompt the AI coder for more information about the problem 76 | ProblemMonitor.prototype.promptAICoder = function (issueId) { 77 | return __awaiter(this, void 0, void 0, function () { 78 | var aiResponse; 79 | return __generator(this, function (_a) { 80 | switch (_a.label) { 81 | case 0: 82 | // This function simulates prompting the AI coder and getting a response 83 | // In a real implementation, this would involve an API call or inter-process communication 84 | console.log("Prompting AI coder for more details about issue: ".concat(issueId)); 85 | return [4 /*yield*/, this.getAICoderResponse(issueId)]; 86 | case 1: 87 | aiResponse = _a.sent(); 88 | // Store the response 89 | this.issueResponses.set(issueId, aiResponse); 90 | // Log the action 91 | this.logAction('info', "Prompted AI coder for issue '".concat(issueId, "'. Response received.")); 92 | return [2 /*return*/]; 93 | } 94 | }); 95 | }); 96 | }; 97 | // Simulated function to get AI coder's response 98 | ProblemMonitor.prototype.getAICoderResponse = function (issueId) { 99 | return __awaiter(this, void 0, void 0, function () { 100 | return __generator(this, function (_a) { 101 | // Simulate an asynchronous operation 102 | return [2 /*return*/, new Promise(function (resolve) { 103 | setTimeout(function () { 104 | var response = "AI coder's explanation for issue '".concat(issueId, "'"); 105 | resolve(response); 106 | }, 1000); 107 | })]; 108 | }); 109 | }); 110 | }; 111 | // Get the AI coder's response for a specific issue 112 | ProblemMonitor.prototype.getAICoderResponseForIssue = function (issueId) { 113 | return this.issueResponses.get(issueId); 114 | }; 115 | // Helper function to log actions 116 | ProblemMonitor.prototype.logAction = function (status, message, errorStack) { 117 | var logFilePath = path_1.default.resolve(process.cwd(), 'action_log.txt'); 118 | var timestamp = new Date().toISOString(); 119 | var logEntry = "[".concat(timestamp, "] [").concat(status.toUpperCase(), "] ").concat(message).concat(errorStack ? "\nStack Trace:\n".concat(errorStack, "\n") : '', "\n"); 120 | fs_1.default.appendFileSync(logFilePath, logEntry); 121 | }; 122 | return ProblemMonitor; 123 | }()); 124 | var problemMonitor = new ProblemMonitor(); 125 | exports.default = problemMonitor; 126 | -------------------------------------------------------------------------------- /src/handlers/addDocumentationHandler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); 13 | return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | exports.addDocumentationHandler = addDocumentationHandler; 40 | var detectTechnologies_js_1 = require("../utils/detectTechnologies.js"); 41 | var findDocumentationLinks_js_1 = require("../utils/findDocumentationLinks.js"); 42 | var addDocumentationFromUrls_js_1 = require("../utils/addDocumentationFromUrls.js"); 43 | var loadDocumentation_js_1 = require("../utils/loadDocumentation.js"); 44 | var getProjectPath_js_1 = require("../utils/getProjectPath.js"); 45 | var problemMonitor_js_1 = require("../utils/problemMonitor.js"); 46 | var fs = require("fs"); 47 | var path = require("path"); 48 | var vectorStore = null; 49 | function addDocumentationHandler(request) { 50 | return __awaiter(this, void 0, void 0, function () { 51 | // Helper function to log actions 52 | function logAction(status, message, errorStack) { 53 | var logFilePath = path.resolve(process.cwd(), 'action_log.txt'); 54 | var timestamp = new Date().toISOString(); 55 | var logEntry = "[".concat(timestamp, "] [").concat(status.toUpperCase(), "] ").concat(message).concat(errorStack ? "\nStack Trace:\n".concat(errorStack, "\n") : '', "\n"); 56 | fs.appendFileSync(logFilePath, logEntry); 57 | } 58 | function isAddDocumentationArgs(obj) { 59 | return (obj && 60 | typeof obj === 'object' && 61 | (obj.urls === undefined || 62 | (Array.isArray(obj.urls) && obj.urls.every(function (url) { return typeof url === 'string'; }))) && 63 | (obj.projectPath === undefined || typeof obj.projectPath === 'string')); 64 | } 65 | var args, issueId, errorMsg, _a, urls, projectPath, errorMsg, aiResponse, searchResults, successMsg_1, errorMsg_1, effectiveProjectPath, OPENAI_API_KEY, urlsToProcess, technologies, autoUrls, addedDocs, successMsg, error_1, attemptCount, infoMsg; 66 | return __generator(this, function (_b) { 67 | switch (_b.label) { 68 | case 0: 69 | args = request.params.arguments || {}; 70 | issueId = 'add_documentation'; 71 | if (!isAddDocumentationArgs(args)) { 72 | errorMsg = 'Invalid arguments for add_documentation'; 73 | logAction('error', errorMsg); 74 | throw new Error(errorMsg); 75 | } 76 | _a = args, urls = _a.urls, projectPath = _a.projectPath; 77 | _b.label = 1; 78 | case 1: 79 | _b.trys.push([1, 12, , 15]); 80 | if (!!problemMonitor_js_1.default.shouldAutoApprove()) return [3 /*break*/, 5]; 81 | errorMsg = 'Auto-approval is disabled due to repeated failures.'; 82 | logAction('error', errorMsg); 83 | // Prompt the AI coder for more information 84 | return [4 /*yield*/, problemMonitor_js_1.default.promptAICoder(issueId)]; 85 | case 2: 86 | // Prompt the AI coder for more information 87 | _b.sent(); 88 | aiResponse = problemMonitor_js_1.default.getAICoderResponseForIssue(issueId); 89 | if (!(aiResponse && vectorStore)) return [3 /*break*/, 4]; 90 | return [4 /*yield*/, (0, searchDocumentation_js_1.searchDocumentation)(aiResponse, vectorStore)]; 91 | case 3: 92 | searchResults = _b.sent(); 93 | // Log the search results 94 | logAction('info', "Searched documentation based on AI coder's response. Found ".concat(searchResults.length, " related documents.")); 95 | // Attempt to resolve the issue again using search results 96 | // For this example, we'll simulate a successful resolution 97 | problemMonitor_js_1.default.resetAttempts(issueId); 98 | problemMonitor_js_1.default.setAutoApprove(true); 99 | successMsg_1 = "Issue resolved after consulting AI coder's response and searching documentation."; 100 | logAction('success', successMsg_1); 101 | return [2 /*return*/, { 102 | content: [ 103 | { 104 | type: 'text', 105 | text: successMsg_1, 106 | }, 107 | ], 108 | }]; 109 | case 4: 110 | errorMsg_1 = 'No response from AI coder to proceed with.'; 111 | logAction('error', errorMsg_1); 112 | throw new Error(errorMsg_1); 113 | case 5: 114 | effectiveProjectPath = projectPath; 115 | if (!effectiveProjectPath) { 116 | effectiveProjectPath = (0, getProjectPath_js_1.getProjectPath)(); 117 | if (!effectiveProjectPath) { 118 | throw new Error('Project path not provided and PROJECT_PATH environment variable is not set or invalid.'); 119 | } 120 | } 121 | if (!!vectorStore) return [3 /*break*/, 7]; 122 | OPENAI_API_KEY = process.env.OPENAI_API_KEY; 123 | if (!OPENAI_API_KEY) { 124 | throw new Error('OPENAI_API_KEY environment variable is required'); 125 | } 126 | return [4 /*yield*/, (0, loadDocumentation_js_1.loadDocumentation)(OPENAI_API_KEY)]; 127 | case 6: 128 | vectorStore = _b.sent(); 129 | _b.label = 7; 130 | case 7: 131 | urlsToProcess = []; 132 | if (urls && urls.length > 0) { 133 | urlsToProcess.push.apply(urlsToProcess, urls); 134 | } 135 | if (!effectiveProjectPath) return [3 /*break*/, 10]; 136 | return [4 /*yield*/, (0, detectTechnologies_js_1.detectTechnologies)(effectiveProjectPath)]; 137 | case 8: 138 | technologies = _b.sent(); 139 | return [4 /*yield*/, (0, findDocumentationLinks_js_1.findDocumentationLinks)(technologies)]; 140 | case 9: 141 | autoUrls = _b.sent(); 142 | urlsToProcess.push.apply(urlsToProcess, autoUrls); 143 | _b.label = 10; 144 | case 10: 145 | if (urlsToProcess.length === 0) { 146 | throw new Error('No URLs provided or detected.'); 147 | } 148 | return [4 /*yield*/, (0, addDocumentationFromUrls_js_1.addDocumentationFromUrls)(urlsToProcess, vectorStore)]; 149 | case 11: 150 | addedDocs = _b.sent(); 151 | // Reset attempts on success 152 | problemMonitor_js_1.default.resetAttempts(issueId); 153 | problemMonitor_js_1.default.setAutoApprove(true); 154 | successMsg = "Successfully added documentation from the following URLs:\n".concat(addedDocs.join('\n')); 155 | logAction('success', successMsg); 156 | return [2 /*return*/, { 157 | content: [ 158 | { 159 | type: 'text', 160 | text: successMsg, 161 | }, 162 | ], 163 | }]; 164 | case 12: 165 | error_1 = _b.sent(); 166 | console.error('Error adding documentation:', error_1.message); 167 | // Record the failed attempt 168 | problemMonitor_js_1.default.recordAttempt(issueId); 169 | attemptCount = problemMonitor_js_1.default.getAttemptCount(issueId); 170 | if (!problemMonitor_js_1.default.isThresholdReached(issueId)) return [3 /*break*/, 14]; 171 | problemMonitor_js_1.default.setAutoApprove(false); 172 | infoMsg = "Attempt threshold reached for ".concat(issueId, ". Auto-approval disabled."); 173 | logAction('info', infoMsg); 174 | // Prompt the AI coder for more information 175 | return [4 /*yield*/, problemMonitor_js_1.default.promptAICoder(issueId)]; 176 | case 13: 177 | // Prompt the AI coder for more information 178 | _b.sent(); 179 | _b.label = 14; 180 | case 14: 181 | // Log the error 182 | logAction('error', "Error in addDocumentationHandler: ".concat(error_1.message), error_1.stack); 183 | throw new Error("Error adding documentation: ".concat(error_1.message)); 184 | case 15: return [2 /*return*/]; 185 | } 186 | }); 187 | }); 188 | } 189 | // Import the searchDocumentation function 190 | var searchDocumentation_js_1 = require("../utils/searchDocumentation.js"); 191 | --------------------------------------------------------------------------------