getTopLevelDeclarations
Extract and filter top-level declarations in PureScript code to retrieve names, types, and full source code. Supports regex-based filtering for precise analysis of functions, data types, or classes.
Instructions
Get detailed information about all main definitions in PureScript code: names, types (function/data/class), and full source code. Includes filtering options to find specific items. More comprehensive than getTopLevelDeclarationNames.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | PureScript code string. | |
| filePath | No | Absolute path to the PureScript file. Only absolute paths are supported. | |
| filters | No | Optional filters to apply to the declarations. |
Implementation Reference
- index.js:922-1017 (handler)The main handler function for the getTopLevelDeclarations tool. It uses Tree-sitter to parse the input PureScript code (from file or string), executes a comprehensive Tree-sitter query to match various top-level declaration types (functions, data, classes, etc.), extracts the name, mapped type (e.g., DeclValue, DeclData), and full source text for each declaration, handles special cases for instance and derive/fixity declarations, applies optional regex filters on name/type/value, and returns the filtered list of declarations as JSON in MCP content format."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) }] }; },
- index.js:638-660 (schema)The tool definition object in the TOOL_DEFINITIONS array, which includes the name, description, and detailed inputSchema specifying parameters: filePath or code (exactly one), and optional filters object with name/type/value regex strings. This is served by the tools/list MCP method.name: "getTopLevelDeclarations", description: "Get detailed information about all main definitions in PureScript code: names, types (function/data/class), and full source code. Includes filtering options to find specific items. More comprehensive than getTopLevelDeclarationNames.", 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." }, filters: { type: "object", properties: { name: { type: "string", description: "Regex to filter declarations by name." }, type: { type: "string", description: "Regex to filter declarations by their mapped type (e.g., DeclData, DeclValue)." }, value: { type: "string", description: "Regex to filter declarations by their full text value." } }, additionalProperties: false, description: "Optional filters to apply to the declarations." } }, additionalProperties: false, description: "Exactly one of 'filePath' or 'code' must be provided. Filters are optional." } }, // End of Phase 1 tools
- index.js:126-151 (helper)The getCodeFromInput helper function used by the handler (and others) to retrieve code from either filePath (absolute) or inline code string, validating exactly one is provided for module-oriented tools.async function getCodeFromInput(args, isModuleOriented = true) { if (isModuleOriented) { const hasFilePath = args && typeof args.filePath === 'string'; const hasCode = args && typeof args.code === 'string'; if ((hasFilePath && hasCode) || (!hasFilePath && !hasCode)) { throw new Error("Invalid input: Exactly one of 'filePath' or 'code' must be provided for module-oriented tools."); } if (hasFilePath) { if (!path.isAbsolute(args.filePath)) { throw new Error(`Invalid filePath: '${args.filePath}' is not an absolute path. Only absolute paths are supported.`); } try { return await fs.readFile(args.filePath, 'utf-8'); } catch (e) { throw new Error(`Failed to read file at ${args.filePath}: ${e.message}`); } } return args.code; } else { // Snippet-oriented if (!args || typeof args.code !== 'string') { throw new Error("Invalid input: 'code' (string) is required for snippet-oriented tools."); } return args.code; } }
- index.js:922-1017 (registration)The tool handler is registered in the INTERNAL_TOOL_HANDLERS object literal at key 'getTopLevelDeclarations', which is used by the tools/call MCP method dispatcher to invoke the handler."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) }] }; },
- index.js:557-785 (registration)The TOOL_DEFINITIONS array (used by tools/list) includes the schema and metadata for getTopLevelDeclarations.const TOOL_DEFINITIONS = [ { name: "get_server_status", description: "Check if IDE server processes are running to avoid resource conflicts. Shows status of Tree-sitter parser (lightweight code analysis) and purs IDE server (process for type checking). ALWAYS use this before starting new IDE servers to prevent running multiple processes simultaneously.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, }, { name: "echo", description: "Simple test tool that echoes back your input. Use to verify the MCP server is responding correctly.", inputSchema: { type: "object", properties: { message: { type: "string"}}, required: ["message"], additionalProperties: false }, }, { name: "query_purescript_ast", description: "[DEPRECATED] Parses PureScript code and executes a Tree-sitter query against its AST. Prefer specific AST query tools.", inputSchema: { type: "object", properties: { purescript_code: { type: "string" }, tree_sitter_query: { type: "string" }}, required: ["purescript_code", "tree_sitter_query"], additionalProperties: false }, }, // --- Phase 1: Core AST Query Tools --- // Module Information { name: "getModuleName", description: "Extract the module name (like 'Data.List' or 'Main') from PureScript code. Works on files or code snippets without needing the IDE server. Useful for understanding code structure.", 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." } }, { 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." } }, { name: "getTopLevelDeclarationNames", description: "List all main definitions in PureScript code: function names, data types, type classes, etc. Gets just the names (like 'myFunction', 'MyDataType'). Fast analysis without needing 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." } }, // Function and Value Declarations { name: "getFunctionNames", description: "Extract only function names from PureScript code snippets. Focuses specifically on functions, ignoring data types and classes. Quick analysis for code understanding.", inputSchema: { type: "object", properties: { code: { type: "string", description: "PureScript code snippet." } }, required: ["code"], additionalProperties: false } }, // Expressions and Literals // Control Flow Analysis { name: "getWhereBindings", description: "Find 'where' clauses in PureScript functions. These contain local helper functions and variables. Useful for understanding function implementation details.", inputSchema: { type: "object", properties: { code: { type: "string", description: "PureScript code snippet." } }, required: ["code"], additionalProperties: false } }, { name: "getTopLevelDeclarations", description: "Get detailed information about all main definitions in PureScript code: names, types (function/data/class), and full source code. Includes filtering options to find specific items. More comprehensive than getTopLevelDeclarationNames.", 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." }, filters: { type: "object", properties: { name: { type: "string", description: "Regex to filter declarations by name." }, type: { type: "string", description: "Regex to filter declarations by their mapped type (e.g., DeclData, DeclValue)." }, value: { type: "string", description: "Regex to filter declarations by their full text value." } }, additionalProperties: false, description: "Optional filters to apply to the declarations." } }, additionalProperties: false, description: "Exactly one of 'filePath' or 'code' must be provided. Filters are optional." } }, // End of Phase 1 tools { name: "start_purs_ide_server", description: "Start the PureScript IDE server for type checking, auto-completion, and error detection. Automatically stops any existing server to prevent conflicts. Only run one at a time. Required for all pursIde* tools to work. Automatically selects a random available port to avoid conflicts - the port number is returned in the response. Only accepts absolute paths.", inputSchema: { type: "object", properties: { project_path: { type: "string", description: "Absolute path to the PureScript project directory. Only absolute paths are supported." }, output_directory: { type: "string", default: "output/" }, source_globs: { type: "array", items: { type: "string" }, default: ["src/**/*.purs", ".spago/*/*/src/**/*.purs", "test/**/*.purs"]}, log_level: { type: "string", enum: ["all", "debug", "perf", "none"], default: "none" } }, required: ["project_path"], additionalProperties: false }, }, { name: "stop_purs_ide_server", description: "Stop the PureScript IDE server to free up system resources. Use when you're done with type checking or want to switch projects. All pursIde* tools will stop working after this.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, }, { name: "query_purs_ide", description: "Send raw commands to the PureScript IDE server. PREREQUISITE: IDE server must be running (use start_purs_ide_server first). Advanced tool - prefer specific pursIde* tools for common tasks.", inputSchema: { type: "object", properties: { purs_ide_command: { type: "string" }, purs_ide_params: { type: "object" }}, required: ["purs_ide_command"], additionalProperties: false }, }, { name: "generate_dependency_graph", description: "Create a dependency graph showing which functions/types use which others in PureScript modules. PREREQUISITES: IDE server must be running and modules must be loaded. Useful for understanding code relationships and refactoring impact.", inputSchema: { type: "object", properties: { target_modules: { type: "array", items: { type: "string" }, description: "Array of module names." }, max_concurrent_requests: { type: "integer", description: "Max concurrent 'usages' requests.", default: 5 } }, required: ["target_modules"], additionalProperties: false }, }, // --- purs ide direct command wrappers --- { name: "pursIdeLoad", description: "Load PureScript modules into the IDE server for type checking and completions. PREREQUISITE: IDE server must be running. ALWAYS run this first after starting the IDE server before using other pursIde* tools.", inputSchema: { type: "object", properties: { modules: { type: "array", items: { type: "string" }, description: "Optional: specific modules to load. If omitted, attempts to load all compiled modules." } }, additionalProperties: false } }, { name: "pursIdeType", description: "Look up the type signature of functions, variables, or values in PureScript code. PREREQUISITES: IDE server running and modules loaded. Helpful for understanding what a function expects and returns.", inputSchema: { type: "object", properties: { search: { type: "string", description: "Identifier name to search for." }, filters: { type: "array", items: { type: "object" }, description: "Optional: Array of Filter objects." }, currentModule: { type: "string", description: "Optional: Current module context." } }, required: ["search"], additionalProperties: false } }, { name: "pursIdeCwd", description: "Get the current working directory that the IDE server is using. PREREQUISITE: IDE server must be running. Useful for understanding the project context.", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { 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 } }, { name: "pursIdeQuit", description: "Gracefully shut down the IDE server and free up resources. PREREQUISITE: IDE server must be running. Same effect as stop_purs_ide_server but uses the server's built-in quit command first.", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { name: "pursIdeRebuild", description: "Quickly recompile a single PureScript module and check for errors. PREREQUISITES: IDE server running and modules loaded. Much faster than full project rebuild. Use when editing code to get immediate feedback.", inputSchema: { type: "object", properties: { file: { type: "string", description: "Path to the module to rebuild, or 'data:' prefixed source code." }, actualFile: { type: "string", description: "Optional: Real path if 'file' is 'data:' or a temp file." }, codegen: { type: "array", items: { type: "string" }, description: "Optional: Codegen targets (e.g., 'js', 'corefn'). Defaults to ['js']." } }, required: ["file"], additionalProperties: false } }, { name: "pursIdeUsages", description: "Find everywhere a specific function, type, or value is used across the project. PREREQUISITES: IDE server running and modules loaded. Essential for refactoring - shows impact of changes. If you plan to refactor, get usages before refactoring so you can make changes to all places that function is used.", inputSchema: { type: "object", properties: { module: { type: "string", description: "Module where the identifier is defined." }, namespace: { type: "string", enum: ["value", "type", "kind"], description: "Namespace of the identifier." }, identifier: { type: "string", description: "The identifier to find usages for." } }, required: ["module", "namespace", "identifier"], additionalProperties: false } }, { name: "pursIdeList", description: "List available modules in the project or imports in a specific file. PREREQUISITES: IDE server running and modules loaded. Helps understand project structure and dependencies.", inputSchema: { type: "object", properties: { listType: { type: "string", enum: ["availableModules", "import"], description: "Type of list to retrieve." }, file: { type: "string", description: "Path to the .purs file (required for 'import' listType)." } }, required: ["listType"], additionalProperties: false } } ];