pursIdeReset
Clear all loaded modules from the PureScript IDE server's memory to aid in switching projects or handling major code changes. Ensures a clean slate before reloading with pursIdeLoad.
Instructions
Clear all loaded modules from the IDE server's memory. PREREQUISITE: IDE server must be running. Use when switching projects or after major code changes. You'll need to run pursIdeLoad again after this.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- index.js:1040-1043 (handler)The handler function for the 'pursIdeReset' MCP tool. It sends a 'reset' command to the underlying 'purs ide' server via sendCommandToPursIde and formats the response as MCP content."pursIdeReset": async () => { const result = await sendCommandToPursIde({ command: "reset" }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; },
- index.js:734-738 (schema)The tool schema definition for 'pursIdeReset' in the TOOL_DEFINITIONS array, used by the MCP 'tools/list' method. Defines no input parameters.{ name: "pursIdeReset", description: "Clear all loaded modules from the IDE server's memory. PREREQUISITE: IDE server must be running. Use when switching projects or after major code changes. You'll need to run pursIdeLoad again after this.", inputSchema: { type: "object", properties: {}, additionalProperties: false } },
- index.js:791-1127 (registration)The INTERNAL_TOOL_HANDLERS object registers the 'pursIdeReset' handler function, which is used in 'tools/call' dispatch to map tool names to their executors.const INTERNAL_TOOL_HANDLERS = { "get_server_status": internalHandleGetServerStatus, "echo": internalHandleEcho, "query_purescript_ast": internalHandleQueryPurescriptAst, "start_purs_ide_server": internalHandleStartPursIdeServer, "stop_purs_ide_server": internalHandleStopPursIdeServer, "query_purs_ide": internalHandleQueryPursIde, "generate_dependency_graph": internalHandleGenerateDependencyGraph, // --- Phase 1: Core AST Query Tool Handlers (to be added) --- "getModuleName": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); const tree = purescriptTsParser.parse(code); // Corrected query to capture the full text of the qualified_module node const query = new Query(PureScriptLanguage, `(purescript name: (qualified_module) @module.qname)`); const captures = query.captures(tree.rootNode); if (captures.length > 0 && captures[0].name === 'module.qname') { // The text of the qualified_module node itself is the full module name return { content: [{ type: "text", text: JSON.stringify(captures[0].node.text.replace(/\s+/g, ''), null, 2) }] }; } return { content: [{ type: "text", text: JSON.stringify(null, null, 2) }] }; }, "getFunctionNames": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); const tree = purescriptTsParser.parse(code); const query = new Query(PureScriptLanguage, `(function name: (variable) @func.name)`); const captures = query.captures(tree.rootNode); const functionNames = captures.map(capture => capture.node.text); return { content: [{ type: "text", text: JSON.stringify(functionNames, null, 2) }] }; }, // Stubs for other Phase 1 handlers - to be implemented "getImports": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); const tree = purescriptTsParser.parse(code); const query = new Query(PureScriptLanguage, `(import module: (qualified_module) @import.path)`); const captures = query.captures(tree.rootNode); const imports = []; for (const capture of captures) { if (capture.name === 'import.path') { const moduleNodes = capture.node.children.filter(child => child.type === 'module'); if (moduleNodes.length > 0) { const fullPath = moduleNodes.map(n => n.text).join('.'); const moduleName = moduleNodes[0].text; const submoduleName = moduleNodes.length > 1 ? moduleNodes[1].text : undefined; imports.push({ module: moduleName, submodule: submoduleName, fullPath: fullPath }); } } } return { content: [{ type: "text", text: JSON.stringify(imports, null, 2) }] }; }, "getTopLevelDeclarationNames": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); // true for module-oriented const tree = purescriptTsParser.parse(code); const querySource = ` [ (function name: (variable) @name) (data name: (type) @name) (class_declaration (class_head (class_name (type) @name))) (type_alias name: (type) @name) (foreign_import name: (variable) @name) (signature name: (variable) @name) (class_instance (instance_name) @name) (kind_value_declaration name: (type) @name) ] `; const query = new Query(PureScriptLanguage, querySource); const captures = query.captures(tree.rootNode); const declarationNames = captures.map(capture => capture.node.text).filter(Boolean); // Deduplicate names const uniqueNames = [...new Set(declarationNames)]; return { content: [{ type: "text", text: JSON.stringify(uniqueNames, null, 2) }] }; }, "getIntegerLiterals": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); const tree = purescriptTsParser.parse(code); const integerLiterals = []; const query = new Query(PureScriptLanguage, `(integer) @integer.literal`); const captures = query.captures(tree.rootNode); captures.forEach(capture => { if (capture.name === 'integer.literal') { integerLiterals.push(parseInt(capture.node.text, 10)); } }); return { content: [{ type: "text", text: JSON.stringify(integerLiterals, null, 2) }] }; }, "getWhereBindings": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); // Snippet-oriented const tree = purescriptTsParser.parse(code); const whereClausesText = []; // Query for 'where' keyword followed by a 'declarations' block within a function or let binding const querySource = ` (function (where) @where_keyword (declarations) @declarations_block) `; // Also consider 'let' bindings with 'where' clauses, though less common for top-level 'where' // (let_binding (where) @where_keyword (declarations) @declarations_block) const query = new Query(PureScriptLanguage, querySource); const matches = query.matches(tree.rootNode); for (const match of matches) { const whereKeywordNode = match.captures.find(c => c.name === 'where_keyword')?.node; const declarationsNode = match.captures.find(c => c.name === 'declarations_block')?.node; if (whereKeywordNode && declarationsNode) { // Construct the text from "where" keyword to the end of the declarations block // This requires careful handling of start and end positions if they are not contiguous in the source text string // For simplicity, if they are siblings and in order, we can take text from start of 'where' to end of 'declarations' // A safer way is to combine their individual texts if they represent the full conceptual block const fullWhereClauseText = `${whereKeywordNode.text} ${declarationsNode.text}`; whereClausesText.push(fullWhereClauseText.trim()); } } // Deduplicate, as some complex structures might yield multiple partial matches const uniqueWhereClauses = [...new Set(whereClausesText)]; return { content: [{ type: "text", text: JSON.stringify(uniqueWhereClauses, null, 2) }] }; }, "getTopLevelDeclarations": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); // true for module-oriented const tree = purescriptTsParser.parse(code); const querySource = ` [ (function name: (variable) @name.function) @DeclValue (data name: (type) @name.data_type) @DeclData (class_declaration (class_head (class_name (type) @name.class))) @DeclClass (type_alias name: (type) @name.type_alias) @DeclType (newtype name: (type) @name.newtype) @DeclNewtype (foreign_import name: (variable) @name.foreign) @DeclForeign (signature name: (variable) @name.signature) @DeclSignature (class_instance (instance_head (class_name) @name.instance_class (type_name)? @name.instance_type)) @DeclInstanceChain (kind_value_declaration name: (type) @name.kind_sig) @DeclKindSignature (derive_declaration) @DeclDerive (type_role_declaration (type) @name.role_type (type_role)+ @name.role_value) @DeclRole (operator_declaration (operator) @name.operator) @DeclFixity ] `; const query = new Query(PureScriptLanguage, querySource); const matches = query.matches(tree.rootNode); const rawDeclarations = []; for (const match of matches) { const mainCapture = match.captures.find(c => c.name.startsWith("Decl")); if (!mainCapture) continue; const declNode = mainCapture.node; const mappedType = mainCapture.name; const value = declNode.text; // This is the full text of the declaration node // Create a map of captures for efficient lookup // Store the full capture object {name, node} as node properties (like .text) are needed const allCapturesMap = new Map(match.captures.map(c => [c.name, c])); let finalName; // Prioritized list of capture names that directly provide the 'name' const singleNameCaptureKeys = [ "name.function", "name.data_type", "name.class", "name.type_alias", "name.newtype", "name.foreign", "name.signature", "name.kind_sig", "name.role_type", "name.operator" ]; let foundSingleName = false; for (const key of singleNameCaptureKeys) { if (allCapturesMap.has(key)) { finalName = allCapturesMap.get(key).node.text; foundSingleName = true; break; } } if (!foundSingleName) { if (allCapturesMap.has("name.instance_class")) { finalName = allCapturesMap.get("name.instance_class").node.text; if (allCapturesMap.has("name.instance_type")) { finalName += ` ${allCapturesMap.get("name.instance_type").node.text}`; } } else if (mappedType === "DeclDerive" || mappedType === "DeclFixity") { // declNode is mainCapture.node, which is already available const firstIdentNode = declNode.descendantsOfType("identifier")[0] || declNode.descendantsOfType("type")[0] || declNode.descendantsOfType("operator")[0]; finalName = firstIdentNode ? firstIdentNode.text : `complex_${mappedType.toLowerCase().replace('decl', '')}`; } else { finalName = "unknown"; // Default if no other specific name found } } rawDeclarations.push({ name: finalName, type: mappedType, value, treeSitterType: declNode.type }); // Removed node property } let declarations = rawDeclarations; // Use rawDeclarations directly without consolidation // Apply filters if provided if (args.filters) { const { name, type, value } = args.filters; if (name) { const nameRegex = new RegExp(name); declarations = declarations.filter(d => nameRegex.test(d.name)); } if (type) { const typeRegex = new RegExp(type); declarations = declarations.filter(d => typeRegex.test(d.type)); } if (value) { const valueRegex = new RegExp(value); declarations = declarations.filter(d => valueRegex.test(d.value)); } } return { content: [{ type: "text", text: JSON.stringify(declarations, null, 2) }] }; }, // --- New purs ide command wrapper handlers --- "pursIdeLoad": async (args) => { const params = args || {}; // If args is null/undefined, pass empty object for default load all const result = await sendCommandToPursIde({ command: "load", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeType": async (args) => { if (!args || typeof args.search !== 'string') { throw new Error("Invalid input: 'search' (string) is required for pursIdeType."); } const params = { search: args.search, filters: args.filters || [], // Default to empty filters array currentModule: args.currentModule }; const result = await sendCommandToPursIde({ command: "type", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeCwd": async () => { const result = await sendCommandToPursIde({ command: "cwd" }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeReset": async () => { const result = await sendCommandToPursIde({ command: "reset" }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeQuit": async () => { let quitMessage = "purs ide server quit command initiated."; let pursIdeResponded = false; if (pursIdeProcess && pursIdeIsReady) { logToStderr("[pursIdeQuit] Attempting to send 'quit' command to purs ide server.", "debug"); sendCommandToPursIde({ command: "quit" }) .then(res => { pursIdeResponded = true; logToStderr(`[pursIdeQuit] purs ide server responded to quit command: ${JSON.stringify(res)}`, 'debug'); }) .catch(err => { logToStderr(`[pursIdeQuit] Error/No response from purs ide server for quit command: ${err.message}`, 'warn'); }); // Wait a short period to allow purs ide server to shut down gracefully // or for the sendCommandToPursIde to potentially resolve/reject. await delay(250); // Increased slightly to 250ms } else { quitMessage = "No purs ide server was running or ready to send quit command to."; logToStderr("[pursIdeQuit] " + quitMessage, "info"); } // Ensure our managed process is stopped regardless of purs ide's response if (pursIdeProcess) { logToStderr("[pursIdeQuit] Ensuring managed purs ide process is stopped.", "debug"); pursIdeProcess.kill(); pursIdeProcess = null; pursIdeIsReady = false; logPursIdeOutput("Managed purs ide server process stopped via pursIdeQuit tool.", "info"); quitMessage += " Managed purs ide process has been stopped."; } else { if (!quitMessage.includes("No purs ide server was running")) { quitMessage += " No managed purs ide process was found running to stop."; } } if (pursIdeResponded) { quitMessage += " purs ide server acknowledged quit."; } else { quitMessage += " purs ide server may not have acknowledged quit before process termination."; } return { content: [{ type: "text", text: JSON.stringify({ status_message: quitMessage, resultType: "success" }, null, 2) }] }; }, "pursIdeRebuild": async (args) => { if (!args || typeof args.file !== 'string') { throw new Error("Invalid input: 'file' (string) is required for pursIdeRebuild."); } const params = { file: args.file, actualFile: args.actualFile, codegen: args.codegen // purs ide server defaults to js if undefined }; const result = await sendCommandToPursIde({ command: "rebuild", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeUsages": async (args) => { if (!args || typeof args.module !== 'string' || typeof args.namespace !== 'string' || typeof args.identifier !== 'string') { throw new Error("Invalid input: 'module', 'namespace', and 'identifier' (strings) are required for pursIdeUsages."); } const params = { module: args.module, namespace: args.namespace, identifier: args.identifier }; const result = await sendCommandToPursIde({ command: "usages", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeList": async (args) => { if (!args || typeof args.listType !== 'string') { throw new Error("Invalid input: 'listType' (string) is required for pursIdeList."); } const params = { type: args.listType }; if (args.listType === "import") { if (typeof args.file !== 'string') { throw new Error("'file' (string) is required when listType is 'import'."); } params.file = args.file; } const result = await sendCommandToPursIde({ command: "list", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } };
- index.js:169-204 (helper)Helper function sendCommandToPursIde used by the pursIdeReset handler to communicate the 'reset' command to the purs ide server process.function sendCommandToPursIde(commandPayload) { return new Promise((resolve, reject) => { if (!pursIdeProcess || !pursIdeIsReady || !pursIdeServerPort) { return reject(new Error("purs ide server is not running or not ready.")); } const client = new net.Socket(); let responseData = ''; client.connect(pursIdeServerPort, '127.0.0.1', () => { logToStderr(`[MCP Client->purs ide]: Sending command: ${JSON.stringify(commandPayload).substring(0,100)}...`, 'debug'); client.write(JSON.stringify(commandPayload) + '\n'); }); client.on('data', (data) => { responseData += data.toString(); if (responseData.includes('\n')) { const completeResponses = responseData.split('\n').filter(Boolean); responseData = ''; if (completeResponses.length > 0) { try { resolve(JSON.parse(completeResponses[0].trim())); } catch (e) { reject(new Error(`Failed to parse JSON response from purs ide: ${e.message}. Raw: ${completeResponses[0]}`)); } } client.end(); } }); client.on('end', () => { if (responseData.trim()) { try { resolve(JSON.parse(responseData.trim())); } catch (e) { reject(new Error(`Failed to parse JSON response from purs ide on end: ${e.message}. Raw: ${responseData}`));} } }); client.on('close', () => { logToStderr(`[MCP Client->purs ide]: Connection closed.`, 'debug'); }); client.on('error', (err) => reject(new Error(`TCP connection error with purs ide server: ${err.message}`))); }); }
- index.js:1158-1164 (registration)The MCP 'tools/list' request handler returns the list of tools including 'pursIdeReset' schema from TOOL_DEFINITIONS.if (method === 'tools/list') { const toolsToExclude = ['query_purescript_ast', 'query_purs_ide']; // Keep query_purs_ide for now, for direct access if needed const filteredToolDefinitions = TOOL_DEFINITIONS.filter( tool => !toolsToExclude.includes(tool.name) ); return createSuccessResponse(id, { tools: filteredToolDefinitions }); }