Skip to main content
Glama
strings.ts17.9 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { SmartlingClient } from '../smartling-client.js'; const createToolResponse = (content: any, isError: boolean = false, requestId: string) => { return { _meta: { requestId, timing: { duration: Date.now() }, source: 'smartling-api', version: '3.1.0' }, content: [ { type: 'text' as const, text: typeof content === 'string' ? content : JSON.stringify(content, null, 2), }, ], ...(isError && { isError: true }), }; }; export const addStringTools = (server: McpServer, client: SmartlingClient) => { server.tool( 'smartling_search_strings', 'Search strings by text with optional filters. If no specific fileUri provided, searches across all files in the project.', { projectId: z.string().describe('The project ID'), searchText: z.string().describe('Text to search for'), localeId: z.string().optional().describe('Optional locale filter'), fileUri: z.string().optional().describe('Optional: specific file URI to search in'), fileUris: z.array(z.string()).optional().describe('Optional: multiple fileUri filters'), includeTimestamps: z.boolean().optional().describe('Include timestamps'), limit: z.number().optional().describe('Pagination limit'), offset: z.number().optional().describe('Pagination offset'), maxFiles: z.number().optional().describe('Maximum number of files to search when searching across all files (default: all files)'), }, async ({ projectId, searchText, localeId, fileUri, fileUris, includeTimestamps, limit, offset, maxFiles }) => { try { const options: any = {}; if (localeId) options.localeId = localeId; if (fileUri) options.fileUri = fileUri; if (fileUris) options.fileUris = fileUris; if (includeTimestamps) options.includeTimestamps = includeTimestamps; if (limit) options.limit = limit; if (maxFiles) options.maxFiles = maxFiles; const result = await client.searchStrings(projectId, searchText, options); // Apply offset if provided if (offset && result.items && Array.isArray(result.items)) { result.items = result.items.slice(offset); } return createToolResponse(result, false, 'smartling-search-strings'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error searching strings: ${errorMessage}`, true, 'smartling-search-strings'); } } ); server.tool( 'smartling_get_string_details', 'Get string details by hashcode', { projectId: z.string().describe('The project ID'), hashcode: z.string().describe('The string hashcode'), }, async ({ projectId, hashcode }) => { try { const result = await client.getStringDetailsByHashcode(projectId, hashcode); return createToolResponse(result, false, 'smartling-string-details'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error getting string details: ${errorMessage}`, true, 'smartling-string-details'); } } ); server.tool( 'smartling_get_workflow_steps', 'Get workflow steps for a job and locale', { projectId: z.string().describe('The project ID'), jobId: z.string().describe('The job ID'), localeId: z.string().describe('The locale ID'), }, async ({ projectId, jobId, localeId }) => { try { const result = await client.getWorkflowSteps(projectId, jobId, localeId); return createToolResponse(result, false, 'smartling-workflow-steps'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error getting workflow steps: ${errorMessage}`, true, 'smartling-workflow-steps'); } } ); server.tool( 'smartling_assign_workflow_step', 'Assign a workflow step to a specific user', { projectId: z.string().describe('The project ID'), jobId: z.string().describe('The job ID'), stepUid: z.string().describe('The workflow step UID'), assigneeUid: z.string().describe('The UID of the user to assign the step to'), }, async ({ projectId, jobId, stepUid, assigneeUid }) => { try { await client.assignWorkflowStep(projectId, jobId, stepUid, assigneeUid); return createToolResponse({ success: true, message: `Successfully assigned workflow step ${stepUid} to user ${assigneeUid}`, projectId, jobId, stepUid, assigneeUid }, false, 'smartling-assign-workflow'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error assigning workflow step: ${errorMessage}`, true, 'smartling-assign-workflow'); } } ); server.tool( 'smartling_get_string_translations', 'Get translations for a specific string across multiple locales', { projectId: z.string().describe('The project ID'), hashcode: z.string().describe('The unique hashcode of the string'), localeIds: z.array(z.string()).optional().describe('Optional: specific locale IDs to get translations for'), }, async ({ projectId, hashcode, localeIds }) => { try { const results = []; if (localeIds && localeIds.length > 0) { // Get translations for specific locales for (const localeId of localeIds) { try { const translation = await client.getStringDetails(projectId, hashcode, localeId); results.push({ localeId, translation, status: 'success' }); } catch (error) { results.push({ localeId, error: error instanceof Error ? error.message : String(error), status: 'error' }); } } } else { // If no specific locales provided, get string from all available locales const projects = await client.getProjects(); const project = projects.find(p => p.projectId === projectId); if (project?.targetLocales) { for (const locale of project.targetLocales) { try { const translation = await client.getStringDetails(projectId, hashcode, locale.localeId); results.push({ localeId: locale.localeId, localeDescription: locale.description, translation, status: 'success' }); } catch (error) { results.push({ localeId: locale.localeId, localeDescription: locale.description, error: error instanceof Error ? error.message : String(error), status: 'error' }); } } } } return createToolResponse({ hashcode, projectId, translationsCount: results.filter(r => r.status === 'success').length, errorsCount: results.filter(r => r.status === 'error').length, translations: results }, false, 'smartling-string-translations'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error getting string translations: ${errorMessage}`, true, 'smartling-string-translations'); } } ); server.tool( 'smartling_search_strings_advanced', 'Advanced search for strings with multiple filters and sorting options. If no specific fileUri provided, searches across all files in the project.', { projectId: z.string().describe('The project ID'), searchText: z.string().describe('Text to search for in strings'), localeId: z.string().optional().describe('Optional: specific locale to search in'), fileUri: z.string().optional().describe('Optional: specific file URI to search in'), fileUris: z.array(z.string()).optional().describe('Optional: specific file URIs to search in'), tags: z.array(z.string()).optional().describe('Optional: filter by tags'), translationStatus: z.enum(['PENDING', 'IN_PROGRESS', 'COMPLETED', 'EXCLUDED']).optional().describe('Optional: filter by translation status'), includeTimestamps: z.boolean().optional().describe('Include timestamp information'), limit: z.number().optional().describe('Maximum number of results to return'), offset: z.number().optional().describe('Number of results to skip (for pagination)'), maxFiles: z.number().optional().describe('Maximum number of files to search when searching across all files (default: all files)'), }, async ({ projectId, searchText, localeId, fileUri, fileUris, tags, translationStatus, includeTimestamps, limit, offset, maxFiles }) => { try { // First, perform basic string search const searchOptions: any = {}; if (localeId) searchOptions.localeId = localeId; if (fileUri) searchOptions.fileUri = fileUri; if (fileUris) searchOptions.fileUris = fileUris; if (includeTimestamps) searchOptions.includeTimestamps = includeTimestamps; if (limit) searchOptions.limit = limit; if (maxFiles) searchOptions.maxFiles = maxFiles; let results = await client.searchStrings(projectId, searchText, searchOptions); // If tags filter is provided, also get strings by tags and intersect if (tags && tags.length > 0) { const taggedStrings = await client.getStringsByTag(projectId, tags, fileUris?.[0]); // Create a set of tagged string hashcodes for quick lookup const taggedHashcodes = new Set( Array.isArray(taggedStrings) ? taggedStrings.map((s: any) => s.hashcode) : [] ); // Filter search results to only include tagged strings if (Array.isArray(results.items)) { results.items = results.items.filter((item: any) => taggedHashcodes.has(item.hashcode)); } } // Apply offset for pagination if (offset && Array.isArray(results.items)) { results.items = results.items.slice(offset); } // Add metadata about the search const searchMetadata = { searchText, filters: { localeId, fileUris, tags, translationStatus, limit, offset }, resultCount: Array.isArray(results.items) ? results.items.length : 0, hasMoreResults: limit && Array.isArray(results.items) ? results.items.length === limit : false }; return createToolResponse({ searchMetadata, results }, false, 'smartling-advanced-search'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error in advanced string search: ${errorMessage}`, true, 'smartling-advanced-search'); } } ); server.tool( 'smartling_get_translation_progress', 'Get translation progress summary for a project or specific files', { projectId: z.string().describe('The project ID'), fileUris: z.array(z.string()).optional().describe('Optional: specific file URIs to check progress for'), localeIds: z.array(z.string()).optional().describe('Optional: specific locales to check progress for'), }, async ({ projectId, fileUris, localeIds }) => { try { const progressData = []; // Get project information to determine target locales const projects = await client.getProjects(); const project = projects.find(p => p.projectId === projectId); if (!project) { throw new Error(`Project ${projectId} not found`); } const targetLocales = localeIds && localeIds.length > 0 ? project.targetLocales?.filter(locale => localeIds.includes(locale.localeId)) || [] : project.targetLocales || []; // Get file status for each file and locale combination if (fileUris && fileUris.length > 0) { for (const fileUri of fileUris) { try { const fileStatus = await client.getFileStatus(projectId, fileUri); progressData.push({ fileUri, status: fileStatus, type: 'file_status' }); } catch (error) { progressData.push({ fileUri, error: error instanceof Error ? error.message : String(error), type: 'file_error' }); } } } // Get recently localized strings for progress insight for (const locale of targetLocales) { try { const options: { limit: number; fileUris?: string[] } = { limit: 100 }; if (fileUris && fileUris.length > 0) { options.fileUris = fileUris; } const recentlyLocalized = await client.getRecentlyLocalized( projectId, locale.localeId, options ); progressData.push({ localeId: locale.localeId, localeDescription: locale.description, recentlyLocalizedCount: Array.isArray(recentlyLocalized.items) ? recentlyLocalized.items.length : 0, recentlyLocalized: recentlyLocalized, type: 'locale_progress' }); } catch (error) { progressData.push({ localeId: locale.localeId, localeDescription: locale.description, error: error instanceof Error ? error.message : String(error), type: 'locale_error' }); } } return createToolResponse({ projectId, projectName: project.projectName, sourceLocale: project.sourceLocaleId, targetLocalesCount: targetLocales.length, filesChecked: fileUris?.length || 0, progressData }, false, 'smartling-translation-progress'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error getting translation progress: ${errorMessage}`, true, 'smartling-translation-progress'); } } ); server.tool( 'smartling_find_hashcodes_for_keys', 'Find hashcodes for a list of key names using exact Apps Script logic - searches across all files in the project', { projectId: z.string().describe('The project ID'), keyNames: z.array(z.string()).describe('Array of key names to search for (e.g., ["auto-renew-mobile.network_issue.toast", "auto-renew-mobile.cta"])'), }, async ({ projectId, keyNames }) => { try { // Get all project files first (like Apps Script) const projectFiles = await client.getProjectFiles(projectId); const fileUris = projectFiles.items ? projectFiles.items.map((file: any) => file.fileUri) : []; console.log(`Starting search for ${keyNames.length} keys across ${fileUris.length} files`); // Use exact Apps Script logic const result = await client.findHashcodesForKeys(keyNames, fileUris, projectId); const response = { totalKeysSearched: keyNames.length, foundKeys: result.hashcodesInfo.length, notFoundKeys: keyNames.filter(key => !result.processedOriginalKeys.includes(key)), hashcodesInfo: result.hashcodesInfo, processedOriginalKeys: result.processedOriginalKeys, filesProcessed: fileUris.length }; return createToolResponse(response, false, 'smartling-find-hashcodes'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error finding hashcodes: ${errorMessage}`, true, 'smartling-find-hashcodes'); } } ); server.tool( 'smartling_get_strings_by_translation_status', 'Get strings by translation status (e.g., PENDING, AWAITING_AUTHORIZATION)', { projectId: z.string().describe('The project ID'), translationStatus: z.string().describe('Translation status to filter by (PENDING, AWAITING_AUTHORIZATION, etc.)'), localeId: z.string().optional().describe('Optional: specific locale to check'), createdBefore: z.string().optional().describe('Optional: ISO date string to filter strings created before this date'), }, async ({ projectId, translationStatus, localeId, createdBefore }) => { try { const result = await client.getStringsByTranslationStatus( projectId, translationStatus, localeId, createdBefore ); return createToolResponse({ projectId, translationStatus, localeId, createdBefore, totalFound: result.length, strings: result }, false, 'smartling-strings-by-status'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createToolResponse(`Error getting strings by translation status: ${errorMessage}`, true, 'smartling-strings-by-status'); } } ); };

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/Jacobolevy/smartling-mcp-server'

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