@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.
- package/LICENSE +23 -0
- package/README.md +533 -0
- package/dist/bin/http.d.ts +3 -0
- package/dist/bin/http.d.ts.map +1 -0
- package/dist/bin/http.js +51 -0
- package/dist/bin/stdio.d.ts +3 -0
- package/dist/bin/stdio.d.ts.map +1 -0
- package/dist/bin/stdio.js +14 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/schemas/common.d.ts +40 -0
- package/dist/src/schemas/common.d.ts.map +1 -0
- package/dist/src/schemas/common.js +31 -0
- package/dist/src/schemas/image-to-text.d.ts +69 -0
- package/dist/src/schemas/image-to-text.d.ts.map +1 -0
- package/dist/src/schemas/image-to-text.js +26 -0
- package/dist/src/schemas/index.d.ts +7 -0
- package/dist/src/schemas/index.d.ts.map +1 -0
- package/dist/src/schemas/index.js +6 -0
- package/dist/src/schemas/ocr.d.ts +333 -0
- package/dist/src/schemas/ocr.d.ts.map +1 -0
- package/dist/src/schemas/ocr.js +46 -0
- package/dist/src/schemas/shopify.d.ts +860 -0
- package/dist/src/schemas/shopify.d.ts.map +1 -0
- package/dist/src/schemas/shopify.js +183 -0
- package/dist/src/schemas/text-removal.d.ts +60 -0
- package/dist/src/schemas/text-removal.d.ts.map +1 -0
- package/dist/src/schemas/text-removal.js +16 -0
- package/dist/src/schemas/translate.d.ts +197 -0
- package/dist/src/schemas/translate.d.ts.map +1 -0
- package/dist/src/schemas/translate.js +70 -0
- package/dist/src/server.d.ts +4 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +7 -0
- package/dist/src/tools/image-to-text.d.ts +4 -0
- package/dist/src/tools/image-to-text.d.ts.map +1 -0
- package/dist/src/tools/image-to-text.js +12 -0
- package/dist/src/tools/index.d.ts +8 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +202 -0
- package/dist/src/tools/ocr.d.ts +4 -0
- package/dist/src/tools/ocr.d.ts.map +1 -0
- package/dist/src/tools/ocr.js +28 -0
- package/dist/src/tools/shopify/batch-translate.d.ts +26 -0
- package/dist/src/tools/shopify/batch-translate.d.ts.map +1 -0
- package/dist/src/tools/shopify/batch-translate.js +143 -0
- package/dist/src/tools/shopify/index.d.ts +19 -0
- package/dist/src/tools/shopify/index.d.ts.map +1 -0
- package/dist/src/tools/shopify/index.js +28 -0
- package/dist/src/tools/shopify/scan-products.d.ts +38 -0
- package/dist/src/tools/shopify/scan-products.d.ts.map +1 -0
- package/dist/src/tools/shopify/scan-products.js +178 -0
- package/dist/src/tools/shopify/shop-stats.d.ts +12 -0
- package/dist/src/tools/shopify/shop-stats.d.ts.map +1 -0
- package/dist/src/tools/shopify/shop-stats.js +89 -0
- package/dist/src/tools/shopify/translate-product.d.ts +19 -0
- package/dist/src/tools/shopify/translate-product.d.ts.map +1 -0
- package/dist/src/tools/shopify/translate-product.js +121 -0
- package/dist/src/tools/text-removal.d.ts +4 -0
- package/dist/src/tools/text-removal.d.ts.map +1 -0
- package/dist/src/tools/text-removal.js +10 -0
- package/dist/src/tools/translate.d.ts +4 -0
- package/dist/src/tools/translate.d.ts.map +1 -0
- package/dist/src/tools/translate.js +16 -0
- package/dist/src/utils/api-client.d.ts +46 -0
- package/dist/src/utils/api-client.d.ts.map +1 -0
- package/dist/src/utils/api-client.js +124 -0
- package/dist/src/utils/config.d.ts +7 -0
- package/dist/src/utils/config.d.ts.map +1 -0
- package/dist/src/utils/config.js +11 -0
- package/dist/src/utils/errors.d.ts +17 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +35 -0
- package/dist/src/utils/image.d.ts +34 -0
- package/dist/src/utils/image.d.ts.map +1 -0
- package/dist/src/utils/image.js +105 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +4 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
3
|
+
import { ZodError } from "zod";
|
|
4
|
+
import { TranslateImageInputSchema } from "../schemas/translate.js";
|
|
5
|
+
import { OcrInputSchema } from "../schemas/ocr.js";
|
|
6
|
+
import { TextRemovalInputSchema } from "../schemas/text-removal.js";
|
|
7
|
+
import { ImageToTextInputSchema } from "../schemas/image-to-text.js";
|
|
8
|
+
import { TranslateProductInputSchema, BatchTranslateInputSchema, ShopStatsInputSchema, ProductScanInputSchema, } from "../schemas/shopify.js";
|
|
9
|
+
import { handleTranslateImage } from "./translate.js";
|
|
10
|
+
import { handleOcr } from "./ocr.js";
|
|
11
|
+
import { handleTextRemoval } from "./text-removal.js";
|
|
12
|
+
import { handleImageToText } from "./image-to-text.js";
|
|
13
|
+
import { shopifyToolDefinitions, handleTranslateProduct, handleBatchTranslate, handleShopStats, handleScanProducts, } from "./shopify/index.js";
|
|
14
|
+
const coreToolDefinitions = [
|
|
15
|
+
{
|
|
16
|
+
name: "translate_image",
|
|
17
|
+
description: "Translate text in images while preserving the original layout. Detects text, translates it to the target language, and renders the translation back onto the image.",
|
|
18
|
+
inputSchema: zodToJsonSchema(TranslateImageInputSchema),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "extract_text",
|
|
22
|
+
description: "Extract text from images using OCR (Optical Character Recognition). Returns detected paragraphs with bounding boxes and detected language information.",
|
|
23
|
+
inputSchema: zodToJsonSchema(OcrInputSchema),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "remove_text",
|
|
27
|
+
description: "Remove text from images using inpainting. Detects text regions and fills them with appropriate background content.",
|
|
28
|
+
inputSchema: zodToJsonSchema(TextRemovalInputSchema),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "image_to_text",
|
|
32
|
+
description: "Extract text from images using Gemini AI and optionally translate to specified languages. Provides high-quality OCR with language detection and translation capabilities.",
|
|
33
|
+
inputSchema: zodToJsonSchema(ImageToTextInputSchema),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
export function registerAllTools(server, config) {
|
|
37
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
38
|
+
tools: [...coreToolDefinitions, ...shopifyToolDefinitions],
|
|
39
|
+
}));
|
|
40
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
41
|
+
const { name, arguments: args } = request.params;
|
|
42
|
+
try {
|
|
43
|
+
let result;
|
|
44
|
+
let contentItems;
|
|
45
|
+
switch (name) {
|
|
46
|
+
case "translate_image": {
|
|
47
|
+
const input = TranslateImageInputSchema.parse(args);
|
|
48
|
+
const output = await handleTranslateImage(input, config);
|
|
49
|
+
result = output;
|
|
50
|
+
contentItems = [
|
|
51
|
+
{
|
|
52
|
+
type: "image",
|
|
53
|
+
data: output.translatedImage,
|
|
54
|
+
mimeType: "image/png",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Translated image with ${output.textRegions.length} text regions`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "extract_text": {
|
|
64
|
+
const input = OcrInputSchema.parse(args);
|
|
65
|
+
const output = await handleOcr(input, config);
|
|
66
|
+
result = output;
|
|
67
|
+
const extractedText = output.paragraphs
|
|
68
|
+
.map((p) => p.text)
|
|
69
|
+
.join("\n\n");
|
|
70
|
+
contentItems = [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: extractedText || "(No text detected)",
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "remove_text": {
|
|
79
|
+
const input = TextRemovalInputSchema.parse(args);
|
|
80
|
+
const output = await handleTextRemoval(input, config);
|
|
81
|
+
result = output;
|
|
82
|
+
contentItems = [
|
|
83
|
+
{
|
|
84
|
+
type: "image",
|
|
85
|
+
data: output.cleanedImage,
|
|
86
|
+
mimeType: "image/png",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: "Text removed from image",
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "image_to_text": {
|
|
96
|
+
const input = ImageToTextInputSchema.parse(args);
|
|
97
|
+
const output = await handleImageToText(input, config);
|
|
98
|
+
result = output;
|
|
99
|
+
let textContent = output.extractedText || "(No text detected)";
|
|
100
|
+
if (output.translations &&
|
|
101
|
+
Object.keys(output.translations).length > 0) {
|
|
102
|
+
textContent += "\n\n--- Translations ---";
|
|
103
|
+
for (const [lang, translation] of Object.entries(output.translations)) {
|
|
104
|
+
textContent += `\n\n[${lang}]:\n${translation}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
contentItems = [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: textContent,
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "translate_product": {
|
|
116
|
+
const input = TranslateProductInputSchema.parse(args);
|
|
117
|
+
const output = await handleTranslateProduct(input, config);
|
|
118
|
+
result = output;
|
|
119
|
+
contentItems = [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Status: ${output.status}, Product: ${output.productTitle}, Images translated: ${output.imagesTranslated}`,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "batch_translate": {
|
|
128
|
+
const input = BatchTranslateInputSchema.parse(args);
|
|
129
|
+
const output = await handleBatchTranslate(input, config);
|
|
130
|
+
result = output;
|
|
131
|
+
contentItems = [
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `Batch complete: ${output.successCount}/${output.totalProducts} products translated to ${output.targetLanguage}`,
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "shop_stats": {
|
|
140
|
+
const input = ShopStatsInputSchema.parse(args);
|
|
141
|
+
const output = await handleShopStats(input, config);
|
|
142
|
+
result = output;
|
|
143
|
+
const partialNote = output.isPartial ? " (partial scan)" : "";
|
|
144
|
+
contentItems = [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: `Shop: ${output.shopName}, Products: ${output.totalProducts}, With images: ${output.productsWithImages}, Total images: ${output.totalImages}${partialNote}`,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "scan_products": {
|
|
153
|
+
const input = ProductScanInputSchema.parse(args);
|
|
154
|
+
const output = await handleScanProducts(input, config);
|
|
155
|
+
result = output;
|
|
156
|
+
const partialScanNote = output.isPartial
|
|
157
|
+
? ` (stopped: ${output.stoppedReason})`
|
|
158
|
+
: "";
|
|
159
|
+
contentItems = [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `Scanned ${output.productsScanned} products, ${output.imagesScanned} images, ${output.imagesWithText} with text${partialScanNote}`,
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
default:
|
|
168
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
content: contentItems,
|
|
172
|
+
structuredContent: result,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (error instanceof ZodError) {
|
|
177
|
+
const issues = error.issues
|
|
178
|
+
.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
179
|
+
.join("; ");
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: `Invalid input: ${issues}` }],
|
|
182
|
+
isError: true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (error instanceof McpError) {
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: "text", text: error.message }],
|
|
188
|
+
isError: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: errorMessage }],
|
|
194
|
+
isError: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
export { handleTranslateImage } from "./translate.js";
|
|
200
|
+
export { handleOcr } from "./ocr.js";
|
|
201
|
+
export { handleTextRemoval } from "./text-removal.js";
|
|
202
|
+
export { handleImageToText } from "./image-to-text.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../../src/tools/ocr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAExD,wBAAsB,SAAS,CAC7B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,SAAS,CAAC,CA4BpB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TranslateImageApiClient } from "../utils/api-client.js";
|
|
2
|
+
import { resolveImageInput } from "../utils/image.js";
|
|
3
|
+
export async function handleOcr(input, config) {
|
|
4
|
+
const client = new TranslateImageApiClient(config);
|
|
5
|
+
const imageBlob = await resolveImageInput(input.image);
|
|
6
|
+
const result = await client.ocr(imageBlob);
|
|
7
|
+
const paragraphs = result.regions.map((region) => ({
|
|
8
|
+
boundingBox: [
|
|
9
|
+
region.bounds.x,
|
|
10
|
+
region.bounds.y,
|
|
11
|
+
region.bounds.x + region.bounds.width,
|
|
12
|
+
region.bounds.y + region.bounds.height,
|
|
13
|
+
],
|
|
14
|
+
text: Object.values(region.languages)[0] || "",
|
|
15
|
+
lines: [],
|
|
16
|
+
detectedLanguage: {
|
|
17
|
+
language: result.language,
|
|
18
|
+
code: result.language,
|
|
19
|
+
confidence: region.probability,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
paragraphs,
|
|
24
|
+
width: 0,
|
|
25
|
+
height: 0,
|
|
26
|
+
angle: 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { McpServerConfig } from "../../utils/config.js";
|
|
2
|
+
import type { BatchTranslateInput } from "../../schemas/shopify.js";
|
|
3
|
+
interface ProductTranslationResult {
|
|
4
|
+
productId: string;
|
|
5
|
+
productTitle: string;
|
|
6
|
+
status: "success" | "failed" | "no_images";
|
|
7
|
+
imagesTranslated: number;
|
|
8
|
+
imagesFailed: number;
|
|
9
|
+
results: Array<{
|
|
10
|
+
mediaId: string;
|
|
11
|
+
translatedImage: string;
|
|
12
|
+
status: "success" | "error";
|
|
13
|
+
error?: string;
|
|
14
|
+
}>;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BatchTranslateResult {
|
|
18
|
+
totalProducts: number;
|
|
19
|
+
successCount: number;
|
|
20
|
+
failureCount: number;
|
|
21
|
+
targetLanguage: string;
|
|
22
|
+
products: ProductTranslationResult[];
|
|
23
|
+
}
|
|
24
|
+
export declare function handleBatchTranslate(input: BatchTranslateInput, config: McpServerConfig): Promise<BatchTranslateResult>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=batch-translate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-translate.d.ts","sourceRoot":"","sources":["../../../../src/tools/shopify/batch-translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AA4GpE,UAAU,wBAAwB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,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;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,wBAAwB,EAAE,CAAC;CACtC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,CAAC,CA0G/B"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { TranslateImageApiClient } from "../../utils/api-client.js";
|
|
2
|
+
const IMAGE_CONCURRENCY_LIMIT = 5;
|
|
3
|
+
async function parallelWithLimit(items, limit, fn) {
|
|
4
|
+
const results = new Array(items.length);
|
|
5
|
+
let currentIndex = 0;
|
|
6
|
+
async function worker() {
|
|
7
|
+
while (currentIndex < items.length) {
|
|
8
|
+
const index = currentIndex++;
|
|
9
|
+
results[index] = await fn(items[index], index);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
13
|
+
await Promise.all(workers);
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
async function shopifyGraphQL(shopDomain, accessToken, query, variables) {
|
|
17
|
+
const response = await fetch(`https://${shopDomain}/admin/api/2024-01/graphql.json`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
"X-Shopify-Access-Token": accessToken,
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({ query, variables }),
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Shopify API error: ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
if (data.errors) {
|
|
30
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
|
|
31
|
+
}
|
|
32
|
+
return data.data;
|
|
33
|
+
}
|
|
34
|
+
const PRODUCTS_BY_IDS_QUERY = `
|
|
35
|
+
query getProducts($ids: [ID!]!) {
|
|
36
|
+
nodes(ids: $ids) {
|
|
37
|
+
... on Product {
|
|
38
|
+
id
|
|
39
|
+
handle
|
|
40
|
+
title
|
|
41
|
+
media(first: 50) {
|
|
42
|
+
edges {
|
|
43
|
+
node {
|
|
44
|
+
... on MediaImage {
|
|
45
|
+
id
|
|
46
|
+
__typename
|
|
47
|
+
image {
|
|
48
|
+
url
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
async function fetchImageAsBlob(imageUrl) {
|
|
59
|
+
const response = await fetch(imageUrl);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Failed to fetch image: ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
return response.blob();
|
|
64
|
+
}
|
|
65
|
+
export async function handleBatchTranslate(input, config) {
|
|
66
|
+
const { shopDomain, accessToken } = input.shopifyCredentials;
|
|
67
|
+
const apiClient = new TranslateImageApiClient(config);
|
|
68
|
+
const productsData = await shopifyGraphQL(shopDomain, accessToken, PRODUCTS_BY_IDS_QUERY, { ids: input.productIds });
|
|
69
|
+
const products = productsData.nodes.filter((node) => node !== null);
|
|
70
|
+
const productResults = [];
|
|
71
|
+
let successCount = 0;
|
|
72
|
+
let failureCount = 0;
|
|
73
|
+
for (const product of products) {
|
|
74
|
+
const mediaImages = product.media.edges
|
|
75
|
+
.filter((edge) => edge.node.__typename === "MediaImage" && edge.node.image?.url)
|
|
76
|
+
.map((edge) => ({
|
|
77
|
+
mediaId: edge.node.id,
|
|
78
|
+
url: edge.node.image.url,
|
|
79
|
+
}));
|
|
80
|
+
if (mediaImages.length === 0) {
|
|
81
|
+
productResults.push({
|
|
82
|
+
productId: product.id,
|
|
83
|
+
productTitle: product.title,
|
|
84
|
+
status: "no_images",
|
|
85
|
+
imagesTranslated: 0,
|
|
86
|
+
imagesFailed: 0,
|
|
87
|
+
results: [],
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const imageResults = await parallelWithLimit(mediaImages, IMAGE_CONCURRENCY_LIMIT, async (media) => {
|
|
92
|
+
try {
|
|
93
|
+
const imageBlob = await fetchImageAsBlob(media.url);
|
|
94
|
+
const translateResult = await apiClient.translate(imageBlob, {
|
|
95
|
+
target_lang: input.targetLanguage,
|
|
96
|
+
translator: input.translator,
|
|
97
|
+
font: "NotoSans",
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
mediaId: media.mediaId,
|
|
101
|
+
translatedImage: translateResult.resultImage,
|
|
102
|
+
status: "success",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
mediaId: media.mediaId,
|
|
108
|
+
translatedImage: "",
|
|
109
|
+
status: "error",
|
|
110
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const productImagesSuccess = imageResults.filter((r) => r.status === "success").length;
|
|
115
|
+
const productImagesFailed = imageResults.filter((r) => r.status === "error").length;
|
|
116
|
+
const productStatus = productImagesSuccess > 0
|
|
117
|
+
? "success"
|
|
118
|
+
: productImagesFailed > 0
|
|
119
|
+
? "failed"
|
|
120
|
+
: "no_images";
|
|
121
|
+
productResults.push({
|
|
122
|
+
productId: product.id,
|
|
123
|
+
productTitle: product.title,
|
|
124
|
+
status: productStatus,
|
|
125
|
+
imagesTranslated: productImagesSuccess,
|
|
126
|
+
imagesFailed: productImagesFailed,
|
|
127
|
+
results: imageResults,
|
|
128
|
+
});
|
|
129
|
+
if (productStatus === "success") {
|
|
130
|
+
successCount++;
|
|
131
|
+
}
|
|
132
|
+
else if (productStatus === "failed") {
|
|
133
|
+
failureCount++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
totalProducts: products.length,
|
|
138
|
+
successCount,
|
|
139
|
+
failureCount,
|
|
140
|
+
targetLanguage: input.targetLanguage,
|
|
141
|
+
products: productResults,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { handleTranslateProduct } from "./translate-product.js";
|
|
2
|
+
export type { TranslateProductResult } from "./translate-product.js";
|
|
3
|
+
export { handleBatchTranslate } from "./batch-translate.js";
|
|
4
|
+
export type { BatchTranslateResult } from "./batch-translate.js";
|
|
5
|
+
export { handleShopStats } from "./shop-stats.js";
|
|
6
|
+
export type { ShopStatsResult } from "./shop-stats.js";
|
|
7
|
+
export { handleScanProducts } from "./scan-products.js";
|
|
8
|
+
export type { ScanProductsResult } from "./scan-products.js";
|
|
9
|
+
export declare const shopifyToolDefinitions: {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: import("zod-to-json-schema").JsonSchema7Type & {
|
|
13
|
+
$schema?: string | undefined;
|
|
14
|
+
definitions?: {
|
|
15
|
+
[key: string]: import("zod-to-json-schema").JsonSchema7Type;
|
|
16
|
+
} | undefined;
|
|
17
|
+
};
|
|
18
|
+
}[];
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tools/shopify/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,eAAO,MAAM,sBAAsB;;;;;;;;;GAyBlC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
+
import { TranslateProductInputSchema, BatchTranslateInputSchema, ShopStatsInputSchema, ProductScanInputSchema, } from "../../schemas/shopify.js";
|
|
3
|
+
export { handleTranslateProduct } from "./translate-product.js";
|
|
4
|
+
export { handleBatchTranslate } from "./batch-translate.js";
|
|
5
|
+
export { handleShopStats } from "./shop-stats.js";
|
|
6
|
+
export { handleScanProducts } from "./scan-products.js";
|
|
7
|
+
export const shopifyToolDefinitions = [
|
|
8
|
+
{
|
|
9
|
+
name: "translate_product",
|
|
10
|
+
description: "Translate product images for an e-commerce store. Fetches product media and translates text in images to the target language.",
|
|
11
|
+
inputSchema: zodToJsonSchema(TranslateProductInputSchema),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "batch_translate",
|
|
15
|
+
description: "Batch translate product images for multiple products. Translates text in all images for the specified products to the target language.",
|
|
16
|
+
inputSchema: zodToJsonSchema(BatchTranslateInputSchema),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "shop_stats",
|
|
20
|
+
description: "Get statistics about a shop including total products, products with images, and total image count. Does not require database access.",
|
|
21
|
+
inputSchema: zodToJsonSchema(ShopStatsInputSchema),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "scan_products",
|
|
25
|
+
description: "Scan product images for text detection. Can scan a specific product or multiple products to identify which images contain translatable text.",
|
|
26
|
+
inputSchema: zodToJsonSchema(ProductScanInputSchema),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { McpServerConfig } from "../../utils/config.js";
|
|
2
|
+
import type { ProductScanInput } from "../../schemas/shopify.js";
|
|
3
|
+
interface TextRegion {
|
|
4
|
+
bounds: {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
text: string;
|
|
11
|
+
language: string;
|
|
12
|
+
confidence: number;
|
|
13
|
+
}
|
|
14
|
+
interface ImageScanResult {
|
|
15
|
+
mediaId: string;
|
|
16
|
+
imageUrl: string;
|
|
17
|
+
hasText: boolean;
|
|
18
|
+
confidence: number;
|
|
19
|
+
textRegions: TextRegion[];
|
|
20
|
+
}
|
|
21
|
+
interface ProductScanResult {
|
|
22
|
+
productId: string;
|
|
23
|
+
productTitle: string;
|
|
24
|
+
imagesScanned: number;
|
|
25
|
+
imagesWithText: number;
|
|
26
|
+
results: ImageScanResult[];
|
|
27
|
+
}
|
|
28
|
+
export interface ScanProductsResult {
|
|
29
|
+
productsScanned: number;
|
|
30
|
+
imagesScanned: number;
|
|
31
|
+
imagesWithText: number;
|
|
32
|
+
results: ProductScanResult[];
|
|
33
|
+
isPartial?: boolean;
|
|
34
|
+
stoppedReason?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function handleScanProducts(input: ProductScanInput, config: McpServerConfig): Promise<ScanProductsResult>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=scan-products.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-products.d.ts","sourceRoot":"","sources":["../../../../src/tools/shopify/scan-products.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAgHjE,UAAU,UAAU;IAClB,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA0CD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAgH7B"}
|