ebay-mcp-remote-edition 4.2.1 → 4.3.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.
@@ -6,6 +6,7 @@ import { MessageApi } from '../api/communication/message.js';
6
6
  import { NegotiationApi } from '../api/communication/negotiation.js';
7
7
  import { NotificationApi } from '../api/communication/notification.js';
8
8
  import { DeveloperApi } from '../api/developer/developer.js';
9
+ import { MediaApi } from '../api/media/media.js';
9
10
  import { InventoryApi } from '../api/listing-management/inventory.js';
10
11
  import { MetadataApi } from '../api/listing-metadata/metadata.js';
11
12
  import { TaxonomyApi } from '../api/listing-metadata/taxonomy.js';
@@ -41,6 +42,7 @@ export class EbaySellerApi {
41
42
  translation;
42
43
  edelivery;
43
44
  developer;
45
+ media;
44
46
  trading;
45
47
  constructor(config, context) {
46
48
  this.client = new EbayApiClient(config, context);
@@ -63,6 +65,7 @@ export class EbaySellerApi {
63
65
  this.translation = new TranslationApi(this.client);
64
66
  this.edelivery = new EDeliveryApi(this.client);
65
67
  this.developer = new DeveloperApi(this.client);
68
+ this.media = new MediaApi(this.client);
66
69
  const tradingClient = new TradingApiClient(this.client);
67
70
  this.trading = new TradingApi(tradingClient);
68
71
  }
@@ -0,0 +1,124 @@
1
+ import { getBaseUrl } from '../../config/environment.js';
2
+ import axios from 'axios';
3
+ /**
4
+ * Commerce Media API (v1_beta) - Upload and manage images via eBay Picture Services
5
+ * Based on: https://developer.ebay.com/api-docs/commerce/media/resources/image/from_url/methods
6
+ */
7
+ export class MediaApi {
8
+ client;
9
+ basePath = '/commerce/media/v1';
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ async getAccessToken() {
14
+ return await this.client.getOAuthClient().getAccessToken();
15
+ }
16
+ getMediaBaseUrl() {
17
+ const env = this.client.getConfig().environment;
18
+ return getBaseUrl(env);
19
+ }
20
+ /**
21
+ * Upload an image from a public URL to eBay Picture Services.
22
+ *
23
+ * Steps:
24
+ * 1. POST /commerce/media/v1/image/from_url to create the image
25
+ * 2. GET /commerce/media/v1/image/{imageId} to retrieve the hosted URL
26
+ *
27
+ * Supported formats: JPG, GIF, PNG, BMP, TIFF, AVIF, HEIC, WEBP
28
+ * Max file size: 10MB per image
29
+ *
30
+ * @param imageUrl - Public URL of the image to upload
31
+ * @param description - Optional description for the image
32
+ * @returns Object with image ID and eBay-hosted image URL
33
+ */
34
+ async createImageFromUrl(imageUrl, description) {
35
+ if (!imageUrl || typeof imageUrl !== 'string') {
36
+ throw new Error('imageUrl is required and must be a string');
37
+ }
38
+ const token = await this.getAccessToken();
39
+ const baseUrl = this.getMediaBaseUrl();
40
+ try {
41
+ // Step 1: Create image from URL
42
+ const body = { imageUrl };
43
+ if (description) {
44
+ Object.assign(body, { description });
45
+ }
46
+ const createResponse = await axios.post(`${baseUrl}${this.basePath}/image/from_url`, body, {
47
+ headers: {
48
+ Authorization: `Bearer ${token}`,
49
+ 'Content-Type': 'application/json',
50
+ Prefer: 'return=representation',
51
+ },
52
+ timeout: 30000,
53
+ });
54
+ // Extract image ID from response body or Location header
55
+ const responseData = createResponse.data;
56
+ const imageId = typeof responseData.id === 'string'
57
+ ? responseData.id
58
+ : createResponse.headers.location?.split('/').pop();
59
+ if (!imageId) {
60
+ throw new Error('No image ID returned from create endpoint');
61
+ }
62
+ // Step 2: Fetch image details to get the eBay-hosted URL
63
+ return await this.getImage(imageId);
64
+ }
65
+ catch (error) {
66
+ if (axios.isAxiosError(error)) {
67
+ const status = error.response?.status;
68
+ const data = error.response?.data;
69
+ const message = typeof data === 'object' && data !== null && 'errors' in data
70
+ ? data.errors?.[0]?.longMessage ||
71
+ data.errors?.[0]?.message ||
72
+ error.message
73
+ : error.message;
74
+ throw new Error(`Failed to upload image from URL (status ${status}): ${message}`, {
75
+ cause: error,
76
+ });
77
+ }
78
+ throw new Error(`Failed to upload image from URL: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
79
+ }
80
+ }
81
+ /**
82
+ * Get image details including the eBay-hosted URL.
83
+ *
84
+ * @param imageId - The image ID returned from createImageFromUrl
85
+ * @returns Image details including hosted URL
86
+ */
87
+ async getImage(imageId) {
88
+ if (!imageId || typeof imageId !== 'string') {
89
+ throw new Error('imageId is required and must be a string');
90
+ }
91
+ const token = await this.getAccessToken();
92
+ const baseUrl = this.getMediaBaseUrl();
93
+ try {
94
+ const response = await axios.get(`${baseUrl}${this.basePath}/image/${imageId}`, {
95
+ headers: {
96
+ Authorization: `Bearer ${token}`,
97
+ Accept: 'application/json',
98
+ },
99
+ timeout: 30000,
100
+ });
101
+ const data = response.data;
102
+ return {
103
+ id: data.id,
104
+ imageUrl: data.imageUrl,
105
+ description: typeof data.description === 'string' ? data.description : undefined,
106
+ };
107
+ }
108
+ catch (error) {
109
+ if (axios.isAxiosError(error)) {
110
+ const status = error.response?.status;
111
+ const data = error.response?.data;
112
+ const message = typeof data === 'object' && data !== null && 'errors' in data
113
+ ? data.errors?.[0]?.longMessage ||
114
+ data.errors?.[0]?.message ||
115
+ error.message
116
+ : error.message;
117
+ throw new Error(`Failed to get image details (status ${status}): ${message}`, {
118
+ cause: error,
119
+ });
120
+ }
121
+ throw new Error(`Failed to get image details: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
122
+ }
123
+ }
124
+ }
@@ -51,7 +51,8 @@ export class TradingApi {
51
51
  return items?.[0] || result;
52
52
  }
53
53
  async createListing(item) {
54
- return await this.client.execute('AddFixedPriceItem', { Item: item });
54
+ const transformed = this.client.transformItemForXML(item);
55
+ return await this.client.execute('AddFixedPriceItem', { Item: transformed });
55
56
  }
56
57
  async reviseListing(itemId, fields) {
57
58
  if (!itemId)
@@ -1,4 +1,4 @@
1
- import { MarketplaceId } from '../../types/ebay-enums.js';
1
+ import { MarketplaceId, Condition } from '../../types/ebay-enums.js';
2
2
  import { z } from 'zod';
3
3
  import { zodToJsonSchema } from '../../utils/zod-compat.js';
4
4
  import { bulkInventoryItemRequestSchema, bulkMigrateRequestSchema, bulkOfferRequestSchema, bulkPriceQuantityRequestSchema, bulkPublishRequestSchema, inventoryItemGroupSchema, inventoryItemSchema, listingFeesRequestSchema, locationSchema, offerSchema, productCompatibilitySchema, } from '../schemas.js';
@@ -29,10 +29,31 @@ export const inventoryTools = [
29
29
  },
30
30
  {
31
31
  name: 'ebay_create_inventory_item',
32
- description: 'Create or replace an inventory item.\n\nRequired OAuth Scope: sell.inventory\nMinimum Scope: https://api.ebay.com/oauth/api_scope/sell.inventory',
32
+ description: 'Create or replace an inventory item.\\n\\nRequired OAuth Scope: sell.inventory\\nMinimum Scope: https://api.ebay.com/oauth/api_scope/sell.inventory',
33
33
  inputSchema: {
34
34
  sku: z.string().describe('The seller-defined SKU'),
35
- inventoryItem: inventoryItemSchema.describe('Inventory item details'),
35
+ inventoryItem: inventoryItemSchema.describe('Inventory item details (availability, condition, etc.)'),
36
+ // Accept individual inventory fields as top-level fallback for LLMs that don't wrap in inventoryItem
37
+ availability: z
38
+ .any()
39
+ .optional()
40
+ .describe('[Fallback] Availability details if inventoryItem not provided'),
41
+ condition: z
42
+ .nativeEnum(Condition)
43
+ .optional()
44
+ .describe('[Fallback] Item condition if inventoryItem not provided'),
45
+ conditionDescription: z
46
+ .string()
47
+ .optional()
48
+ .describe('[Fallback] Condition description if inventoryItem not provided'),
49
+ product: z
50
+ .any()
51
+ .optional()
52
+ .describe('[Fallback] Product details if inventoryItem not provided'),
53
+ packageWeightAndSize: z
54
+ .any()
55
+ .optional()
56
+ .describe('[Fallback] Package weight/size if inventoryItem not provided'),
36
57
  },
37
58
  outputSchema: zodToJsonSchema(createInventoryItemOutputSchema, {
38
59
  name: 'CreateInventoryItemResponse',
@@ -150,4 +150,15 @@ export const tradingTools = [
150
150
  },
151
151
  annotations: { readOnlyHint: false },
152
152
  },
153
+ {
154
+ name: 'ebay_upload_images',
155
+ description: 'Upload images to eBay Picture Services using the Commerce Media API and get eBay-hosted image URLs for use in listings.\n\nUploads images from public URLs — eBay fetches and hosts them on their servers. The returned image URLs can be used in ebay_create_listing PicturesDetails.PictureURL array.\n\nSupports: JPG, GIF, PNG, BMP, TIFF, AVIF, HEIC, WEBP. Max 10MB per image.\n\nReturns a result for each uploaded image including the eBay-hosted URL and image ID.',
156
+ inputSchema: {
157
+ imageUrls: z
158
+ .array(z.string().describe('Public URL of the image to upload'))
159
+ .describe('Array of image URLs to upload (1 or more)'),
160
+ description: z.string().optional().describe('Optional description for the uploaded images'),
161
+ },
162
+ annotations: { readOnlyHint: false },
163
+ },
153
164
  ];
@@ -8,6 +8,37 @@ import { getAwaitingFeedbackSchema, getFeedbackRatingSummarySchema, getFeedbackS
8
8
  import { bulkUpdateConversationSchema, getConversationSchema, getConversationsSchema, sendMessageSchema, updateConversationSchema, } from '../utils/communication/message.js';
9
9
  import { findEligibleItemsSchema, getOffersToBuyersSchema, sendOfferToInterestedBuyersSchema, } from '../utils/communication/negotiation.js';
10
10
  import { createDestinationSchema, createSubscriptionFilterSchema, createSubscriptionSchema, deleteDestinationSchema, deleteSubscriptionFilterSchema, deleteSubscriptionSchema, disableSubscriptionSchema, enableSubscriptionSchema, getConfigSchema, getDestinationSchema, getPublicKeySchema, getSubscriptionFilterSchema, getSubscriptionSchema, getSubscriptionsSchema, getTopicSchema, getTopicsSchema, testSubscriptionSchema, updateConfigSchema, updateDestinationSchema, updateSubscriptionSchema, } from '../utils/communication/notification.js';
11
+ /**
12
+ * Normalize enum values that LLMs often send in wrong formats
13
+ * (lowercase, plural, hyphenated) to eBay API uppercase snake_case format
14
+ */
15
+ function normalizeEnumValue(value) {
16
+ return value.toUpperCase().replace(/-/g, '_').replace(/S$/, ''); // Remove trailing S (e.g., "DAYS" -> "DAY")
17
+ }
18
+ /**
19
+ * Normalize time duration unit values
20
+ */
21
+ function normalizeTimeUnit(value) {
22
+ const normalized = value.toUpperCase().replace(/-/g, '_');
23
+ const timeUnitMap = {
24
+ DAY: 'DAY',
25
+ DAYS: 'DAY',
26
+ BUSINESS_DAY: 'BUSINESS_DAY',
27
+ BUSINESS_DAYS: 'BUSINESS_DAY',
28
+ CALENDAR_DAY: 'CALENDAR_DAY',
29
+ HOUR: 'HOUR',
30
+ HOURS: 'HOUR',
31
+ MINUTE: 'MINUTE',
32
+ MINUTES: 'MINUTE',
33
+ SECOND: 'SECOND',
34
+ SECONDS: 'SECOND',
35
+ MONTH: 'MONTH',
36
+ MONTHS: 'MONTH',
37
+ YEAR: 'YEAR',
38
+ YEARS: 'YEAR',
39
+ };
40
+ return timeUnitMap[normalized] || normalized;
41
+ }
11
42
  /**
12
43
  * Get all tool definitions for the MCP server
13
44
  */
@@ -414,8 +445,17 @@ export async function executeTool(api, toolName, args) {
414
445
  case 'ebay_get_return_policies':
415
446
  return await api.account.getReturnPolicies(args.marketplaceId);
416
447
  // Fulfillment Policy CRUD
417
- case 'ebay_create_fulfillment_policy':
418
- return await api.account.createFulfillmentPolicy(args.policy);
448
+ case 'ebay_create_fulfillment_policy': {
449
+ // Normalize handlingTime.unit to valid enum values (LLMs often send lowercase/plural)
450
+ const policy = args.policy;
451
+ if (policy?.handlingTime && typeof policy.handlingTime === 'object') {
452
+ const ht = policy.handlingTime;
453
+ if (typeof ht.unit === 'string') {
454
+ ht.unit = normalizeTimeUnit(ht.unit);
455
+ }
456
+ }
457
+ return await api.account.createFulfillmentPolicy(policy);
458
+ }
419
459
  case 'ebay_get_fulfillment_policy':
420
460
  return await api.account.getFulfillmentPolicy(args.fulfillmentPolicyId);
421
461
  case 'ebay_get_fulfillment_policy_by_name':
@@ -495,8 +535,27 @@ export async function executeTool(api, toolName, args) {
495
535
  return await api.inventory.getInventoryItems(args.limit, args.offset);
496
536
  case 'ebay_get_inventory_item':
497
537
  return await api.inventory.getInventoryItem(args.sku);
498
- case 'ebay_create_inventory_item':
499
- return await api.inventory.createOrReplaceInventoryItem(args.sku, args.inventoryItem);
538
+ case 'ebay_create_inventory_item': {
539
+ // Handle both patterns: nested inventoryItem OR flat fields
540
+ let inventoryItemData = args.inventoryItem;
541
+ if (!inventoryItemData || typeof inventoryItemData !== 'object') {
542
+ // Merge fallback fields into inventoryItem
543
+ const fallbackFields = [
544
+ 'availability',
545
+ 'condition',
546
+ 'conditionDescription',
547
+ 'product',
548
+ 'packageWeightAndSize',
549
+ ];
550
+ inventoryItemData = {};
551
+ for (const field of fallbackFields) {
552
+ if (args[field] !== undefined) {
553
+ inventoryItemData[field] = args[field];
554
+ }
555
+ }
556
+ }
557
+ return await api.inventory.createOrReplaceInventoryItem(args.sku, inventoryItemData);
558
+ }
500
559
  case 'ebay_delete_inventory_item':
501
560
  return await api.inventory.deleteInventoryItem(args.sku);
502
561
  // Bulk Operations
@@ -540,10 +599,20 @@ export async function executeTool(api, toolName, args) {
540
599
  return await api.inventory.getOffers(args.sku, args.marketplaceId, args.limit);
541
600
  case 'ebay_get_offer':
542
601
  return await api.inventory.getOffer(args.offerId);
543
- case 'ebay_create_offer':
544
- return await api.inventory.createOffer(args.offer);
545
- case 'ebay_update_offer':
546
- return await api.inventory.updateOffer(args.offerId, args.offer);
602
+ case 'ebay_create_offer': {
603
+ const offer = args.offer;
604
+ if (offer?.format) {
605
+ offer.format = normalizeEnumValue(offer.format);
606
+ }
607
+ return await api.inventory.createOffer(offer);
608
+ }
609
+ case 'ebay_update_offer': {
610
+ const offer = args.offer;
611
+ if (offer?.format) {
612
+ offer.format = normalizeEnumValue(offer.format);
613
+ }
614
+ return await api.inventory.updateOffer(args.offerId, offer);
615
+ }
547
616
  case 'ebay_delete_offer':
548
617
  return await api.inventory.deleteOffer(args.offerId);
549
618
  case 'ebay_publish_offer':
@@ -1235,6 +1304,29 @@ export async function executeTool(api, toolName, args) {
1235
1304
  return await api.trading.endListing(args.itemId, args.reason);
1236
1305
  case 'ebay_relist_item':
1237
1306
  return await api.trading.relistItem(args.itemId, args.modifications);
1307
+ case 'ebay_upload_images': {
1308
+ const imageUrls = args.imageUrls;
1309
+ const description = args.description;
1310
+ const results = [];
1311
+ for (const imageUrl of imageUrls) {
1312
+ try {
1313
+ const result = await api.media.createImageFromUrl(imageUrl, description);
1314
+ results.push({ success: true, id: result.id, imageUrl: result.imageUrl });
1315
+ }
1316
+ catch (e) {
1317
+ results.push({
1318
+ success: false,
1319
+ error: e instanceof Error ? e.message : String(e),
1320
+ sourceUrl: imageUrl,
1321
+ });
1322
+ }
1323
+ }
1324
+ return {
1325
+ uploaded: results.filter((r) => r.success).length,
1326
+ failed: results.filter((r) => !r.success).length,
1327
+ results,
1328
+ };
1329
+ }
1238
1330
  default:
1239
1331
  throw new Error(`Unknown tool: ${toolName}`);
1240
1332
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { TimeDurationUnit, RegionType, ShippingCostType, ShippingOptionType, DepositType, RefundMethod, ReturnMethod, ReturnShippingCostPayer, Condition, LengthUnit, WeightUnit, PricingVisibility, FormatType, LocationType, MerchantLocationStatus, DayOfWeek, ReasonForRefund, FundingModel, MessageReferenceType, FeedbackRating, ReportedItemType, } from '../types/ebay-enums.js';
2
+ import { RegionType, ShippingCostType, ShippingOptionType, DepositType, RefundMethod, ReturnMethod, ReturnShippingCostPayer, Condition, LengthUnit, WeightUnit, PricingVisibility, FormatType, LocationType, MerchantLocationStatus, DayOfWeek, ReasonForRefund, FundingModel, MessageReferenceType, FeedbackRating, ReportedItemType, TimeDurationUnit, } from '../types/ebay-enums.js';
3
3
  /**
4
4
  * Reusable Zod schemas for eBay API tool input validation
5
5
  *
@@ -11,7 +11,9 @@ import { TimeDurationUnit, RegionType, ShippingCostType, ShippingOptionType, Dep
11
11
  // ============================================================================
12
12
  export const timeDurationSchema = z
13
13
  .object({
14
- unit: z.nativeEnum(TimeDurationUnit),
14
+ unit: z
15
+ .nativeEnum(TimeDurationUnit)
16
+ .describe('Time unit: DAY, BUSINESS_DAY, CALENDAR_DAY, HOUR, MINUTE, SECOND, MONTH, YEAR'),
15
17
  value: z.number(),
16
18
  })
17
19
  .passthrough();
@@ -233,7 +235,7 @@ export const offerSchema = z
233
235
  .object({
234
236
  sku: z.string(),
235
237
  marketplaceId: z.string(),
236
- format: z.nativeEnum(FormatType),
238
+ format: z.string().describe('Listing format: FIXED_PRICE (recommended) or AUCTION'),
237
239
  availableQuantity: z.number().optional(),
238
240
  categoryId: z.string().optional(),
239
241
  listingDescription: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ebay-mcp-remote-edition",
3
- "version": "4.2.1",
3
+ "version": "4.3.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",
@@ -69,21 +69,21 @@
69
69
  "dependencies": {
70
70
  "@modelcontextprotocol/sdk": "1.29.0",
71
71
  "@upstash/redis": "^1.37.0",
72
- "axios": "^1.14.0",
72
+ "axios": "^1.16.0",
73
73
  "chalk": "^5.6.2",
74
74
  "cors": "^2.8.6",
75
75
  "dotenv-stringify": "^3.0.1",
76
76
  "express": "^5.2.1",
77
- "fast-xml-parser": "^5.5.10",
77
+ "fast-xml-parser": "^5.7.2",
78
78
  "flatted": "^3.4.2",
79
79
  "helmet": "^8.1.0",
80
- "jose": "^6.2.2",
80
+ "jose": "^6.2.3",
81
81
  "jsonwebtoken": "^9.0.3",
82
82
  "playwright-core": "^1.59.1",
83
83
  "prompts": "^2.4.2",
84
84
  "update-notifier": "^7.3.1",
85
85
  "winston": "^3.19.0",
86
- "zod": "^4.3.6",
86
+ "zod": "^4.4.3",
87
87
  "zod-to-json-schema": "^3.25.2"
88
88
  },
89
89
  "devDependencies": {
@@ -91,27 +91,27 @@
91
91
  "@types/cors": "^2.8.19",
92
92
  "@types/express": "^5.0.6",
93
93
  "@types/jsonwebtoken": "^9.0.10",
94
- "@types/node": "^25.5.2",
94
+ "@types/node": "^25.6.0",
95
95
  "@types/prompts": "^2.4.9",
96
96
  "@types/supertest": "^7.2.0",
97
97
  "@types/update-notifier": "^6.0.8",
98
- "@vitest/coverage-v8": "^4.1.2",
99
- "@vitest/eslint-plugin": "^1.6.14",
100
- "@vitest/ui": "^4.1.2",
101
- "eslint": "^10.2.0",
98
+ "@vitest/coverage-v8": "^4.1.5",
99
+ "@vitest/eslint-plugin": "^1.6.16",
100
+ "@vitest/ui": "^4.1.5",
101
+ "eslint": "^10.3.0",
102
102
  "eslint-config-prettier": "^10.1.8",
103
103
  "eslint-plugin-n": "^17.24.0",
104
- "nock": "^14.0.12",
104
+ "nock": "^14.0.14",
105
105
  "openapi-typescript": "^7.13.0",
106
- "prettier": "^3.8.1",
106
+ "prettier": "^3.8.3",
107
107
  "supertest": "^7.2.2",
108
- "tsc-alias": "^1.8.16",
108
+ "tsc-alias": "^1.8.17",
109
109
  "tsx": "^4.21.0",
110
110
  "typescript": "^5.9.3",
111
- "typescript-eslint": "^8.58.0",
112
- "vitest": "^4.1.2"
111
+ "typescript-eslint": "^8.59.1",
112
+ "vitest": "^4.1.5"
113
113
  },
114
- "packageManager": "pnpm@10.33.0",
114
+ "packageManager": "pnpm@10.33.2",
115
115
  "engines": {
116
116
  "node": ">=18.0.0"
117
117
  },