Skip to main content
Glama

getImports

Analyze PureScript code to extract all import statements, revealing external module dependencies. Works independently of IDE servers, supporting absolute file paths or direct code input.

Instructions

Find all import statements in PureScript code (like 'import Data.List', 'import Prelude'). Shows what external modules the code depends on. Works without the IDE server.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
codeNoPureScript code string.
filePathNoAbsolute path to the PureScript file. Only absolute paths are supported.

Implementation Reference

  • Handler function for the 'getImports' MCP tool. Parses PureScript code with Tree-sitter, queries for import statements, extracts module names and full paths from qualified_module nodes.
    "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) }] };
    },
  • Input schema definition for the 'getImports' tool, specifying parameters: filePath (absolute) or code string.
    {
        name: "getImports",
        description: "Find all import statements in PureScript code (like 'import Data.List', 'import Prelude'). Shows what external modules the code depends on. Works without the IDE server.",
        inputSchema: {
            type: "object",
            properties: {
                filePath: { type: "string", description: "Absolute path to the PureScript file. Only absolute paths are supported." },
                code: { type: "string", description: "PureScript code string." }
            },
            additionalProperties: false,
            description: "Exactly one of 'filePath' or 'code' must be provided."
        }
    },
  • index.js:791-1127 (registration)
    The INTERNAL_TOOL_HANDLERS object registers the 'getImports' handler function, used in 'tools/call' method handling.
    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) }] };
        }
    };
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It discloses the key behavioral trait of working 'without the IDE server', which is valuable operational context. However, it doesn't describe output format, error conditions, performance characteristics, or what happens with malformed code - leaving gaps for a tool with no output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tightly focused sentences with zero waste. First sentence states core functionality with examples, second explains the purpose, third provides crucial operational context. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description provides good purpose and operational context but leaves significant gaps. It doesn't describe what the output looks like (list format, structure), error handling, or edge cases. Given the complexity of code analysis, more completeness would be helpful.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters thoroughly. The description adds no additional parameter information beyond what's in the schema. Baseline 3 is appropriate when the schema does all the parameter documentation work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Find all import statements'), the resource ('PureScript code'), and provides concrete examples ('import Data.List', 'import Prelude'). It explicitly distinguishes this tool from siblings by stating it 'Works without the IDE server' (unlike pursIde* tools).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool ('Shows what external modules the code depends on') and distinguishes it from IDE-dependent tools. However, it doesn't explicitly state when NOT to use it or mention specific alternatives among the siblings (e.g., when to use getModuleName instead).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/avi892nash/purescript-mcp-tools'

If you have feedback or need assistance with the MCP directory API, please join our Discord server