@langapi/mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +551 -0
  2. package/bin/langapi-mcp.js +11 -0
  3. package/dist/api/client.d.ts +24 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +145 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/api/types.d.ts +56 -0
  8. package/dist/api/types.d.ts.map +1 -0
  9. package/dist/api/types.js +6 -0
  10. package/dist/api/types.js.map +1 -0
  11. package/dist/config/env.d.ts +18 -0
  12. package/dist/config/env.d.ts.map +1 -0
  13. package/dist/config/env.js +29 -0
  14. package/dist/config/env.js.map +1 -0
  15. package/dist/index.d.ts +15 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +25 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/locale-detection/index.d.ts +41 -0
  20. package/dist/locale-detection/index.d.ts.map +1 -0
  21. package/dist/locale-detection/index.js +188 -0
  22. package/dist/locale-detection/index.js.map +1 -0
  23. package/dist/locale-detection/patterns.d.ts +21 -0
  24. package/dist/locale-detection/patterns.d.ts.map +1 -0
  25. package/dist/locale-detection/patterns.js +135 -0
  26. package/dist/locale-detection/patterns.js.map +1 -0
  27. package/dist/server.d.ts +9 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +24 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/tools/get-diff.d.ts +23 -0
  32. package/dist/tools/get-diff.d.ts.map +1 -0
  33. package/dist/tools/get-diff.js +88 -0
  34. package/dist/tools/get-diff.js.map +1 -0
  35. package/dist/tools/get-translation-status.d.ts +47 -0
  36. package/dist/tools/get-translation-status.d.ts.map +1 -0
  37. package/dist/tools/get-translation-status.js +176 -0
  38. package/dist/tools/get-translation-status.js.map +1 -0
  39. package/dist/tools/list-local-locales.d.ts +39 -0
  40. package/dist/tools/list-local-locales.d.ts.map +1 -0
  41. package/dist/tools/list-local-locales.js +52 -0
  42. package/dist/tools/list-local-locales.js.map +1 -0
  43. package/dist/tools/sync-translations.d.ts +35 -0
  44. package/dist/tools/sync-translations.d.ts.map +1 -0
  45. package/dist/tools/sync-translations.js +629 -0
  46. package/dist/tools/sync-translations.js.map +1 -0
  47. package/dist/utils/format-preserve.d.ts +25 -0
  48. package/dist/utils/format-preserve.d.ts.map +1 -0
  49. package/dist/utils/format-preserve.js +56 -0
  50. package/dist/utils/format-preserve.js.map +1 -0
  51. package/dist/utils/json-parser.d.ts +33 -0
  52. package/dist/utils/json-parser.d.ts.map +1 -0
  53. package/dist/utils/json-parser.js +92 -0
  54. package/dist/utils/json-parser.js.map +1 -0
  55. package/dist/utils/sync-cache.d.ts +40 -0
  56. package/dist/utils/sync-cache.d.ts.map +1 -0
  57. package/dist/utils/sync-cache.js +153 -0
  58. package/dist/utils/sync-cache.js.map +1 -0
  59. package/dist/utils/sync-cache.test.d.ts +2 -0
  60. package/dist/utils/sync-cache.test.d.ts.map +1 -0
  61. package/dist/utils/sync-cache.test.js +205 -0
  62. package/dist/utils/sync-cache.test.js.map +1 -0
  63. package/dist/utils/validation.d.ts +37 -0
  64. package/dist/utils/validation.d.ts.map +1 -0
  65. package/dist/utils/validation.js +67 -0
  66. package/dist/utils/validation.js.map +1 -0
  67. package/package.json +56 -0
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdir, rm, readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { detectLocalDelta, getFullDiff, readSyncCache, writeSyncCache, } from "./sync-cache.js";
6
+ describe("detectLocalDelta", () => {
7
+ it("should mark all keys as new when cache is null", () => {
8
+ const currentContent = [
9
+ { key: "hello", value: "Hello" },
10
+ { key: "world", value: "World" },
11
+ ];
12
+ const result = detectLocalDelta(currentContent, null);
13
+ expect(result.newKeys).toEqual(["hello", "world"]);
14
+ expect(result.changedKeys).toEqual([]);
15
+ expect(result.unchangedKeys).toEqual([]);
16
+ expect(result.contentToSync).toEqual(currentContent);
17
+ });
18
+ it("should return empty arrays when current content is empty", () => {
19
+ const cachedContent = { hello: "Hello", world: "World" };
20
+ const result = detectLocalDelta([], cachedContent);
21
+ expect(result.newKeys).toEqual([]);
22
+ expect(result.changedKeys).toEqual([]);
23
+ expect(result.unchangedKeys).toEqual([]);
24
+ expect(result.contentToSync).toEqual([]);
25
+ });
26
+ it("should detect all new keys when none exist in cache", () => {
27
+ const currentContent = [
28
+ { key: "new1", value: "New 1" },
29
+ { key: "new2", value: "New 2" },
30
+ ];
31
+ const cachedContent = { existing: "Existing" };
32
+ const result = detectLocalDelta(currentContent, cachedContent);
33
+ expect(result.newKeys).toEqual(["new1", "new2"]);
34
+ expect(result.changedKeys).toEqual([]);
35
+ expect(result.unchangedKeys).toEqual([]);
36
+ expect(result.contentToSync).toHaveLength(2);
37
+ });
38
+ it("should detect all unchanged keys when values match", () => {
39
+ const currentContent = [
40
+ { key: "hello", value: "Hello" },
41
+ { key: "world", value: "World" },
42
+ ];
43
+ const cachedContent = { hello: "Hello", world: "World" };
44
+ const result = detectLocalDelta(currentContent, cachedContent);
45
+ expect(result.newKeys).toEqual([]);
46
+ expect(result.changedKeys).toEqual([]);
47
+ expect(result.unchangedKeys).toEqual(["hello", "world"]);
48
+ expect(result.contentToSync).toEqual([]);
49
+ });
50
+ it("should detect changed keys when values differ", () => {
51
+ const currentContent = [
52
+ { key: "hello", value: "Hello there" },
53
+ { key: "world", value: "World" },
54
+ ];
55
+ const cachedContent = { hello: "Hello", world: "World" };
56
+ const result = detectLocalDelta(currentContent, cachedContent);
57
+ expect(result.newKeys).toEqual([]);
58
+ expect(result.changedKeys).toEqual(["hello"]);
59
+ expect(result.unchangedKeys).toEqual(["world"]);
60
+ expect(result.contentToSync).toHaveLength(1);
61
+ expect(result.contentToSync[0]).toEqual({ key: "hello", value: "Hello there" });
62
+ });
63
+ it("should handle mixed new, changed, and unchanged keys", () => {
64
+ const currentContent = [
65
+ { key: "new", value: "New key" },
66
+ { key: "changed", value: "Changed value" },
67
+ { key: "unchanged", value: "Same value" },
68
+ ];
69
+ const cachedContent = {
70
+ changed: "Original value",
71
+ unchanged: "Same value",
72
+ removed: "This will be ignored by detectLocalDelta",
73
+ };
74
+ const result = detectLocalDelta(currentContent, cachedContent);
75
+ expect(result.newKeys).toEqual(["new"]);
76
+ expect(result.changedKeys).toEqual(["changed"]);
77
+ expect(result.unchangedKeys).toEqual(["unchanged"]);
78
+ expect(result.contentToSync).toHaveLength(2);
79
+ });
80
+ });
81
+ describe("getFullDiff", () => {
82
+ it("should mark all keys as new when cache is null", () => {
83
+ const currentContent = [
84
+ { key: "hello", value: "Hello" },
85
+ { key: "world", value: "World" },
86
+ ];
87
+ const result = getFullDiff(currentContent, null);
88
+ expect(result.newKeys).toEqual(["hello", "world"]);
89
+ expect(result.changedKeys).toEqual([]);
90
+ expect(result.unchangedKeys).toEqual([]);
91
+ expect(result.removedKeys).toEqual([]);
92
+ });
93
+ it("should detect removed keys", () => {
94
+ const currentContent = [
95
+ { key: "remaining", value: "Still here" },
96
+ ];
97
+ const cachedContent = {
98
+ remaining: "Still here",
99
+ removed1: "Gone",
100
+ removed2: "Also gone",
101
+ };
102
+ const result = getFullDiff(currentContent, cachedContent);
103
+ expect(result.newKeys).toEqual([]);
104
+ expect(result.changedKeys).toEqual([]);
105
+ expect(result.unchangedKeys).toEqual(["remaining"]);
106
+ expect(result.removedKeys).toEqual(["removed1", "removed2"]);
107
+ });
108
+ it("should include old and new values for changed keys", () => {
109
+ const currentContent = [
110
+ { key: "greeting", value: "Welcome" },
111
+ ];
112
+ const cachedContent = { greeting: "Hello there" };
113
+ const result = getFullDiff(currentContent, cachedContent);
114
+ expect(result.changedKeys).toHaveLength(1);
115
+ expect(result.changedKeys[0]).toEqual({
116
+ key: "greeting",
117
+ oldValue: "Hello there",
118
+ newValue: "Welcome",
119
+ });
120
+ });
121
+ it("should handle mixed scenario with all change types", () => {
122
+ const currentContent = [
123
+ { key: "new", value: "Brand new" },
124
+ { key: "changed", value: "Updated value" },
125
+ { key: "unchanged", value: "Same" },
126
+ ];
127
+ const cachedContent = {
128
+ changed: "Old value",
129
+ unchanged: "Same",
130
+ removed: "Will be detected as removed",
131
+ };
132
+ const result = getFullDiff(currentContent, cachedContent);
133
+ expect(result.newKeys).toEqual(["new"]);
134
+ expect(result.changedKeys).toEqual([
135
+ { key: "changed", oldValue: "Old value", newValue: "Updated value" },
136
+ ]);
137
+ expect(result.unchangedKeys).toEqual(["unchanged"]);
138
+ expect(result.removedKeys).toEqual(["removed"]);
139
+ });
140
+ it("should handle empty current content with cached content", () => {
141
+ const cachedContent = { key1: "Value 1", key2: "Value 2" };
142
+ const result = getFullDiff([], cachedContent);
143
+ expect(result.newKeys).toEqual([]);
144
+ expect(result.changedKeys).toEqual([]);
145
+ expect(result.unchangedKeys).toEqual([]);
146
+ expect(result.removedKeys).toEqual(["key1", "key2"]);
147
+ });
148
+ });
149
+ describe("readSyncCache / writeSyncCache", () => {
150
+ let testDir;
151
+ beforeEach(async () => {
152
+ testDir = join(tmpdir(), `sync-cache-test-${Date.now()}`);
153
+ await mkdir(testDir, { recursive: true });
154
+ });
155
+ afterEach(async () => {
156
+ try {
157
+ await rm(testDir, { recursive: true, force: true });
158
+ }
159
+ catch {
160
+ // Ignore cleanup errors
161
+ }
162
+ });
163
+ it("should write and read back same content", async () => {
164
+ const content = [
165
+ { key: "hello", value: "Hello" },
166
+ { key: "world", value: "World" },
167
+ ];
168
+ await writeSyncCache(testDir, "en", content);
169
+ const result = await readSyncCache(testDir, "en");
170
+ expect(result).toEqual({
171
+ hello: "Hello",
172
+ world: "World",
173
+ });
174
+ });
175
+ it("should return null when cache file does not exist", async () => {
176
+ const result = await readSyncCache(testDir, "en");
177
+ expect(result).toBeNull();
178
+ });
179
+ it("should return null when source language does not match", async () => {
180
+ const content = [{ key: "hello", value: "Hello" }];
181
+ await writeSyncCache(testDir, "en", content);
182
+ const result = await readSyncCache(testDir, "de");
183
+ expect(result).toBeNull();
184
+ });
185
+ it("should create cache directory if it does not exist", async () => {
186
+ const nestedDir = join(testDir, "nested", "path");
187
+ const content = [{ key: "test", value: "Test" }];
188
+ await writeSyncCache(nestedDir, "en", content);
189
+ const cacheFile = join(nestedDir, ".langapi", "sync-cache.json");
190
+ const fileContent = await readFile(cacheFile, "utf-8");
191
+ const cache = JSON.parse(fileContent);
192
+ expect(cache.sourceLang).toBe("en");
193
+ expect(cache.content).toEqual({ test: "Test" });
194
+ expect(cache.lastSync).toBeDefined();
195
+ });
196
+ it("should overwrite existing cache on subsequent writes", async () => {
197
+ const content1 = [{ key: "old", value: "Old value" }];
198
+ const content2 = [{ key: "new", value: "New value" }];
199
+ await writeSyncCache(testDir, "en", content1);
200
+ await writeSyncCache(testDir, "en", content2);
201
+ const result = await readSyncCache(testDir, "en");
202
+ expect(result).toEqual({ new: "New value" });
203
+ });
204
+ });
205
+ //# sourceMappingURL=sync-cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-cache.test.js","sourceRoot":"","sources":["../../src/utils/sync-cache.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Validation utilities for input sanitization
3
+ */
4
+ import { z } from "zod";
5
+ /**
6
+ * RFC 5646 language code pattern
7
+ * Matches: en, de, fr, pt-BR, zh-CN, etc.
8
+ */
9
+ export declare const LANGUAGE_CODE_PATTERN: RegExp;
10
+ /**
11
+ * Zod schema for language code validation
12
+ */
13
+ export declare const languageCodeSchema: z.ZodString;
14
+ /**
15
+ * Zod schema for array of language codes
16
+ */
17
+ export declare const languageCodesArraySchema: z.ZodArray<z.ZodString, "many">;
18
+ /**
19
+ * Validate a language code
20
+ */
21
+ export declare function isValidLanguageCode(code: string): boolean;
22
+ /**
23
+ * Validate that a path is within the project directory
24
+ * Prevents path traversal attacks
25
+ */
26
+ export declare function isPathWithinProject(filePath: string, projectPath: string): boolean;
27
+ /**
28
+ * Get a safe file path within the project directory
29
+ * Returns null if path would escape project directory
30
+ */
31
+ export declare function getSafeFilePath(relativePath: string, projectPath: string): string | null;
32
+ /**
33
+ * Sanitize a language code by ensuring it matches the expected pattern
34
+ * Returns the sanitized code or null if invalid
35
+ */
36
+ export declare function sanitizeLanguageCode(code: string): string | null;
37
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAA6B,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,kBAAkB,aAO3B,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,wBAAwB,iCAE6B,CAAC;AAEnE;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAUf;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Validation utilities for input sanitization
3
+ */
4
+ import { z } from "zod";
5
+ import { resolve } from "path";
6
+ /**
7
+ * RFC 5646 language code pattern
8
+ * Matches: en, de, fr, pt-BR, zh-CN, etc.
9
+ */
10
+ export const LANGUAGE_CODE_PATTERN = /^[a-z]{2,3}(-[A-Z]{2})?$/;
11
+ /**
12
+ * Zod schema for language code validation
13
+ */
14
+ export const languageCodeSchema = z
15
+ .string()
16
+ .min(2)
17
+ .max(6)
18
+ .regex(LANGUAGE_CODE_PATTERN, {
19
+ message: "Invalid language code. Expected format: 'en', 'de', 'pt-BR', etc.",
20
+ });
21
+ /**
22
+ * Zod schema for array of language codes
23
+ */
24
+ export const languageCodesArraySchema = z
25
+ .array(languageCodeSchema)
26
+ .min(1, { message: "At least one target language is required" });
27
+ /**
28
+ * Validate a language code
29
+ */
30
+ export function isValidLanguageCode(code) {
31
+ return LANGUAGE_CODE_PATTERN.test(code);
32
+ }
33
+ /**
34
+ * Validate that a path is within the project directory
35
+ * Prevents path traversal attacks
36
+ */
37
+ export function isPathWithinProject(filePath, projectPath) {
38
+ const resolvedProject = resolve(projectPath);
39
+ const resolvedFile = resolve(projectPath, filePath);
40
+ // Check that resolved file path starts with project path
41
+ return resolvedFile.startsWith(resolvedProject);
42
+ }
43
+ /**
44
+ * Get a safe file path within the project directory
45
+ * Returns null if path would escape project directory
46
+ */
47
+ export function getSafeFilePath(relativePath, projectPath) {
48
+ const resolvedProject = resolve(projectPath);
49
+ const resolvedFile = resolve(projectPath, relativePath);
50
+ // Check that resolved file path starts with project path
51
+ if (!resolvedFile.startsWith(resolvedProject)) {
52
+ return null;
53
+ }
54
+ return resolvedFile;
55
+ }
56
+ /**
57
+ * Sanitize a language code by ensuring it matches the expected pattern
58
+ * Returns the sanitized code or null if invalid
59
+ */
60
+ export function sanitizeLanguageCode(code) {
61
+ const trimmed = code.trim();
62
+ if (isValidLanguageCode(trimmed)) {
63
+ return trimmed;
64
+ }
65
+ return null;
66
+ }
67
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAwB,MAAM,MAAM,CAAC;AAErD;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAEhE;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,CAAC,CAAC;KACN,KAAK,CAAC,qBAAqB,EAAE;IAC5B,OAAO,EACL,mEAAmE;CACtE,CAAC,CAAC;AAEL;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC;KACtC,KAAK,CAAC,kBAAkB,CAAC;KACzB,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,WAAmB;IAEnB,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAEpD,yDAAyD;IACzD,OAAO,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,WAAmB;IAEnB,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAExD,yDAAyD;IACzD,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@langapi/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for LangAPI - AI-powered translation management for i18n projects",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "langapi-mcp": "./bin/langapi-mcp.js",
10
+ "@langapi/mcp-server": "./bin/langapi-mcp.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "bin"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsx watch src/index.ts",
19
+ "start": "node dist/index.js",
20
+ "lint": "eslint src/**/*.ts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "i18n",
29
+ "internationalization",
30
+ "translation",
31
+ "langapi",
32
+ "ai",
33
+ "claude",
34
+ "cursor"
35
+ ],
36
+ "author": "LangAPI",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/TedyHub/-langapi-mcp-server"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/sdk": "^1.0.0",
44
+ "glob": "^11.0.0",
45
+ "zod": "^3.24.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.0.0",
49
+ "tsx": "^4.19.0",
50
+ "typescript": "^5.6.0",
51
+ "vitest": "^4.0.16"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ }
56
+ }