Skip to main content
Glama

lokalise_list_projects

List all accessible localization projects to discover projects, assess translation health, and find project IDs. Optionally include progress and QA statistics.

Instructions

Portfolio overview showing all accessible localization projects. Optional: limit (100), page, includeStats (adds progress/QA data). Use as entry point to discover projects, assess translation health, or find specific project IDs. Returns: Projects with names, IDs, base language, stats. Start here before diving into specific projects.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoNumber of projects to return (1-500, default: 100)
pageNoPage number for pagination (default: 1)
includeStatsNoInclude detailed project statistics

Implementation Reference

  • Core MCP tool handler that receives list projects arguments, delegates to projectsController.listProjects(), and returns a formatted text response.
    async function handleListProjects(args: ListProjectsToolArgsType) {
    	const methodLogger = Logger.forContext(
    		"tools/projects.tool.ts",
    		"handleListProjects",
    	);
    	methodLogger.debug(
    		`Getting Lokalise projects list (limit: ${args.limit || "default"}, page: ${args.page || "1"})...`,
    		args,
    	);
    
    	try {
    		// Pass args directly to the controller
    		const result = await projectsController.listProjects(args);
    		methodLogger.debug("Got the response from the controller", result);
    
    		// Format the response for the MCP tool
    		return {
    			content: [
    				{
    					type: "text" as const,
    					text: result.content,
    				},
    			],
    		};
    	} catch (error) {
    		methodLogger.error("Error getting Lokalise projects list", error);
    		return formatErrorForMcpTool(error);
    	}
    }
  • Zod validation schema for the list projects tool arguments: limit (number, 1-500, optional), page (number, optional), includeStats (boolean, default false).
    export const ListProjectsToolArgs = z
    	.object({
    		limit: z
    			.number()
    			.optional()
    			.describe("Number of projects to return (1-500, default: 100)"),
    		page: z
    			.number()
    			.optional()
    			.describe("Page number for pagination (default: 1)"),
    		includeStats: z
    			.boolean()
    			.optional()
    			.default(false)
    			.describe("Include detailed project statistics"),
    	})
    	.strict();
  • Registration of the 'lokalise_list_projects' tool with the MCP server, binding the name, description, schema, and handler.
    // Register project listing tool
    server.tool(
    	"lokalise_list_projects",
    	"Portfolio overview showing all accessible localization projects. Optional: limit (100), page, includeStats (adds progress/QA data). Use as entry point to discover projects, assess translation health, or find specific project IDs. Returns: Projects with names, IDs, base language, stats. Start here before diving into specific projects.",
    	ListProjectsToolArgs.shape,
    	handleListProjects,
    );
  • Controller that orchestrates the list projects flow: validates args, calls projectsService to fetch data, and formats via formatProjectsList.
    async function listProjects(
    	args: ListProjectsToolArgsType,
    ): Promise<ControllerResponse> {
    	const methodLogger = Logger.forContext(
    		"controllers/projects.controller.ts",
    		"listProjects",
    	);
    	methodLogger.debug("Getting Lokalise projects list...");
    
    	try {
    		// Apply defaults and validation
    		const options: ApiRequestOptions = {
    			limit: args.limit,
    			page: args.page,
    		};
    
    		if (
    			options.limit !== undefined &&
    			(options.limit < 1 || options.limit > 500)
    		) {
    			// Validate limit if provided
    			throw new McpError(
    				"VALIDATION_ERROR: Invalid limit parameter. Must be between 1 and 500.",
    				ErrorType.VALIDATION_ERROR,
    			);
    		}
    
    		if (options.page !== undefined && options.page < 1) {
    			// Validate page if provided
    			throw new McpError(
    				"VALIDATION_ERROR: Invalid page parameter. Must be 1 or greater.",
    				ErrorType.VALIDATION_ERROR,
    			);
    		}
    
    		// Note: API key validation is deferred to service layer for lazy loading
    		methodLogger.debug("Getting projects from Lokalise", {
    			originalOptions: args,
    			options,
    		});
    
    		try {
    			// Call the service with the options
    			const projects = await projectsService.default.getProjects(options);
    			methodLogger.debug("Got the response from the service", {
    				projectCount: projects.length,
    			});
    
    			// Pass pagination metadata to formatter
    			const metadata = {
    				page: args.page,
    				limit: args.limit,
    			};
    			const formattedContent = formatProjectsList(
    				projects,
    				args.includeStats,
    				metadata,
    			);
    			return { content: formattedContent };
    		} catch (error) {
    			// Handle specific Lokalise API errors
    			if (
    				error instanceof McpError &&
    				(error.message.includes("Unauthorized") ||
    					error.message.includes("Invalid API token"))
    			) {
    				methodLogger.error("Lokalise API authentication failed");
    				throw new McpError(
    					"Lokalise API authentication failed. Please check your API token.",
    					ErrorType.AUTH_INVALID,
    				);
    			}
    
    			// For other errors, rethrow
    			throw error;
    		}
    	} catch (error) {
    		throw handleControllerError(
    			error,
    			buildErrorContext(
    				"Lokalise Project",
    				"listProjects",
    				"controllers/projects.controller.ts@listProjects",
    				`limit: ${args.limit}, page: ${args.page}`,
    				{ args },
    			),
    		);
    	}
    }
  • Formatter that converts project data into a Markdown string with headings, project info, optional statistics, and dashboard links.
    export function formatProjectsList(
    	projects: LokaliseProject[],
    	includeStats = false,
    	metadata?: { page?: number; limit?: number; total?: number },
    ): string {
    	const lines: string[] = [];
    
    	// Add a main heading with optional pagination info
    	const headingParts = [`Lokalise Projects (${projects.length})`];
    	if (metadata?.page) {
    		headingParts.push(`- Page ${metadata.page}`);
    	}
    	lines.push(formatHeading(headingParts.join(" "), 1));
    	lines.push("");
    
    	if (projects.length === 0) {
    		lines.push(
    			"No projects found. Create your first project in the Lokalise dashboard.",
    		);
    		lines.push("");
    		lines.push(formatSeparator());
    		lines.push(`*List retrieved at ${formatDate(new Date())}*`);
    		return lines.join("\n");
    	}
    
    	// Add projects list
    	for (const project of projects) {
    		lines.push(formatHeading(project.name, 2));
    		lines.push("");
    
    		// Add basic project information section
    		lines.push(formatHeading("Project Information", 3));
    
    		const projectInfo: Record<string, unknown> = {
    			"Project ID": project.project_id,
    			"Base Language": project.base_language_iso,
    			Created: formatDate(project.created_at),
    			"Created By": project.created_by_email,
    		};
    
    		// Add description if available
    		if (project.description) {
    			projectInfo.Description = project.description;
    		}
    
    		// Add basic stats inline if not detailed
    		if (project.statistics) {
    			projectInfo.Progress = `${project.statistics.progress_total}%`;
    			projectInfo["Total Keys"] = project.statistics.keys_total;
    			projectInfo.Languages = project.statistics.languages?.length;
    		} else {
    			projectInfo.Progress = "N/A";
    			projectInfo["Total Keys"] = "N/A";
    			projectInfo.Languages = "N/A";
    		}
    
    		lines.push(formatBulletList(projectInfo));
    		lines.push("");
    
    		if (project.statistics && includeStats) {
    			// Add statistics section if available and requested
    			lines.push(formatHeading("Project Statistics", 3));
    
    			const statsInfo: Record<string, unknown> = {
    				Progress: `${project.statistics.progress_total}%`,
    				"Total Keys": project.statistics.keys_total,
    				Languages: project.statistics.languages?.length,
    				"Team Members": project.statistics.team,
    				"Base Words": project.statistics.base_words,
    			};
    
    			if (project.statistics.qa_issues_total > 0) {
    				statsInfo["QA Issues"] = project.statistics.qa_issues_total;
    			}
    
    			lines.push(formatBulletList(statsInfo));
    			lines.push("");
    		}
    
    		// Add project URL section
    		lines.push(formatHeading("Dashboard", 3));
    		const baseUrl = config.getLokaliseHostname("lokalise.com");
    		const projectUrl = `https://app.${baseUrl}/project/${project.project_id}/?view=multi`;
    		lines.push(`${formatUrl(projectUrl, "View in Lokalise Dashboard")}`);
    		lines.push("");
    	}
    
    	// Add a separator
    	lines.push(formatSeparator());
    
    	// Add a timestamp footer
    	lines.push(`*List retrieved at ${formatDate(new Date())}*`);
    
    	return lines.join("\n");
    }
Behavior4/5

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

With no annotations, the description compensates by stating it returns project names, IDs, base language, and stats, and that includeStats 'adds progress/QA data'. It implies a read-only operation (listing) with no destructive behavior mentioned, providing sufficient behavioral context.

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?

The description is concise with 4 short sentences, each adding value: purpose, parameters, use cases, return info, and guidance. No wasted words; front-loaded with the core purpose.

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

Completeness5/5

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

For a list tool with 3 optional parameters and no output schema, the description is complete. It covers return fields, optional features (stats), and usage context. No missing critical information.

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 coverage is 100%, so baseline is 3. The description adds minor rephrasing: 'limit (100), page, includeStats (adds progress/QA data)'. It reinforces default values but does not significantly enrich the schema descriptions.

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 it's a 'Portfolio overview showing all accessible localization projects', uses specific verbs and resource, and distinguishes from siblings by positioning it as an entry point before diving into specific projects. It also lists use cases like discovering projects, assessing translation health, and finding project IDs.

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 explicit usage context: 'Use as entry point to discover projects, assess translation health, or find specific project IDs' and 'Start here before diving into specific projects.' It implies when not to use (for specific project details, use other tools) but does not explicitly name alternatives.

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

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/AbdallahAHO/lokalise-mcp'

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