ebay-mcp-remote-edition 4.4.0 → 4.5.1
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/build/api/media/media.js +88 -73
- package/build/index.js +0 -0
- package/build/scripts/update-api-status-doc.js +19 -1
- package/build/server-http.js +26 -1
- package/build/tools/index.js +135 -4
- package/build/utils/api-status-feed.js +4 -1
- package/package.json +24 -34
package/build/api/media/media.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { processImageForUpload, } from '../../utils/image-processor.js';
|
|
1
2
|
import axios from 'axios';
|
|
2
3
|
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
4
|
/**
|
|
5
5
|
* Commerce Media API (v1_beta) - Upload and manage images via eBay Picture Services
|
|
6
6
|
* Based on: https://developer.ebay.com/api-docs/commerce/media/resources/image/from_url/methods
|
|
@@ -21,15 +21,13 @@ export class MediaApi {
|
|
|
21
21
|
/**
|
|
22
22
|
* Upload an image from a public URL to eBay Picture Services.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 2. Validate dimensions (min 500px, max 4800px)
|
|
27
|
-
* 3. Resize if needed (maintain aspect ratio)
|
|
28
|
-
* 4. Optimize and convert to JPEG
|
|
29
|
-
* 5. Upload to eBay via multipart/form-data
|
|
30
|
-
* 6. Return hosted URL
|
|
24
|
+
* Primary flow: Pass URL directly to eBay's createImageFromUrl endpoint
|
|
25
|
+
* (eBay downloads and processes server-side — no local processing needed).
|
|
31
26
|
*
|
|
32
|
-
*
|
|
27
|
+
* Fallback: If eBay rejects the image (e.g., too small for 500px minimum),
|
|
28
|
+
* download → enlarge with Sharp → upload via createImageFromFile.
|
|
29
|
+
*
|
|
30
|
+
* Supported source formats: JPG, GIF, PNG, BMP, TIFF, AVIF, HEIC, WEBP
|
|
33
31
|
* Max file size: 10MB per image
|
|
34
32
|
*
|
|
35
33
|
* @param imageUrl - Public URL of the image to upload
|
|
@@ -43,42 +41,69 @@ export class MediaApi {
|
|
|
43
41
|
const token = await this.getAccessToken();
|
|
44
42
|
const baseUrl = this.getMediaBaseUrl();
|
|
45
43
|
try {
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
const requestBody = { imageUrl };
|
|
44
|
+
// Primary: Pass URL directly to eBay — they handle server-side download
|
|
45
|
+
const body = { imageUrl };
|
|
49
46
|
if (description) {
|
|
50
|
-
|
|
47
|
+
body.description = description;
|
|
51
48
|
}
|
|
52
|
-
const
|
|
49
|
+
const createResponse = await axios.post(`${baseUrl}${this.basePath}/image/create_image_from_url`, body, {
|
|
53
50
|
headers: {
|
|
54
51
|
Authorization: `Bearer ${token}`,
|
|
55
52
|
'Content-Type': 'application/json',
|
|
56
53
|
Accept: 'application/json',
|
|
54
|
+
Prefer: 'return=representation',
|
|
57
55
|
},
|
|
58
56
|
timeout: 30000,
|
|
59
57
|
});
|
|
60
|
-
const
|
|
61
|
-
const imageId =
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
const responseData = createResponse.data;
|
|
59
|
+
const imageId = typeof responseData.id === 'string'
|
|
60
|
+
? responseData.id
|
|
61
|
+
: createResponse.headers.location?.split('/').pop();
|
|
62
|
+
if (!imageId) {
|
|
63
|
+
throw new Error('No image ID returned from create endpoint');
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
throw new Error('No image ID returned from createImageFromUrl endpoint');
|
|
65
|
+
return await this.getImage(imageId);
|
|
67
66
|
}
|
|
68
|
-
catch (
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
const
|
|
67
|
+
catch (primaryError) {
|
|
68
|
+
// Fallback: if eBay rejects (e.g., image too small), download → Sharp enlarge → upload via file
|
|
69
|
+
if (axios.isAxiosError(primaryError)) {
|
|
70
|
+
const status = primaryError.response?.status;
|
|
71
|
+
// Only fallback on errors that suggest image quality/size issues
|
|
72
|
+
if (status && (status === 400 || status === 500)) {
|
|
73
|
+
try {
|
|
74
|
+
console.log(`[MediaApi] Direct URL upload failed (status ${status}), falling back to Sharp processing for: ${imageUrl}`);
|
|
75
|
+
// Download the image
|
|
76
|
+
const downloadResponse = await axios.get(imageUrl, {
|
|
77
|
+
responseType: 'arraybuffer',
|
|
78
|
+
timeout: 30000,
|
|
79
|
+
maxContentLength: 10 * 1024 * 1024, // 10MB max
|
|
80
|
+
});
|
|
81
|
+
const imageBuffer = Buffer.from(downloadResponse.data);
|
|
82
|
+
// Process with Sharp (enlarge if too small, convert to JPEG)
|
|
83
|
+
const processed = await processImageForUpload(imageBuffer);
|
|
84
|
+
// Upload via file endpoint
|
|
85
|
+
return await this.uploadProcessedImage(processed.buffer, processed.metadata, token, baseUrl, description);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// If fallback also fails, throw the original error (more actionable)
|
|
89
|
+
throw primaryError;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Re-throw non-retryable errors as-is
|
|
94
|
+
if (axios.isAxiosError(primaryError)) {
|
|
95
|
+
const status = primaryError.response?.status;
|
|
96
|
+
const data = primaryError.response?.data;
|
|
72
97
|
const message = typeof data === 'object' && data !== null && 'errors' in data
|
|
73
98
|
? data.errors?.[0]?.longMessage ||
|
|
74
99
|
data.errors?.[0]?.message ||
|
|
75
|
-
|
|
76
|
-
:
|
|
100
|
+
primaryError.message
|
|
101
|
+
: primaryError.message;
|
|
77
102
|
throw new Error(`Failed to upload image from URL (status ${status}): ${message}`, {
|
|
78
|
-
cause:
|
|
103
|
+
cause: primaryError,
|
|
79
104
|
});
|
|
80
105
|
}
|
|
81
|
-
throw new Error(`Failed to upload image from URL: ${
|
|
106
|
+
throw new Error(`Failed to upload image from URL: ${primaryError instanceof Error ? primaryError.message : 'Unknown error'}`, { cause: primaryError });
|
|
82
107
|
}
|
|
83
108
|
}
|
|
84
109
|
/**
|
|
@@ -105,41 +130,10 @@ export class MediaApi {
|
|
|
105
130
|
const baseUrl = this.getMediaBaseUrl();
|
|
106
131
|
try {
|
|
107
132
|
const fileBuffer = fs.readFileSync(filePath);
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
let body = '';
|
|
113
|
-
// File part
|
|
114
|
-
body += `--${boundary}\r\n`;
|
|
115
|
-
body += `Content-Disposition: form-data; name="imageFile"; filename="${fileName}"\r\n`;
|
|
116
|
-
body += `Content-Type: application/octet-stream\r\n\r\n`;
|
|
117
|
-
// Description part (optional)
|
|
118
|
-
if (description) {
|
|
119
|
-
body += `--${boundary}\r\n`;
|
|
120
|
-
body += `Content-Disposition: form-data; name="description"\r\n\r\n`;
|
|
121
|
-
body += `${description}\r\n`;
|
|
122
|
-
}
|
|
123
|
-
body += `--${boundary}--\r\n`;
|
|
124
|
-
const multipartBody = Buffer.concat([Buffer.from(body, 'utf-8'), Buffer.from(fileBuffer)]);
|
|
125
|
-
const createResponse = await axios.post(`${baseUrl}${this.basePath}/image/create_image_from_file`, multipartBody, {
|
|
126
|
-
headers: {
|
|
127
|
-
Authorization: `Bearer ${token}`,
|
|
128
|
-
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
129
|
-
Prefer: 'return=representation',
|
|
130
|
-
},
|
|
131
|
-
timeout: 30000,
|
|
132
|
-
});
|
|
133
|
-
// Extract image ID from response body or Location header
|
|
134
|
-
const responseData = createResponse.data;
|
|
135
|
-
const imageId = typeof responseData.id === 'string'
|
|
136
|
-
? responseData.id
|
|
137
|
-
: createResponse.headers.location?.split('/').pop();
|
|
138
|
-
if (!imageId) {
|
|
139
|
-
throw new Error('No image ID returned from create endpoint');
|
|
140
|
-
}
|
|
141
|
-
// Fetch image details to get the eBay-hosted URL
|
|
142
|
-
return await this.getImage(imageId);
|
|
133
|
+
// Process image — validate dimensions, enlarge to min 500px if too small,
|
|
134
|
+
// convert to JPEG, and optimize. Uses sharp library.
|
|
135
|
+
const processed = await processImageForUpload(fileBuffer);
|
|
136
|
+
return await this.uploadProcessedImage(processed.buffer, processed.metadata, token, baseUrl, description);
|
|
143
137
|
}
|
|
144
138
|
catch (error) {
|
|
145
139
|
if (axios.isAxiosError(error)) {
|
|
@@ -168,19 +162,34 @@ export class MediaApi {
|
|
|
168
162
|
* @returns Object with image ID and eBay-hosted image URL
|
|
169
163
|
*/
|
|
170
164
|
async uploadProcessedImage(buffer, metadata, token, baseUrl, description) {
|
|
171
|
-
// Build multipart/form-data body
|
|
165
|
+
// Build multipart/form-data body correctly:
|
|
166
|
+
// --boundary\r\n
|
|
167
|
+
// Content-Disposition: imageFile\r\n
|
|
168
|
+
// Content-Type: image/jpeg\r\n\r\n
|
|
169
|
+
// [IMAGE BINARY DATA]
|
|
170
|
+
// --boundary\r\n
|
|
171
|
+
// Content-Disposition: description\r\n\r\n
|
|
172
|
+
// [description text]
|
|
173
|
+
// --boundary--\r\n
|
|
172
174
|
const boundary = `----FormBoundary${Date.now()}`;
|
|
173
175
|
const fileName = `image_${Date.now()}.jpg`;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
const parts = [];
|
|
177
|
+
// Image file part — headers + binary data
|
|
178
|
+
const imageHeaders = `--${boundary}\r\n` +
|
|
179
|
+
`Content-Disposition: form-data; name="imageFile"; filename="${fileName}"\r\n` +
|
|
180
|
+
`Content-Type: image/jpeg\r\n\r\n`;
|
|
181
|
+
parts.push(Buffer.from(imageHeaders, 'utf-8'));
|
|
182
|
+
parts.push(buffer);
|
|
183
|
+
// Description part (optional)
|
|
177
184
|
if (description) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
185
|
+
const descPart = `--${boundary}\r\n` +
|
|
186
|
+
`Content-Disposition: form-data; name="description"\r\n\r\n` +
|
|
187
|
+
`${description}\r\n`;
|
|
188
|
+
parts.push(Buffer.from(descPart, 'utf-8'));
|
|
181
189
|
}
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
// Closing boundary
|
|
191
|
+
parts.push(Buffer.from(`--${boundary}--\r\n`, 'utf-8'));
|
|
192
|
+
const multipartBody = Buffer.concat(parts);
|
|
184
193
|
const createResponse = await axios.post(`${baseUrl}${this.basePath}/image/create_image_from_file`, multipartBody, {
|
|
185
194
|
headers: {
|
|
186
195
|
Authorization: `Bearer ${token}`,
|
|
@@ -219,9 +228,15 @@ export class MediaApi {
|
|
|
219
228
|
timeout: 30000,
|
|
220
229
|
});
|
|
221
230
|
const data = response.data;
|
|
231
|
+
let imageUrl = data.imageUrl;
|
|
232
|
+
// eBay Media API returns $_1.JPG thumbnail URL. Convert to full-size (s-l1600.jpg)
|
|
233
|
+
// which is required for listing images (500px minimum).
|
|
234
|
+
if (imageUrl?.includes('$_1.JPG')) {
|
|
235
|
+
imageUrl = imageUrl.replace('$_1.JPG', 's-l1600.jpg');
|
|
236
|
+
}
|
|
222
237
|
return {
|
|
223
238
|
id: data.id,
|
|
224
|
-
imageUrl:
|
|
239
|
+
imageUrl: imageUrl || '',
|
|
225
240
|
description: typeof data.description === 'string' ? data.description : undefined,
|
|
226
241
|
};
|
|
227
242
|
}
|
package/build/index.js
CHANGED
|
File without changes
|
|
@@ -32,7 +32,25 @@ function buildMarkdown(items) {
|
|
|
32
32
|
async function main() {
|
|
33
33
|
const { items, error } = await getApiStatusFeed({ limit: DEFAULT_LIMIT });
|
|
34
34
|
if (error && items.length === 0) {
|
|
35
|
-
|
|
35
|
+
// Feed is temporarily unavailable — write a fallback doc instead of failing
|
|
36
|
+
const fallback = [
|
|
37
|
+
'# eBay API Status (cached)',
|
|
38
|
+
'',
|
|
39
|
+
'The eBay API Status RSS feed is currently unavailable. Last attempt:',
|
|
40
|
+
`*${new Date().toISOString()}*`,
|
|
41
|
+
'',
|
|
42
|
+
`**Error:** ${error}`,
|
|
43
|
+
'',
|
|
44
|
+
'Full list: [developer.ebay.com/support/api-status](https://developer.ebay.com/support/api-status).',
|
|
45
|
+
'',
|
|
46
|
+
'---',
|
|
47
|
+
'',
|
|
48
|
+
'*Generated by [ebay-mcp-remote-edition](https://github.com/mrnajiboy/ebay-mcp-remote-edition) API status sync.*',
|
|
49
|
+
].join('\n');
|
|
50
|
+
writeFileSync(OUT_PATH, fallback, 'utf8');
|
|
51
|
+
console.warn(`Feed unavailable (${error}), wrote fallback to ${OUT_PATH}`);
|
|
52
|
+
process.exitCode = 0;
|
|
53
|
+
return;
|
|
36
54
|
}
|
|
37
55
|
const markdown = buildMarkdown(items);
|
|
38
56
|
writeFileSync(OUT_PATH, markdown, 'utf8');
|
package/build/server-http.js
CHANGED
|
@@ -966,17 +966,42 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
|
|
|
966
966
|
{ src: `${iconBaseUrl}/48x48.png`, mimeType: 'image/png', sizes: ['48x48'] },
|
|
967
967
|
],
|
|
968
968
|
});
|
|
969
|
+
// Configurable tool execution timeout (60s default — most tools complete in <10s)
|
|
970
|
+
// Longer than individual API timeouts (30s) to allow for token refresh + API chaining
|
|
971
|
+
const TOOL_TIMEOUT_MS = Number(process.env.MCP_TOOL_TIMEOUT_MS ?? 60_000);
|
|
969
972
|
const tools = getToolDefinitions();
|
|
970
973
|
for (const toolDef of tools) {
|
|
971
974
|
try {
|
|
972
975
|
server.registerTool(toolDef.name, { description: toolDef.description, inputSchema: toolDef.inputSchema }, async (args) => {
|
|
976
|
+
const startTime = Date.now();
|
|
973
977
|
try {
|
|
974
|
-
|
|
978
|
+
// Wrap tool execution with timeout to prevent indefinite hangs
|
|
979
|
+
const result = await Promise.race([
|
|
980
|
+
executeTool(api, toolDef.name, args),
|
|
981
|
+
new Promise((_, reject) => {
|
|
982
|
+
setTimeout(() => reject(new Error(`Tool ${toolDef.name} timed out after ${TOOL_TIMEOUT_MS}ms`)), TOOL_TIMEOUT_MS);
|
|
983
|
+
}),
|
|
984
|
+
]);
|
|
985
|
+
const duration = Date.now() - startTime;
|
|
986
|
+
serverLogger.info(`[tool-exec] ${toolDef.name}`, {
|
|
987
|
+
userId,
|
|
988
|
+
environment,
|
|
989
|
+
durationMs: duration,
|
|
990
|
+
status: 'success',
|
|
991
|
+
});
|
|
975
992
|
return {
|
|
976
993
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
977
994
|
};
|
|
978
995
|
}
|
|
979
996
|
catch (error) {
|
|
997
|
+
const duration = Date.now() - startTime;
|
|
998
|
+
serverLogger.warn(`[tool-exec] ${toolDef.name}`, {
|
|
999
|
+
userId,
|
|
1000
|
+
environment,
|
|
1001
|
+
durationMs: duration,
|
|
1002
|
+
status: 'error',
|
|
1003
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1004
|
+
});
|
|
980
1005
|
return {
|
|
981
1006
|
content: [
|
|
982
1007
|
{
|
package/build/tools/index.js
CHANGED
|
@@ -3,6 +3,31 @@ import { accountTools, analyticsTools, browseTools, communicationTools, develope
|
|
|
3
3
|
import { chatGptTools } from '../tools/chat-tools.js';
|
|
4
4
|
import { getApiStatusFeed } from '../utils/api-status-feed.js';
|
|
5
5
|
import { convertToTimestamp, validateTokenExpiry } from '../utils/date-converter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Tools disabled at config level because the eBay seller account doesn't support them.
|
|
8
|
+
* These tools return errors when called, so we exclude them from the tool list entirely.
|
|
9
|
+
*
|
|
10
|
+
* Categories:
|
|
11
|
+
* - Commerce Shipping: account not enrolled in commerce shipping services
|
|
12
|
+
* - VERO (Vendor Enforcement of Rights Online): account not enrolled in VERO program
|
|
13
|
+
* - Signing Keys: account doesn't have signing key permissions
|
|
14
|
+
*
|
|
15
|
+
* @see TASK-MCP.6 — Disable unsupported tools (config-level tools.exclude)
|
|
16
|
+
*/
|
|
17
|
+
const EXCLUDED_TOOLS = new Set([
|
|
18
|
+
// Commerce Shipping (account not enrolled)
|
|
19
|
+
'ebay_get_shipping_services',
|
|
20
|
+
'ebay_get_dropoff_sites',
|
|
21
|
+
'ebay_get_consign_preferences',
|
|
22
|
+
'ebay_get_battery_qualifications',
|
|
23
|
+
// VERO (account not enrolled)
|
|
24
|
+
'ebay_get_vero_reason_codes',
|
|
25
|
+
'ebay_create_vero_report',
|
|
26
|
+
// Signing Keys (account doesn't have permissions)
|
|
27
|
+
'ebay_suppress_violation',
|
|
28
|
+
'ebay_get_signing_keys',
|
|
29
|
+
'ebay_create_signing_key',
|
|
30
|
+
]);
|
|
6
31
|
// Import Zod schemas for input validation
|
|
7
32
|
import { getAwaitingFeedbackSchema, getFeedbackRatingSummarySchema, getFeedbackSchema, leaveFeedbackForBuyerSchema, respondToFeedbackSchema, } from '../utils/communication/feedback.js';
|
|
8
33
|
import { bulkUpdateConversationSchema, getConversationSchema, getConversationsSchema, sendMessageSchema, updateConversationSchema, } from '../utils/communication/message.js';
|
|
@@ -187,10 +212,11 @@ function normalizeTimeUnit(value) {
|
|
|
187
212
|
}
|
|
188
213
|
/**
|
|
189
214
|
* Get all tool definitions for the MCP server
|
|
215
|
+
* Filters out tools the eBay seller account doesn't support (see EXCLUDED_TOOLS)
|
|
190
216
|
*/
|
|
191
217
|
export function getToolDefinitions() {
|
|
192
218
|
const chatConnectorTools = chatGptTools.filter((tool) => tool.name === 'search' || tool.name === 'fetch');
|
|
193
|
-
|
|
219
|
+
const allTools = [
|
|
194
220
|
...chatConnectorTools,
|
|
195
221
|
...tokenManagementTools,
|
|
196
222
|
...accountTools,
|
|
@@ -206,6 +232,8 @@ export function getToolDefinitions() {
|
|
|
206
232
|
...developerTools,
|
|
207
233
|
...tradingTools,
|
|
208
234
|
];
|
|
235
|
+
// Filter out tools the account doesn't support
|
|
236
|
+
return allTools.filter((tool) => !EXCLUDED_TOOLS.has(tool.name));
|
|
209
237
|
}
|
|
210
238
|
/**
|
|
211
239
|
* Execute a tool based on its name
|
|
@@ -803,13 +831,19 @@ export async function executeTool(api, toolName, args) {
|
|
|
803
831
|
return await api.inventory.deleteOffer(args.offerId);
|
|
804
832
|
case 'ebay_publish_offer': {
|
|
805
833
|
const offerId = args.offerId;
|
|
806
|
-
// Pre-publish: ensure offer has categoryId + merchantLocationKey,
|
|
807
|
-
// and inventory item has brand/mpn pair + location mapping.
|
|
834
|
+
// Pre-publish: ensure offer has categoryId + merchantLocationKey + listingPolicies,
|
|
835
|
+
// and inventory item has brand/mpn pair + location mapping + itemSpecifics.
|
|
808
836
|
try {
|
|
809
837
|
const offer = await api.inventory.getOffer(offerId);
|
|
810
838
|
const offerData = offer;
|
|
811
839
|
const sku = offerData?.sku;
|
|
812
840
|
if (sku) {
|
|
841
|
+
// Default policy IDs (Hankuk Expo)
|
|
842
|
+
const defaultPolicies = {
|
|
843
|
+
paymentPolicyId: '259198675013',
|
|
844
|
+
returnPolicyId: '259198703013',
|
|
845
|
+
fulfillmentPolicyId: '259198453013',
|
|
846
|
+
};
|
|
813
847
|
// Fix offer-level fields first
|
|
814
848
|
let needsOfferUpdate = false;
|
|
815
849
|
const offerUpdate = {
|
|
@@ -829,10 +863,30 @@ export async function executeTool(api, toolName, args) {
|
|
|
829
863
|
offerUpdate.merchantLocationKey = 'seoul-warehouse';
|
|
830
864
|
needsOfferUpdate = true;
|
|
831
865
|
}
|
|
866
|
+
// Inject listingPolicies if missing
|
|
867
|
+
const existingPolicies = offerData.listingPolicies;
|
|
868
|
+
if (!existingPolicies?.paymentPolicyId ||
|
|
869
|
+
!existingPolicies.returnPolicyId ||
|
|
870
|
+
!existingPolicies.fulfillmentPolicyId) {
|
|
871
|
+
offerUpdate.listingPolicies = {
|
|
872
|
+
...defaultPolicies,
|
|
873
|
+
...(existingPolicies || {}),
|
|
874
|
+
};
|
|
875
|
+
needsOfferUpdate = true;
|
|
876
|
+
}
|
|
877
|
+
// Ensure pricingSummary has currency
|
|
878
|
+
const pricingSummary = offerData.pricingSummary;
|
|
879
|
+
if (pricingSummary?.price && typeof pricingSummary.price === 'object') {
|
|
880
|
+
const priceObj = pricingSummary.price;
|
|
881
|
+
if (!priceObj.currency) {
|
|
882
|
+
priceObj.currency = 'USD';
|
|
883
|
+
needsOfferUpdate = true;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
832
886
|
if (needsOfferUpdate) {
|
|
833
887
|
await api.inventory.updateOffer(offerId, offerUpdate);
|
|
834
888
|
}
|
|
835
|
-
// Fix inventory item fields (brand/mpn pair)
|
|
889
|
+
// Fix inventory item fields (brand/mpn pair + itemSpecifics)
|
|
836
890
|
try {
|
|
837
891
|
const items = await api.inventory.getInventoryItem(sku);
|
|
838
892
|
const itemData = Array.isArray(items)
|
|
@@ -868,6 +922,83 @@ export async function executeTool(api, toolName, args) {
|
|
|
868
922
|
catch (e) {
|
|
869
923
|
console.warn(`publish_offer pre-transform warning for ${offerId}: ${e}`);
|
|
870
924
|
}
|
|
925
|
+
// Use Trading API to publish with proper XML transform (Currency, Country, itemSpecifics)
|
|
926
|
+
try {
|
|
927
|
+
const offer = await api.inventory.getOffer(offerId);
|
|
928
|
+
const offerData = offer;
|
|
929
|
+
const sku = offerData?.sku;
|
|
930
|
+
if (sku) {
|
|
931
|
+
const items = await api.inventory.getInventoryItem(sku);
|
|
932
|
+
const itemData = Array.isArray(items) ? items?.[0] : items;
|
|
933
|
+
const inventoryItem = itemData;
|
|
934
|
+
const product = inventoryItem?.product || {};
|
|
935
|
+
// Get location for Country
|
|
936
|
+
const locationKey = offerData.merchantLocationKey || 'seoul-warehouse';
|
|
937
|
+
let country = 'KR';
|
|
938
|
+
try {
|
|
939
|
+
const location = await api.inventory.getInventoryLocation(locationKey);
|
|
940
|
+
const locData = location;
|
|
941
|
+
const address = locData?.address;
|
|
942
|
+
if (address?.country) {
|
|
943
|
+
country = address.country;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
// Default to KR
|
|
948
|
+
}
|
|
949
|
+
// Build Trading API item object
|
|
950
|
+
const pricingSummary = offerData.pricingSummary;
|
|
951
|
+
const priceObj = pricingSummary?.price;
|
|
952
|
+
const tradingItem = {
|
|
953
|
+
SKU: sku,
|
|
954
|
+
Title: product.title || offerData.title || sku,
|
|
955
|
+
Description: product.description || offerData.listingDescription || '',
|
|
956
|
+
PrimaryCategory: { CategoryID: offerData.categoryId || '176984' },
|
|
957
|
+
StartPrice: priceObj?.value || '0',
|
|
958
|
+
Currency: priceObj?.currency || 'USD',
|
|
959
|
+
Quantity: offerData.availableQuantity || 1,
|
|
960
|
+
ListingDuration: 'GTC',
|
|
961
|
+
ListingType: 'FixedPriceItem',
|
|
962
|
+
ConditionID: offerData.condition === 'NEW' ? 1000 : 3000,
|
|
963
|
+
Country: country,
|
|
964
|
+
PaymentMethods: ['PayPal'],
|
|
965
|
+
DispatchTimeMax: 3,
|
|
966
|
+
ShippingServiceOptions: {
|
|
967
|
+
ShippingService: 'USPSGround',
|
|
968
|
+
ShippingServiceCost: { Value: '0', CurrencyId: 'USD' },
|
|
969
|
+
},
|
|
970
|
+
ReturnPolicy: {
|
|
971
|
+
ReturnsAcceptedOption: 'ReturnsAccepted',
|
|
972
|
+
ReturnsWithinOption: 'Days_30',
|
|
973
|
+
Description: 'Returns accepted within 30 days.',
|
|
974
|
+
RefundOption: 'MoneyBack',
|
|
975
|
+
},
|
|
976
|
+
};
|
|
977
|
+
// Add itemSpecifics from inventory item product aspects
|
|
978
|
+
const aspects = product.aspects;
|
|
979
|
+
if (aspects && Object.keys(aspects).length > 0) {
|
|
980
|
+
tradingItem.ItemSpecifics = {
|
|
981
|
+
NameValueList: Object.entries(aspects).map(([name, value]) => ({
|
|
982
|
+
Name: name,
|
|
983
|
+
Value: Array.isArray(value) ? value : [value],
|
|
984
|
+
})),
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
// Add pictures
|
|
988
|
+
const imageUrls = product.imageUrls;
|
|
989
|
+
if (imageUrls && imageUrls.length > 0) {
|
|
990
|
+
tradingItem.PictureDetails = {
|
|
991
|
+
PictureURL: imageUrls.length === 1 ? imageUrls[0] : imageUrls,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
// Publish via Trading API with transform
|
|
995
|
+
return await api.trading.createListing(tradingItem);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
console.warn(`publish_offer Trading API warning for ${offerId}: ${e}`);
|
|
1000
|
+
// Fallback to Inventory API if Trading API fails
|
|
1001
|
+
}
|
|
871
1002
|
return await api.inventory.publishOffer(offerId);
|
|
872
1003
|
}
|
|
873
1004
|
case 'ebay_withdraw_offer':
|
|
@@ -47,7 +47,10 @@ export async function getApiStatusFeed(options = {}) {
|
|
|
47
47
|
const response = await axios.get(RSS_URL, {
|
|
48
48
|
timeout: 15_000,
|
|
49
49
|
responseType: 'text',
|
|
50
|
-
headers: {
|
|
50
|
+
headers: {
|
|
51
|
+
Accept: 'application/rss+xml, application/xml, text/xml',
|
|
52
|
+
'User-Agent': 'ebay-mcp-remote-edition/1.0 (GitHub Actions CI; +https://github.com/mrnajiboy/ebay-mcp-remote-edition)',
|
|
53
|
+
},
|
|
51
54
|
});
|
|
52
55
|
const xml = response.data;
|
|
53
56
|
const parser = new XMLParser({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebay-mcp-remote-edition",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"description": "Remote + Local MCP server for eBay APIs - provides access to eBay developer functionality through MCP (Model Context Protocol)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -8,29 +8,6 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"ebay-mcp": "build/index.js"
|
|
10
10
|
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc && tsc-alias",
|
|
13
|
-
"check-for-updates": "npx -y npm-check-updates",
|
|
14
|
-
"update": "npx -y npm-check-updates -u && npm install",
|
|
15
|
-
"playwright:install": "npx -y playwright@1.59.1 install chromium",
|
|
16
|
-
"start": "node build/scripts/env-check.js node build/index.js",
|
|
17
|
-
"start:http": "node build/scripts/env-check.js node build/server-http.js",
|
|
18
|
-
"dev": "tsx src/scripts/env-check.ts tsx src/index.ts",
|
|
19
|
-
"dev:http": "tsx src/scripts/env-check.ts tsx src/server-http.ts",
|
|
20
|
-
"research:bootstrap": "node build/scripts/env-check.js node build/scripts/bootstrap-ebay-research-session.js",
|
|
21
|
-
"research:inspect-session": "node build/scripts/env-check.js node build/scripts/inspect-ebay-research-session.js",
|
|
22
|
-
"research:check-browser": "node build/scripts/env-check.js node build/scripts/check-playwright.js",
|
|
23
|
-
"test": "vitest run",
|
|
24
|
-
"test:coverage": "vitest run --coverage",
|
|
25
|
-
"setup": "tsx src/scripts/env-check.ts tsx src/scripts/setup.ts",
|
|
26
|
-
"sync": "tsx src/scripts/dev-sync.ts",
|
|
27
|
-
"diagnose": "tsx src/scripts/env-check.ts tsx src/scripts/diagnostics.ts",
|
|
28
|
-
"typecheck": "tsc --noEmit",
|
|
29
|
-
"check": "tsc --noEmit && eslint . && prettier --check \"src/**/*.{ts,js,json,md}\"",
|
|
30
|
-
"fix": "eslint . --fix && prettier --write \"src/**/*.{ts,js,json,md}\"",
|
|
31
|
-
"env:encrypt": "npx -y @dotenvx/dotenvx encrypt",
|
|
32
|
-
"env:decrypt": "npx -y @dotenvx/dotenvx decrypt"
|
|
33
|
-
},
|
|
34
11
|
"keywords": [
|
|
35
12
|
"mcp",
|
|
36
13
|
"ebay",
|
|
@@ -111,17 +88,30 @@
|
|
|
111
88
|
"typescript-eslint": "^8.59.2",
|
|
112
89
|
"vitest": "^4.1.5"
|
|
113
90
|
},
|
|
114
|
-
"packageManager": "pnpm@10.33.2",
|
|
115
91
|
"engines": {
|
|
116
92
|
"node": ">=18.0.0"
|
|
117
93
|
},
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "tsc && tsc-alias",
|
|
96
|
+
"check-for-updates": "npx -y npm-check-updates",
|
|
97
|
+
"update": "npx -y npm-check-updates -u && npm install",
|
|
98
|
+
"playwright:install": "npx -y playwright@1.59.1 install chromium",
|
|
99
|
+
"start": "node build/scripts/env-check.js node build/index.js",
|
|
100
|
+
"start:http": "node build/scripts/env-check.js node build/server-http.js",
|
|
101
|
+
"dev": "tsx src/scripts/env-check.ts tsx src/index.ts",
|
|
102
|
+
"dev:http": "tsx src/scripts/env-check.ts tsx src/server-http.ts",
|
|
103
|
+
"research:bootstrap": "node build/scripts/env-check.js node build/scripts/bootstrap-ebay-research-session.js",
|
|
104
|
+
"research:inspect-session": "node build/scripts/env-check.js node build/scripts/inspect-ebay-research-session.js",
|
|
105
|
+
"research:check-browser": "node build/scripts/env-check.js node build/scripts/check-playwright.js",
|
|
106
|
+
"test": "vitest run",
|
|
107
|
+
"test:coverage": "vitest run --coverage",
|
|
108
|
+
"setup": "tsx src/scripts/env-check.ts tsx src/scripts/setup.ts",
|
|
109
|
+
"sync": "tsx src/scripts/dev-sync.ts",
|
|
110
|
+
"diagnose": "tsx src/scripts/env-check.ts tsx src/scripts/diagnostics.ts",
|
|
111
|
+
"typecheck": "tsc --noEmit",
|
|
112
|
+
"check": "tsc --noEmit && eslint . && prettier --check \"src/**/*.{ts,js,json,md}\"",
|
|
113
|
+
"fix": "eslint . --fix && prettier --write \"src/**/*.{ts,js,json,md}\"",
|
|
114
|
+
"env:encrypt": "npx -y @dotenvx/dotenvx encrypt",
|
|
115
|
+
"env:decrypt": "npx -y @dotenvx/dotenvx decrypt"
|
|
126
116
|
}
|
|
127
|
-
}
|
|
117
|
+
}
|