Skip to main content
Glama

Weblate MCP Server

by mmntm
changes.tool.ts8.57 kB
import { Injectable, Logger } from '@nestjs/common'; import { Tool } from '@rekog/mcp-nest'; import { z } from 'zod'; import { WeblateApiService } from '../services'; import { type Change } from '../client'; @Injectable() export class WeblateChangesTool { private readonly logger = new Logger(WeblateChangesTool.name); constructor(private weblateApiService: WeblateApiService) {} @Tool({ name: 'listRecentChanges', description: 'List recent changes across all projects in Weblate', parameters: z.object({ limit: z.number().optional().describe('Number of changes to return (default: 20)').default(20), user: z.string().optional().describe('Filter by specific user'), timestampAfter: z.string().optional().describe('Show changes after this timestamp (ISO format)'), timestampBefore: z.string().optional().describe('Show changes before this timestamp (ISO format)'), }), }) async listRecentChanges({ limit = 20, user, timestampAfter, timestampBefore, }: { limit?: number; user?: string; timestampAfter?: string; timestampBefore?: string; }) { try { const result = await this.weblateApiService.listRecentChanges( limit, user, timestampAfter, timestampBefore, ); if (result.results.length === 0) { return { content: [ { type: 'text', text: 'No recent changes found.', }, ], }; } const changesList = result.results .slice(0, limit) .map(change => this.formatChangeResult(change)) .join('\n\n---\n\n'); return { content: [ { type: 'text', text: `Found ${result.count} recent changes (showing ${Math.min(limit, result.results.length)}):\n\n${changesList}`, }, ], }; } catch (error) { this.logger.error('Failed to list recent changes', error); return { content: [ { type: 'text', text: `Error listing recent changes: ${error.message}`, }, ], isError: true, }; } } @Tool({ name: 'getProjectChanges', description: 'Get recent changes for a specific project', parameters: z.object({ projectSlug: z.string().describe('The slug of the project'), }), }) async getProjectChanges({ projectSlug }: { projectSlug: string }) { try { const result = await this.weblateApiService.getProjectChanges(projectSlug); if (result.results.length === 0) { return { content: [ { type: 'text', text: `No changes found for project "${projectSlug}".`, }, ], }; } const changesList = result.results .slice(0, 20) .map(change => this.formatChangeResult(change)) .join('\n\n---\n\n'); return { content: [ { type: 'text', text: `Recent changes in project "${projectSlug}" (${result.count} total):\n\n${changesList}`, }, ], }; } catch (error) { this.logger.error(`Failed to get changes for project ${projectSlug}`, error); return { content: [ { type: 'text', text: `Error getting changes for project "${projectSlug}": ${error.message}`, }, ], isError: true, }; } } @Tool({ name: 'getComponentChanges', description: 'Get recent changes for a specific component', parameters: z.object({ projectSlug: z.string().describe('The slug of the project'), componentSlug: z.string().describe('The slug of the component'), }), }) async getComponentChanges({ projectSlug, componentSlug, }: { projectSlug: string; componentSlug: string; }) { try { const result = await this.weblateApiService.getComponentChanges( projectSlug, componentSlug, ); if (result.results.length === 0) { return { content: [ { type: 'text', text: `No changes found for component "${componentSlug}" in project "${projectSlug}".`, }, ], }; } const changesList = result.results .slice(0, 20) .map(change => this.formatChangeResult(change)) .join('\n\n---\n\n'); return { content: [ { type: 'text', text: `Recent changes in component "${componentSlug}" (${result.count} total):\n\n${changesList}`, }, ], }; } catch (error) { this.logger.error( `Failed to get changes for component ${componentSlug} in project ${projectSlug}`, error, ); return { content: [ { type: 'text', text: `Error getting changes for component "${componentSlug}": ${error.message}`, }, ], isError: true, }; } } @Tool({ name: 'getChangesByUser', description: 'Get recent changes by a specific user', parameters: z.object({ user: z.string().describe('Username to filter by'), limit: z.number().optional().describe('Number of changes to return (default: 20)').default(20), }), }) async getChangesByUser({ user, limit = 20, }: { user: string; limit?: number; }) { try { const result = await this.weblateApiService.getChangesByUser(user, limit); if (result.results.length === 0) { return { content: [ { type: 'text', text: `No changes found for user "${user}".`, }, ], }; } const changesList = result.results .slice(0, limit) .map(change => this.formatChangeResult(change)) .join('\n\n---\n\n'); return { content: [ { type: 'text', text: `Recent changes by user "${user}" (${result.count} total):\n\n${changesList}`, }, ], }; } catch (error) { this.logger.error(`Failed to get changes by user ${user}`, error); return { content: [ { type: 'text', text: `Error getting changes by user "${user}": ${error.message}`, }, ], isError: true, }; } } private formatChangeResult(change: Change): string { const timestamp = change.timestamp ? new Date(change.timestamp).toLocaleString() : 'Unknown'; const actionDescription = this.getActionDescription(change.action || 0); const user = change.user || 'Unknown user'; const target = change.target || 'N/A'; return `**${actionDescription}**\n**User:** ${user}\n**Time:** ${timestamp}\n**Target:** ${target}`; } private getActionDescription(action: number): string { const actionMap: Record<number, string> = { 0: 'Resource updated', 1: 'Translation completed', 2: 'Translation changed', 3: 'Comment added', 4: 'Suggestion added', 5: 'Translation added', 6: 'Automatically translated', 7: 'Suggestion accepted', 8: 'Translation reverted', 9: 'Translation uploaded', 13: 'Source string added', 14: 'Component locked', 15: 'Component unlocked', 17: 'Changes committed', 18: 'Changes pushed', 19: 'Repository reset', 20: 'Repository merged', 21: 'Repository rebased', 22: 'Repository merge failed', 23: 'Repository rebase failed', 24: 'Parsing failed', 25: 'Translation removed', 26: 'Suggestion removed', 27: 'Translation replaced', 28: 'Repository push failed', 29: 'Suggestion removed during clean-up', 30: 'Source string changed', 31: 'String added', 32: 'Bulk status changed', 33: 'Visibility changed', 34: 'User added', 35: 'User removed', 36: 'Translation approved', 37: 'Marked for edit', 38: 'Component removed', 39: 'Project removed', 41: 'Project renamed', 42: 'Component renamed', 43: 'Moved component', 45: 'Contributor joined', 46: 'Announcement posted', 47: 'Alert triggered', 48: 'Language added', 49: 'Language requested', 50: 'Project created', 51: 'Component created', 52: 'User invited', }; return actionMap[action] || `Unknown action (${action})`; } }

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/mmntm/weblate-mcp'

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