Skip to main content
Glama
MCP_IMPLEMENTATION_GUIDE.md18 kB
# MCP Implementation Guide for Lokalise This guide provides comprehensive instructions for implementing new MCP tools for the Lokalise MCP Server. It incorporates lessons learned from the contributors domain implementation and references the official Lokalise API SDK. ## Table of Contents 1. [Overview](#overview) 2. [Prerequisites](#prerequisites) 3. [Implementation Steps](#implementation-steps) 4. [SDK Reference Guide](#sdk-reference-guide) 5. [Common Pitfalls & Solutions](#common-pitfalls--solutions) 6. [Testing & Validation](#testing--validation) 7. [Examples & Patterns](#examples--patterns) ## Overview The Lokalise MCP Server follows a domain-driven architecture with automated discovery. Each domain implements: - **MCP Tools**: AI assistant capabilities via Model Context Protocol - **CLI Commands**: Direct command-line interface - **MCP Resources**: Queryable data resources ### Architecture Pattern ``` domains/ ├── <domain>/ │ ├── index.ts # Domain barrel exports │ ├── <domain>.types.ts # Zod schemas & type definitions │ ├── <domain>.service.ts # Lokalise API integration │ ├── <domain>.controller.ts # Business logic orchestration │ ├── <domain>.formatter.ts # Response formatting │ ├── <domain>.tool.ts # MCP tool implementation │ ├── <domain>.cli.ts # CLI command implementation │ └── <domain>.resource.ts # MCP resource implementation ``` ## Prerequisites 1. **Lokalise API SDK Documentation**: Located at `/node_modules/@lokalise/node-api/` - Type definitions: `/node_modules/@lokalise/node-api/dist/main.d.ts` - API examples: `/node_modules/@lokalise/node-api/docs/api/<domain>.md` - Collection classes: `/node_modules/@lokalise/node-api/dist/collections/<domain>.d.ts` 2. **Required Knowledge**: - TypeScript with strict mode - Zod schema validation - Lokalise API concepts - MCP protocol basics ## Implementation Steps ### Step 1: Use the Scaffolding Script **ALWAYS use the scaffolding script first to generate the domain structure:** #### Option A: Interactive Mode (for humans) ```bash npm run scaffold:domain ``` #### Option B: CLI Mode (for AI agents - REQUIRED) **For AI Agents**: Use the CLI command to save 99% of tokens: ```bash npm run scaffold:domain:cli -- \ -n glossary \ -d "Glossary terms management for consistent translations" \ -a glossaryTerms \ -t list,get,create,update,delete \ -r collection,detail \ -c list,get,create ``` **Configuration Options:** - `-n, --name <name>`: Domain name (lowercase, no spaces) - `-d, --description <description>`: Domain description - `-a, --api-endpoint <endpoint>`: API endpoint (default: /<name>) - `-t, --tools <tools>`: Comma-separated operations: list,get,create,update,delete,bulkUpdate,bulkDelete - `-r, --resources <resources>`: Comma-separated resource types: collection,detail - `-c, --cli <commands>`: Comma-separated CLI commands (subset of tools) - `-q, --quiet`: Quiet mode (only outputs domain name on success) **Token Efficiency for AI Agents**: - Manual creation: ~2,500+ lines = ~10,000+ tokens - CLI scaffolding: 1 command = ~100 tokens - **Savings: 99% token reduction** ### Step 2: Study the Lokalise SDK 1. **Check available methods** in `/node_modules/@lokalise/node-api/dist/collections/<domain>.d.ts`: ```typescript // Example from contributors.d.ts export declare class Contributors extends BaseCollection { list(request_params: ContributorsListParams): Promise<PaginatedResult<Contributor>>; get(user_id: string | number, request_params: ProjectOnly): Promise<Contributor>; create(raw_contributors: NewContributor[], request_params: ProjectOnly): Promise<BulkResult<Contributor>>; update(user_id: string | number, params: UpdateContributor, request_params: ProjectOnly): Promise<Contributor>; delete(user_id: string | number, request_params: ProjectOnly): Promise<any>; me(request_params: ProjectOnly): Promise<Contributor>; } ``` 2. **Review API documentation** in `/node_modules/@lokalise/node-api/docs/api/<domain>.md` 3. **Identify SDK types** in `/node_modules/@lokalise/node-api/dist/main.d.ts`: ```typescript export interface Contributor { user_id: string | number; email: string; fullname: string; created_at: string; created_at_timestamp: number; is_admin: boolean; is_reviewer: boolean; languages: ContributorLanguage[]; admin_rights: string[]; } ``` ### Step 3: Update Types File 1. **Import SDK types**: ```typescript import type { Contributor, NewContributor, UpdateContributor, ContributorLanguage, PaginatedResult, BulkResult } from "@lokalise/node-api"; ``` 2. **Create Zod schemas that match SDK types**: ```typescript export const ContributorLanguageSchema = z.object({ lang_iso: z.string().describe("Language ISO code"), is_writable: z.boolean().optional().describe("Whether the contributor can edit translations") }); export const ContributorRightsSchema = z.enum([ "upload", "activity", "download", "settings", "create_branches", "statistics", "keys", "screenshots", "glossary", "contributors", "languages", "tasks" ]); ``` 3. **Define tool argument schemas**: ```typescript export const ListContributorsToolArgs = z.object({ projectId: z.string().describe("The ID of the Lokalise project"), page: z.number().int().positive().optional().describe("Page number for pagination"), limit: z.number().int().min(1).max(100).optional().describe("Number of items per page") }); ``` ### Step 4: Implement Service Layer 1. **Replace REPLACE_ME placeholders** with actual SDK methods: ```typescript // BEFORE (template): const result = await api.REPLACE_ME().list({ // AFTER (implementation): const result = await api.contributors().list({ ``` 2. **Map Zod types to SDK types** for create/update operations: ```typescript const contributorsData = args.contributors.map((contributor) => ({ email: contributor.email, fullname: contributor.fullname, is_admin: contributor.isAdmin, is_reviewer: contributor.isReviewer, languages: contributor.languages?.map(lang => ({ lang_iso: lang.langIso, is_writable: lang.isWritable })), admin_rights: contributor.adminRights })); ``` 3. **Handle special endpoints** (like `me()` for contributors): ```typescript async me(args: GetCurrentUserToolArgsType): Promise<Contributor> { const api = getLokaliseApi(); const result = await api.contributors().me({ project_id: args.projectId, }); return result; } ``` ### Step 5: Fix Common Issues 1. **Controller Response Type**: ```typescript // CORRECT - Only return content property return { content: formattedContent }; // WRONG - Don't include data or metadata return { content: formattedContent, data: result, // ❌ Remove this metadata: {} // ❌ Remove this }; ``` 2. **Error Handling**: ```typescript // CORRECT - Use ErrorContext object throw handleControllerError(error, { source: "ContributorsController.listContributors", entityType: "Contributors", entityId: args.projectId, operation: "listing" }); // WRONG - Don't pass string throw handleControllerError(error, "listing contributors"); // ❌ ``` 3. **MCP Tool Type Property**: ```typescript // CORRECT - Use "as const" return { content: [{ type: "text" as const, text: result.content }] }; // WRONG - Missing "as const" type: "text" // ❌ TypeScript will complain ``` 4. **Service Export Pattern**: ```typescript // CORRECT - Export as object export const contributorsService = { list: async (...) => { ... }, get: async (...) => { ... } }; // WRONG - Don't use default export export default { ... }; // ❌ ``` ### Step 6: Implement Additional Features 1. **Add domain-specific validations**: ```typescript // Validate language permissions if (contributor.languages?.some(lang => !lang.langIso)) { throw new McpError("Language ISO code is required", ErrorType.VALIDATION_ERROR); } ``` 2. **Handle bulk operations properly**: ```typescript // Check for partial failures in bulk operations if (result.errors && result.errors.length > 0) { const errorDetails = result.errors.map(e => e.message).join(", "); logger.warn("Some contributors failed to create", { errors: errorDetails }); } ``` 3. **Implement pagination helpers**: ```typescript // Add pagination info to formatted output if (result.hasNextPage()) { content += `\n\n*Page ${result.currentPage} of ${result.totalPages}*`; } ``` ## SDK Reference Guide ### Finding SDK Information 1. **Type Definitions**: `/node_modules/@lokalise/node-api/dist/main.d.ts` - Search for your domain interface (e.g., `interface Contributor`) - Look for related types (e.g., `NewContributor`, `UpdateContributor`) 2. **Collection Methods**: `/node_modules/@lokalise/node-api/dist/collections/<domain>.d.ts` - Shows available API methods - Parameter types and return types 3. **API Examples**: `/node_modules/@lokalise/node-api/docs/api/<domain>.md` - Working code examples - Common patterns and use cases ### SDK Patterns 1. **Pagination**: ```typescript // Standard pagination const result = await api.contributors().list({ project_id: projectId, page: 1, limit: 100 }); result.totalResults; // Total count result.hasNextPage(); // Check for more ``` 2. **Bulk Operations**: ```typescript // Create returns BulkResult with potential errors const result = await api.contributors().create(data, { project_id }); if (result.errors?.length > 0) { // Handle partial failures } ``` 3. **Project Context**: ```typescript // All methods require project_id in options { project_id: args.projectId } ``` ## Critical Implementation Requirements ### MCP Tool Schema Registration **REQUIREMENT**: Always use `.shape` property for Zod schemas: ```typescript // tool.ts implementation server.tool( "lokalise_<operation>", "Description...", SchemaName.shape, // MUST use .shape for ZodRawShape handler ); ``` **Why**: The MCP SDK expects `ZodRawShape`, not `ZodObject`. Using the full schema causes TypeScript compilation errors. ### Resource URI Parsing **REQUIREMENT**: Handle custom protocol URLs correctly: ```typescript // resource.ts implementation for lokalise://domain/id const pathParts = uri.pathname.split("/").filter(Boolean); const id = pathParts[0]; // NOT pathParts[1] // Custom protocols parse differently: // lokalise://projects/123 -> host="projects", pathname="/123" ``` ### Resource Response Format **REQUIREMENT**: Include all required fields: ```typescript return { contents: [{ uri: uri.toString(), // REQUIRED text: result.content, // REQUIRED mimeType: "text/markdown", // REQUIRED description: "..." // REQUIRED }] }; ``` ### Error Type Consistency **REQUIREMENT**: Use correct error types with prefixes: ```typescript // Validation errors MUST include prefix throw new McpError( "VALIDATION_ERROR: Field is required", ErrorType.VALIDATION_ERROR ); // API errors for external failures throw new McpError( "Failed to fetch from Lokalise API", ErrorType.API_ERROR ); ``` ### Number Parameter Parsing **REQUIREMENT**: Check for NaN explicitly: ```typescript // Parse URL parameters safely if (urlParams.get("limit")) { const parsed = Number.parseInt(urlParams.get("limit"), 10); limit = Number.isNaN(parsed) ? undefined : parsed; } ``` ## Common Pitfalls & Solutions ### 1. McpError Constructor Order **Problem**: `new McpError("CONFIG_ERROR", "message")` fails **Solution**: Use `new McpError("message", "CONFIG_ERROR")` - message first! ### 2. Missing McpError.fromError **Problem**: `McpError.fromError()` doesn't exist **Solution**: Use `createUnexpectedError(message, error)` ### 3. ControllerResponse Type Issues **Problem**: TypeScript errors about data/metadata properties **Solution**: Only return `{ content: string }` ### 4. handleControllerError Parameter **Problem**: Expects ErrorContext object, not string **Solution**: Pass object with source, entityType, entityId, operation ### 5. MCP Tool Registration Type Errors **Problem**: TypeScript overload errors **Solution**: Add `as const` to type property: `type: "text" as const` ### 6. Import Path Issues **Problem**: Can't find modules **Solution**: Always use `.js` extension in imports, even for TypeScript files ## Testing & Validation ### ⚠️ CRITICAL: Pre-Completion Checklist **NEVER mark a task as completed without running ALL these checks:** ### 1. Format and Lint Code ```bash # Run formatter first to fix any syntax issues npm run format # Should show "Formatted X files" with no errors # Run linter to check code quality npm run lint # Should show "Checked X files" with no issues # If linting shows errors, fix them with: npx biome check src/domains/<domain> --fix ``` ### 2. Build Verification ```bash npm run build # MUST compile without ANY errors # If build fails, fix all TypeScript errors before proceeding ``` ### 3. Type Checking ```bash npx tsc --noEmit # Should pass all type checks without errors ``` ### 4. Manual Testing ```bash # Test CLI commands npm run cli -- list-<domain> <project-id> # Verify command appears in help and executes properly # Test MCP tools npm run mcp:inspect # Use Inspector to test each tool ``` ### 5. Integration Testing ```bash # Test with real API (requires valid API key) LOKALISE_API_KEY=your-key npm test ``` ### 6. Final Verification Checklist - [ ] ✅ `npm run format` - No formatting issues - [ ] ✅ `npm run lint` - No linting errors - [ ] ✅ `npm run build` - Builds successfully - [ ] ✅ All TypeScript errors resolved - [ ] ✅ CLI commands appear in help menu - [ ] ✅ No unused imports or variables - [ ] ✅ All `any` types replaced with proper types **IMPORTANT**: If ANY of these checks fail, the implementation is NOT complete! ## Examples & Patterns ### Working Domain Examples 1. **Keys Domain** (`/src/domains/keys/`): - Complex filtering and cursor pagination - Bulk operations (create/update/delete) - Translation content handling 2. **Languages Domain** (`/src/domains/languages/`): - System languages vs project languages - Progress tracking - Plural forms configuration 3. **Projects Domain** (`/src/domains/projects/`): - Statistics integration - Nested resource handling - Settings management 4. **Tasks Domain** (`/src/domains/tasks/`): - Status workflows - User assignment - Due date handling ### Formatting Patterns ```typescript // List formatting with details export function formatContributorsList(result: PaginatedResult<Contributor>): string { let content = `# Contributors (${result.totalResults} total)\n\n`; result.items.forEach((contributor) => { content += `## ${contributor.fullname}\n`; content += `- **Email**: ${contributor.email}\n`; content += `- **Role**: ${contributor.is_admin ? "Admin" : contributor.is_reviewer ? "Reviewer" : "Translator"}\n`; // ... more fields }); return content; } ``` ### Error Context Patterns ```typescript // Consistent error context across operations const errorContext = { source: `${domainName}Controller.${methodName}`, entityType: domainName, entityId: args.projectId, // or complex: { project: id, item: id } operation: "listing" // listing, creating, updating, deleting, retrieving }; ``` ## Next Steps After Implementation 1. **Update Domain Index**: ```typescript // src/domains/<domain>/index.ts export { default as contributorsTool } from "./contributors.tool.js"; export { default as contributorsCli } from "./contributors.cli.js"; export { default as contributorsResource } from "./contributors.resource.js"; ``` 2. **Verify Auto-Discovery**: - Domain should automatically register without manual imports - Check server startup logs for registration confirmation 3. **Update Documentation**: - Add domain to README.md implementation status - Update CLAUDE.md with any new patterns - Document any special endpoints or behaviors 4. **Create Tests**: - Unit tests for service methods - Controller tests with mocked services - Integration tests for real API calls --- ## Quick Reference Checklist ### Implementation Steps - [ ] Run scaffolding script with correct operations - [ ] Study SDK types in `/node_modules/@lokalise/node-api/dist/main.d.ts` - [ ] Import SDK types in types file - [ ] Replace all REPLACE_ME placeholders in service - [ ] Fix controller to return only `{ content }` - [ ] Add `as const` to MCP tool type properties - [ ] Use correct error handling patterns - [ ] Update domain barrel exports - [ ] Document special endpoints or behaviors ### ⚠️ MANDATORY Pre-Completion Validation - [ ] ✅ Run `npm run format` - Must show no errors - [ ] ✅ Run `npm run lint` - Must show no issues - [ ] ✅ Run `npx biome check src/domains/<domain> --fix` - Fix all issues - [ ] ✅ Run `npm run build` - Must compile successfully - [ ] ✅ Verify no TypeScript errors remain - [ ] ✅ Test CLI command appears in help menu - [ ] ✅ Remove all `any` types - [ ] ✅ Remove all unused imports **🚫 DO NOT mark task as completed if ANY check fails!** Remember: The scaffolding script saves significant time and ensures consistency. Always start there and then customize based on the SDK documentation.

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