@translateimage/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 (82) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +533 -0
  3. package/dist/bin/http.d.ts +3 -0
  4. package/dist/bin/http.d.ts.map +1 -0
  5. package/dist/bin/http.js +51 -0
  6. package/dist/bin/stdio.d.ts +3 -0
  7. package/dist/bin/stdio.d.ts.map +1 -0
  8. package/dist/bin/stdio.js +14 -0
  9. package/dist/src/index.d.ts +5 -0
  10. package/dist/src/index.d.ts.map +1 -0
  11. package/dist/src/index.js +3 -0
  12. package/dist/src/schemas/common.d.ts +40 -0
  13. package/dist/src/schemas/common.d.ts.map +1 -0
  14. package/dist/src/schemas/common.js +31 -0
  15. package/dist/src/schemas/image-to-text.d.ts +69 -0
  16. package/dist/src/schemas/image-to-text.d.ts.map +1 -0
  17. package/dist/src/schemas/image-to-text.js +26 -0
  18. package/dist/src/schemas/index.d.ts +7 -0
  19. package/dist/src/schemas/index.d.ts.map +1 -0
  20. package/dist/src/schemas/index.js +6 -0
  21. package/dist/src/schemas/ocr.d.ts +333 -0
  22. package/dist/src/schemas/ocr.d.ts.map +1 -0
  23. package/dist/src/schemas/ocr.js +46 -0
  24. package/dist/src/schemas/shopify.d.ts +860 -0
  25. package/dist/src/schemas/shopify.d.ts.map +1 -0
  26. package/dist/src/schemas/shopify.js +183 -0
  27. package/dist/src/schemas/text-removal.d.ts +60 -0
  28. package/dist/src/schemas/text-removal.d.ts.map +1 -0
  29. package/dist/src/schemas/text-removal.js +16 -0
  30. package/dist/src/schemas/translate.d.ts +197 -0
  31. package/dist/src/schemas/translate.d.ts.map +1 -0
  32. package/dist/src/schemas/translate.js +70 -0
  33. package/dist/src/server.d.ts +4 -0
  34. package/dist/src/server.d.ts.map +1 -0
  35. package/dist/src/server.js +7 -0
  36. package/dist/src/tools/image-to-text.d.ts +4 -0
  37. package/dist/src/tools/image-to-text.d.ts.map +1 -0
  38. package/dist/src/tools/image-to-text.js +12 -0
  39. package/dist/src/tools/index.d.ts +8 -0
  40. package/dist/src/tools/index.d.ts.map +1 -0
  41. package/dist/src/tools/index.js +202 -0
  42. package/dist/src/tools/ocr.d.ts +4 -0
  43. package/dist/src/tools/ocr.d.ts.map +1 -0
  44. package/dist/src/tools/ocr.js +28 -0
  45. package/dist/src/tools/shopify/batch-translate.d.ts +26 -0
  46. package/dist/src/tools/shopify/batch-translate.d.ts.map +1 -0
  47. package/dist/src/tools/shopify/batch-translate.js +143 -0
  48. package/dist/src/tools/shopify/index.d.ts +19 -0
  49. package/dist/src/tools/shopify/index.d.ts.map +1 -0
  50. package/dist/src/tools/shopify/index.js +28 -0
  51. package/dist/src/tools/shopify/scan-products.d.ts +38 -0
  52. package/dist/src/tools/shopify/scan-products.d.ts.map +1 -0
  53. package/dist/src/tools/shopify/scan-products.js +178 -0
  54. package/dist/src/tools/shopify/shop-stats.d.ts +12 -0
  55. package/dist/src/tools/shopify/shop-stats.d.ts.map +1 -0
  56. package/dist/src/tools/shopify/shop-stats.js +89 -0
  57. package/dist/src/tools/shopify/translate-product.d.ts +19 -0
  58. package/dist/src/tools/shopify/translate-product.d.ts.map +1 -0
  59. package/dist/src/tools/shopify/translate-product.js +121 -0
  60. package/dist/src/tools/text-removal.d.ts +4 -0
  61. package/dist/src/tools/text-removal.d.ts.map +1 -0
  62. package/dist/src/tools/text-removal.js +10 -0
  63. package/dist/src/tools/translate.d.ts +4 -0
  64. package/dist/src/tools/translate.d.ts.map +1 -0
  65. package/dist/src/tools/translate.js +16 -0
  66. package/dist/src/utils/api-client.d.ts +46 -0
  67. package/dist/src/utils/api-client.d.ts.map +1 -0
  68. package/dist/src/utils/api-client.js +124 -0
  69. package/dist/src/utils/config.d.ts +7 -0
  70. package/dist/src/utils/config.d.ts.map +1 -0
  71. package/dist/src/utils/config.js +11 -0
  72. package/dist/src/utils/errors.d.ts +17 -0
  73. package/dist/src/utils/errors.d.ts.map +1 -0
  74. package/dist/src/utils/errors.js +35 -0
  75. package/dist/src/utils/image.d.ts +34 -0
  76. package/dist/src/utils/image.d.ts.map +1 -0
  77. package/dist/src/utils/image.js +105 -0
  78. package/dist/src/utils/index.d.ts +5 -0
  79. package/dist/src/utils/index.d.ts.map +1 -0
  80. package/dist/src/utils/index.js +4 -0
  81. package/dist/tsconfig.build.tsbuildinfo +1 -0
  82. package/package.json +63 -0
@@ -0,0 +1,178 @@
1
+ import { InvalidInputError } from "../../utils/errors.js";
2
+ import { TranslateImageApiClient } from "../../utils/api-client.js";
3
+ const MAX_PRODUCTS = 50;
4
+ const DEFAULT_MAX_PRODUCTS = 10;
5
+ const MAX_IMAGES_PER_CALL = 100;
6
+ const MAX_IMAGES_PER_PRODUCT = 50;
7
+ async function shopifyGraphQL(shopDomain, accessToken, query, variables) {
8
+ const response = await fetch(`https://${shopDomain}/admin/api/2024-01/graphql.json`, {
9
+ method: "POST",
10
+ headers: {
11
+ "Content-Type": "application/json",
12
+ "X-Shopify-Access-Token": accessToken,
13
+ },
14
+ body: JSON.stringify({ query, variables }),
15
+ });
16
+ if (!response.ok) {
17
+ throw new Error(`Shopify API error: ${response.statusText}`);
18
+ }
19
+ const data = await response.json();
20
+ if (data.errors) {
21
+ throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
22
+ }
23
+ return data.data;
24
+ }
25
+ const PRODUCT_BY_ID_QUERY = `
26
+ query getProduct($id: ID!) {
27
+ product(id: $id) {
28
+ id
29
+ handle
30
+ title
31
+ media(first: 50) {
32
+ edges {
33
+ node {
34
+ ... on MediaImage {
35
+ id
36
+ __typename
37
+ image {
38
+ url
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ `;
47
+ const PRODUCTS_QUERY = `
48
+ query getProducts($first: Int!) {
49
+ products(first: $first) {
50
+ edges {
51
+ node {
52
+ id
53
+ handle
54
+ title
55
+ media(first: 50) {
56
+ edges {
57
+ node {
58
+ ... on MediaImage {
59
+ id
60
+ __typename
61
+ image {
62
+ url
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ `;
73
+ async function fetchImageAsBlob(imageUrl) {
74
+ const response = await fetch(imageUrl);
75
+ if (!response.ok) {
76
+ throw new Error(`Failed to fetch image: ${response.status}`);
77
+ }
78
+ return response.blob();
79
+ }
80
+ function parseOcrResponse(ocrResult) {
81
+ const regions = ocrResult.regions.map((region) => ({
82
+ bounds: region.bounds,
83
+ text: Object.values(region.languages)[0] || "",
84
+ language: Object.keys(region.languages)[0] || "unknown",
85
+ confidence: region.probability,
86
+ }));
87
+ const avgConfidence = regions.length > 0
88
+ ? regions.reduce((sum, r) => sum + r.confidence, 0) / regions.length
89
+ : 0;
90
+ return {
91
+ hasText: regions.length > 0 && ocrResult.text.trim().length > 0,
92
+ regions,
93
+ confidence: avgConfidence,
94
+ };
95
+ }
96
+ export async function handleScanProducts(input, config) {
97
+ const { shopDomain, accessToken } = input.shopifyCredentials;
98
+ const apiClient = new TranslateImageApiClient(config);
99
+ let products = [];
100
+ if (input.productId) {
101
+ const productData = await shopifyGraphQL(shopDomain, accessToken, PRODUCT_BY_ID_QUERY, { id: input.productId });
102
+ if (!productData.product) {
103
+ throw new InvalidInputError(`Product not found: ${input.productId}`);
104
+ }
105
+ products = [productData.product];
106
+ }
107
+ else {
108
+ const maxProducts = Math.min(input.maxProducts ?? DEFAULT_MAX_PRODUCTS, MAX_PRODUCTS);
109
+ const productsData = await shopifyGraphQL(shopDomain, accessToken, PRODUCTS_QUERY, { first: maxProducts });
110
+ products = productsData.products.edges.map((e) => e.node);
111
+ }
112
+ const results = [];
113
+ let totalImagesScanned = 0;
114
+ let totalImagesWithText = 0;
115
+ let stoppedReason;
116
+ for (const product of products) {
117
+ const mediaImages = product.media.edges
118
+ .filter((edge) => edge.node.__typename === "MediaImage" && edge.node.image?.url)
119
+ .slice(0, MAX_IMAGES_PER_PRODUCT)
120
+ .map((edge) => ({
121
+ mediaId: edge.node.id,
122
+ url: edge.node.image.url,
123
+ }));
124
+ const imageScanResults = [];
125
+ let productImagesWithText = 0;
126
+ for (const media of mediaImages) {
127
+ if (totalImagesScanned >= MAX_IMAGES_PER_CALL) {
128
+ stoppedReason = `Max images per call limit reached (${MAX_IMAGES_PER_CALL})`;
129
+ break;
130
+ }
131
+ try {
132
+ const imageBlob = await fetchImageAsBlob(media.url);
133
+ const ocrResponse = await apiClient.ocr(imageBlob);
134
+ const parsed = parseOcrResponse(ocrResponse);
135
+ imageScanResults.push({
136
+ mediaId: media.mediaId,
137
+ imageUrl: media.url,
138
+ hasText: parsed.hasText,
139
+ confidence: parsed.confidence,
140
+ textRegions: parsed.regions,
141
+ });
142
+ if (parsed.hasText) {
143
+ productImagesWithText++;
144
+ totalImagesWithText++;
145
+ }
146
+ totalImagesScanned++;
147
+ }
148
+ catch {
149
+ imageScanResults.push({
150
+ mediaId: media.mediaId,
151
+ imageUrl: media.url,
152
+ hasText: false,
153
+ confidence: 0,
154
+ textRegions: [],
155
+ });
156
+ totalImagesScanned++;
157
+ }
158
+ }
159
+ results.push({
160
+ productId: product.id,
161
+ productTitle: product.title,
162
+ imagesScanned: imageScanResults.length,
163
+ imagesWithText: productImagesWithText,
164
+ results: imageScanResults,
165
+ });
166
+ if (stoppedReason) {
167
+ break;
168
+ }
169
+ }
170
+ return {
171
+ productsScanned: results.length,
172
+ imagesScanned: totalImagesScanned,
173
+ imagesWithText: totalImagesWithText,
174
+ results,
175
+ isPartial: stoppedReason ? true : undefined,
176
+ stoppedReason,
177
+ };
178
+ }
@@ -0,0 +1,12 @@
1
+ import { McpServerConfig } from "../../utils/config.js";
2
+ import type { ShopStatsInput } from "../../schemas/shopify.js";
3
+ export interface ShopStatsResult {
4
+ shopName: string;
5
+ shopDomain: string;
6
+ totalProducts: number;
7
+ productsWithImages: number;
8
+ totalImages: number;
9
+ isPartial?: boolean;
10
+ }
11
+ export declare function handleShopStats(input: ShopStatsInput, _config: McpServerConfig): Promise<ShopStatsResult>;
12
+ //# sourceMappingURL=shop-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop-stats.d.ts","sourceRoot":"","sources":["../../../../src/tools/shopify/shop-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA2G/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC,CAsD1B"}
@@ -0,0 +1,89 @@
1
+ const MAX_PAGINATION_PAGES = 10;
2
+ const PRODUCTS_PER_PAGE = 250;
3
+ async function shopifyGraphQL(shopDomain, accessToken, query, variables) {
4
+ const response = await fetch(`https://${shopDomain}/admin/api/2024-01/graphql.json`, {
5
+ method: "POST",
6
+ headers: {
7
+ "Content-Type": "application/json",
8
+ "X-Shopify-Access-Token": accessToken,
9
+ },
10
+ body: JSON.stringify({ query, variables }),
11
+ });
12
+ if (!response.ok) {
13
+ throw new Error(`Shopify API error: ${response.statusText}`);
14
+ }
15
+ const data = await response.json();
16
+ if (data.errors) {
17
+ throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
18
+ }
19
+ return data.data;
20
+ }
21
+ const SHOP_INFO_QUERY = `
22
+ query getShopInfo {
23
+ shop {
24
+ name
25
+ myshopifyDomain
26
+ }
27
+ productsCount {
28
+ count
29
+ }
30
+ }
31
+ `;
32
+ const PRODUCTS_WITH_IMAGES_QUERY = `
33
+ query getProductsWithImages($first: Int!, $after: String) {
34
+ products(first: $first, after: $after) {
35
+ edges {
36
+ node {
37
+ id
38
+ media(first: 50) {
39
+ edges {
40
+ node {
41
+ ... on MediaImage {
42
+ id
43
+ __typename
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ cursor
50
+ }
51
+ pageInfo {
52
+ hasNextPage
53
+ }
54
+ }
55
+ }
56
+ `;
57
+ export async function handleShopStats(input, _config) {
58
+ const { shopDomain, accessToken } = input.shopifyCredentials;
59
+ const shopInfo = await shopifyGraphQL(shopDomain, accessToken, SHOP_INFO_QUERY);
60
+ const totalProducts = shopInfo.productsCount.count;
61
+ let productsWithImages = 0;
62
+ let totalImages = 0;
63
+ let cursor = null;
64
+ let pagesProcessed = 0;
65
+ let hasMorePages = true;
66
+ while (hasMorePages && pagesProcessed < MAX_PAGINATION_PAGES) {
67
+ const productsData = await shopifyGraphQL(shopDomain, accessToken, PRODUCTS_WITH_IMAGES_QUERY, { first: PRODUCTS_PER_PAGE, after: cursor });
68
+ for (const edge of productsData.products.edges) {
69
+ const imageCount = edge.node.media.edges.filter((mediaEdge) => mediaEdge.node.__typename === "MediaImage").length;
70
+ if (imageCount > 0) {
71
+ productsWithImages++;
72
+ totalImages += imageCount;
73
+ }
74
+ }
75
+ const edges = productsData.products.edges;
76
+ cursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
77
+ hasMorePages = productsData.products.pageInfo.hasNextPage;
78
+ pagesProcessed++;
79
+ }
80
+ const isPartial = hasMorePages && pagesProcessed >= MAX_PAGINATION_PAGES ? true : undefined;
81
+ return {
82
+ shopName: shopInfo.shop.name,
83
+ shopDomain: shopInfo.shop.myshopifyDomain,
84
+ totalProducts,
85
+ productsWithImages,
86
+ totalImages,
87
+ isPartial,
88
+ };
89
+ }
@@ -0,0 +1,19 @@
1
+ import { McpServerConfig } from "../../utils/config.js";
2
+ import type { TranslateProductInput } from "../../schemas/shopify.js";
3
+ export interface TranslateProductResult {
4
+ productId: string;
5
+ productTitle: string;
6
+ status: "completed" | "failed" | "no_images";
7
+ imagesTranslated: number;
8
+ targetLanguage: string;
9
+ results: Array<{
10
+ mediaId: string;
11
+ originalUrl: string;
12
+ translatedImage: string;
13
+ status: "success" | "error";
14
+ error?: string;
15
+ }>;
16
+ error?: string;
17
+ }
18
+ export declare function handleTranslateProduct(input: TranslateProductInput, config: McpServerConfig): Promise<TranslateProductResult>;
19
+ //# sourceMappingURL=translate-product.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate-product.d.ts","sourceRoot":"","sources":["../../../../src/tools/shopify/translate-product.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAuFtE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,qBAAqB,EAC5B,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,sBAAsB,CAAC,CA4EjC"}
@@ -0,0 +1,121 @@
1
+ import { InvalidInputError } from "../../utils/errors.js";
2
+ import { TranslateImageApiClient } from "../../utils/api-client.js";
3
+ async function shopifyGraphQL(shopDomain, accessToken, query, variables) {
4
+ const response = await fetch(`https://${shopDomain}/admin/api/2024-01/graphql.json`, {
5
+ method: "POST",
6
+ headers: {
7
+ "Content-Type": "application/json",
8
+ "X-Shopify-Access-Token": accessToken,
9
+ },
10
+ body: JSON.stringify({ query, variables }),
11
+ });
12
+ if (!response.ok) {
13
+ throw new Error(`Shopify API error: ${response.statusText}`);
14
+ }
15
+ const data = await response.json();
16
+ if (data.errors) {
17
+ throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
18
+ }
19
+ return data.data;
20
+ }
21
+ const PRODUCT_BY_ID_QUERY = `
22
+ query getProduct($id: ID!) {
23
+ product(id: $id) {
24
+ id
25
+ handle
26
+ title
27
+ featuredImage {
28
+ url
29
+ }
30
+ images(first: 50) {
31
+ edges {
32
+ node {
33
+ id
34
+ url
35
+ }
36
+ }
37
+ }
38
+ media(first: 50) {
39
+ edges {
40
+ node {
41
+ ... on MediaImage {
42
+ id
43
+ __typename
44
+ image {
45
+ url
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ `;
54
+ async function fetchImageAsBlob(imageUrl) {
55
+ const response = await fetch(imageUrl);
56
+ if (!response.ok) {
57
+ throw new Error(`Failed to fetch image: ${response.status}`);
58
+ }
59
+ return response.blob();
60
+ }
61
+ export async function handleTranslateProduct(input, config) {
62
+ const { shopDomain, accessToken } = input.shopifyCredentials;
63
+ const apiClient = new TranslateImageApiClient(config);
64
+ const productData = await shopifyGraphQL(shopDomain, accessToken, PRODUCT_BY_ID_QUERY, { id: input.productId });
65
+ if (!productData.product) {
66
+ throw new InvalidInputError(`Product not found: ${input.productId}`);
67
+ }
68
+ const product = productData.product;
69
+ const mediaImages = product.media.edges
70
+ .filter((edge) => edge.node.__typename === "MediaImage" && edge.node.image?.url)
71
+ .map((edge) => ({
72
+ mediaId: edge.node.id,
73
+ url: edge.node.image.url,
74
+ }));
75
+ if (mediaImages.length === 0) {
76
+ return {
77
+ productId: input.productId,
78
+ productTitle: product.title,
79
+ status: "no_images",
80
+ imagesTranslated: 0,
81
+ targetLanguage: input.targetLanguage,
82
+ results: [],
83
+ };
84
+ }
85
+ const results = [];
86
+ let successCount = 0;
87
+ for (const media of mediaImages) {
88
+ try {
89
+ const imageBlob = await fetchImageAsBlob(media.url);
90
+ const translateResult = await apiClient.translate(imageBlob, {
91
+ target_lang: input.targetLanguage,
92
+ translator: input.translator,
93
+ font: "NotoSans",
94
+ });
95
+ results.push({
96
+ mediaId: media.mediaId,
97
+ originalUrl: media.url,
98
+ translatedImage: translateResult.resultImage,
99
+ status: "success",
100
+ });
101
+ successCount++;
102
+ }
103
+ catch (error) {
104
+ results.push({
105
+ mediaId: media.mediaId,
106
+ originalUrl: media.url,
107
+ translatedImage: "",
108
+ status: "error",
109
+ error: error instanceof Error ? error.message : "Unknown error",
110
+ });
111
+ }
112
+ }
113
+ return {
114
+ productId: input.productId,
115
+ productTitle: product.title,
116
+ status: successCount > 0 ? "completed" : "failed",
117
+ imagesTranslated: successCount,
118
+ targetLanguage: input.targetLanguage,
119
+ results,
120
+ };
121
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServerConfig } from "../utils/config.js";
2
+ import { TextRemovalInput, TextRemovalOutput } from "../schemas/text-removal.js";
3
+ export declare function handleTextRemoval(input: TextRemovalInput, config: McpServerConfig): Promise<TextRemovalOutput>;
4
+ //# sourceMappingURL=text-removal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-removal.d.ts","sourceRoot":"","sources":["../../../src/tools/text-removal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAEpC,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAS5B"}
@@ -0,0 +1,10 @@
1
+ import { TranslateImageApiClient } from "../utils/api-client.js";
2
+ import { resolveImageInput } from "../utils/image.js";
3
+ export async function handleTextRemoval(input, config) {
4
+ const client = new TranslateImageApiClient(config);
5
+ const imageBlob = await resolveImageInput(input.image);
6
+ const result = await client.removeText(imageBlob);
7
+ return {
8
+ cleanedImage: result.cleanedImage,
9
+ };
10
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServerConfig } from "../utils/config.js";
2
+ import { TranslateImageInput, TranslateImageOutput } from "../schemas/translate.js";
3
+ export declare function handleTranslateImage(input: TranslateImageInput, config: McpServerConfig): Promise<TranslateImageOutput>;
4
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../../src/tools/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EAErB,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
@@ -0,0 +1,16 @@
1
+ import { TranslateImageApiClient } from "../utils/api-client.js";
2
+ import { resolveImageInput } from "../utils/image.js";
3
+ export async function handleTranslateImage(input, config) {
4
+ const client = new TranslateImageApiClient(config);
5
+ const imageBlob = await resolveImageInput(input.image);
6
+ const result = await client.translate(imageBlob, {
7
+ target_lang: input.target_lang.toLowerCase(),
8
+ translator: input.translator,
9
+ font: input.font,
10
+ });
11
+ return {
12
+ translatedImage: result.resultImage,
13
+ inpaintedImage: result.inpaintedImage,
14
+ textRegions: result.textRegions,
15
+ };
16
+ }
@@ -0,0 +1,46 @@
1
+ import { McpServerConfig } from "./config.js";
2
+ export interface ApiResponse<T> {
3
+ success: boolean;
4
+ data?: T;
5
+ error?: string;
6
+ }
7
+ export declare class TranslateImageApiClient {
8
+ private readonly apiKey;
9
+ private readonly baseUrl;
10
+ constructor(config: McpServerConfig);
11
+ translate(imageBlob: Blob, config: {
12
+ target_lang: string;
13
+ translator: string;
14
+ font?: string;
15
+ }): Promise<{
16
+ resultImage: string;
17
+ inpaintedImage: string;
18
+ textRegions: unknown[];
19
+ }>;
20
+ ocr(imageBlob: Blob): Promise<{
21
+ text: string;
22
+ language: string;
23
+ regions: Array<{
24
+ bounds: {
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ };
30
+ languages: Record<string, string>;
31
+ probability: number;
32
+ }>;
33
+ }>;
34
+ removeText(imageBlob: Blob): Promise<{
35
+ cleanedImage: string;
36
+ }>;
37
+ imageToText(imageBlob: Blob, targetLanguages?: string[]): Promise<{
38
+ text: string;
39
+ languages: string[];
40
+ translations?: Record<string, string>;
41
+ }>;
42
+ private parseError;
43
+ private extractBase64;
44
+ private blobToBase64;
45
+ }
46
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../../src/utils/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,eAAe;IAQ7B,SAAS,CACb,SAAS,EAAE,IAAI,EACf,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACA,OAAO,CAAC;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,OAAO,EAAE,CAAC;KACxB,CAAC;IAqCI,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC;QAClC,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,KAAK,CAAC;YACb,MAAM,EAAE;gBAAE,CAAC,EAAE,MAAM,CAAC;gBAAC,CAAC,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,CAAA;aAAE,CAAC;YAChE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAClC,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;IAoBI,UAAU,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA4B9D,WAAW,CACf,SAAS,EAAE,IAAI,EACf,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC;QACT,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACvC,CAAC;YAuBY,UAAU;IASxB,OAAO,CAAC,aAAa;YASP,YAAY;CAI3B"}
@@ -0,0 +1,124 @@
1
+ import { ApiKeyMissingError, ImageTranslationError } from "./errors.js";
2
+ export class TranslateImageApiClient {
3
+ apiKey;
4
+ baseUrl;
5
+ constructor(config) {
6
+ if (!config.apiKey) {
7
+ throw new ApiKeyMissingError("TranslateImage");
8
+ }
9
+ this.apiKey = config.apiKey;
10
+ this.baseUrl = config.apiBaseUrl.replace(/\/$/, "");
11
+ }
12
+ async translate(imageBlob, config) {
13
+ const formData = new FormData();
14
+ formData.append("image", imageBlob);
15
+ formData.append("config", JSON.stringify(config));
16
+ const response = await fetch(`${this.baseUrl}/api/translate`, {
17
+ method: "POST",
18
+ headers: {
19
+ Authorization: `Bearer ${this.apiKey}`,
20
+ },
21
+ body: formData,
22
+ });
23
+ if (!response.ok) {
24
+ const error = await this.parseError(response);
25
+ throw new ImageTranslationError(error);
26
+ }
27
+ const contentType = response.headers.get("content-type");
28
+ if (contentType?.includes("application/json")) {
29
+ const json = await response.json();
30
+ return {
31
+ resultImage: this.extractBase64(json.resultImage),
32
+ inpaintedImage: this.extractBase64(json.inpaintedImage),
33
+ textRegions: json.textRegions || [],
34
+ };
35
+ }
36
+ const blob = await response.blob();
37
+ const base64 = await this.blobToBase64(blob);
38
+ return {
39
+ resultImage: base64,
40
+ inpaintedImage: base64,
41
+ textRegions: [],
42
+ };
43
+ }
44
+ async ocr(imageBlob) {
45
+ const formData = new FormData();
46
+ formData.append("image", imageBlob);
47
+ const response = await fetch(`${this.baseUrl}/api/ocr`, {
48
+ method: "POST",
49
+ headers: {
50
+ Authorization: `Bearer ${this.apiKey}`,
51
+ },
52
+ body: formData,
53
+ });
54
+ if (!response.ok) {
55
+ const error = await this.parseError(response);
56
+ throw new ImageTranslationError(error);
57
+ }
58
+ return response.json();
59
+ }
60
+ async removeText(imageBlob) {
61
+ const formData = new FormData();
62
+ formData.append("image", imageBlob);
63
+ const response = await fetch(`${this.baseUrl}/api/remove-text`, {
64
+ method: "POST",
65
+ headers: {
66
+ Authorization: `Bearer ${this.apiKey}`,
67
+ },
68
+ body: formData,
69
+ });
70
+ if (!response.ok) {
71
+ const error = await this.parseError(response);
72
+ throw new ImageTranslationError(error);
73
+ }
74
+ const contentType = response.headers.get("content-type");
75
+ if (contentType?.includes("application/json")) {
76
+ const json = await response.json();
77
+ return { cleanedImage: this.extractBase64(json.cleanedImage) };
78
+ }
79
+ const blob = await response.blob();
80
+ const base64 = await this.blobToBase64(blob);
81
+ return { cleanedImage: base64 };
82
+ }
83
+ async imageToText(imageBlob, targetLanguages) {
84
+ const formData = new FormData();
85
+ formData.append("image", imageBlob);
86
+ if (targetLanguages && targetLanguages.length > 0) {
87
+ formData.append("config", JSON.stringify({ targetLanguages }));
88
+ }
89
+ const response = await fetch(`${this.baseUrl}/api/image-to-text`, {
90
+ method: "POST",
91
+ headers: {
92
+ Authorization: `Bearer ${this.apiKey}`,
93
+ },
94
+ body: formData,
95
+ });
96
+ if (!response.ok) {
97
+ const error = await this.parseError(response);
98
+ throw new ImageTranslationError(error);
99
+ }
100
+ return response.json();
101
+ }
102
+ async parseError(response) {
103
+ try {
104
+ const json = await response.json();
105
+ return json.error || `API error: ${response.status}`;
106
+ }
107
+ catch {
108
+ return `API error: ${response.status} ${response.statusText}`;
109
+ }
110
+ }
111
+ extractBase64(dataUrl) {
112
+ if (!dataUrl)
113
+ return "";
114
+ if (dataUrl.startsWith("data:")) {
115
+ const [, base64] = dataUrl.split(",");
116
+ return base64 || "";
117
+ }
118
+ return dataUrl;
119
+ }
120
+ async blobToBase64(blob) {
121
+ const buffer = await blob.arrayBuffer();
122
+ return Buffer.from(buffer).toString("base64");
123
+ }
124
+ }
@@ -0,0 +1,7 @@
1
+ export interface McpServerConfig {
2
+ apiKey: string;
3
+ apiBaseUrl: string;
4
+ }
5
+ export declare function loadConfigFromEnv(): McpServerConfig;
6
+ export declare function validateConfig(config: McpServerConfig): void;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/utils/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,eAAe,CAMnD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,11 @@
1
+ import { ApiKeyMissingError } from "./errors.js";
2
+ export function loadConfigFromEnv() {
3
+ const apiKey = process.env.TRANSLATEIMAGE_API_KEY || "";
4
+ const apiBaseUrl = process.env.TRANSLATEIMAGE_API_URL || "https://translateimage.io";
5
+ return { apiKey, apiBaseUrl };
6
+ }
7
+ export function validateConfig(config) {
8
+ if (!config.apiKey) {
9
+ throw new ApiKeyMissingError("TranslateImage");
10
+ }
11
+ }