ebay-mcp 1.7.2 → 1.7.3

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/README.md CHANGED
@@ -595,8 +595,7 @@ The diagnostic tool checks:
595
595
  If you're still experiencing issues:
596
596
 
597
597
  1. Check existing [GitHub Issues](https://github.com/YosefHayim/ebay-mcp/issues)
598
- 2. Review [GitHub Discussions](https://github.com/YosefHayim/ebay-mcp/discussions)
599
- 3. Create a new issue with:
598
+ 2. Create a new issue with:
600
599
  - Your diagnostic report (`npm run diagnose:export`)
601
600
  - Steps to reproduce the problem
602
601
  - Error messages or logs
@@ -628,7 +627,6 @@ Check current eBay API health, incidents, and fixes:
628
627
 
629
628
  ### Support
630
629
 
631
- - [GitHub Discussions](https://github.com/YosefHayim/ebay-mcp/discussions) - Community Q&A and general discussions
632
630
  - [Issue Tracker](https://github.com/YosefHayim/ebay-mcp/issues) - Bug reports and feature requests
633
631
  - [Bug Report Template](BUG_REPORT.md) - Detailed bug reporting guide
634
632
 
@@ -0,0 +1,101 @@
1
+ import axios from 'axios';
2
+ import { XMLBuilder, XMLParser } from 'fast-xml-parser';
3
+ import { apiLogger } from '../utils/logger.js';
4
+ const COMPAT_LEVEL = '1451';
5
+ const SITE_ID = '0';
6
+ export class TradingApiClient {
7
+ restClient;
8
+ baseUrl;
9
+ parser;
10
+ builder;
11
+ constructor(restClient) {
12
+ this.restClient = restClient;
13
+ const env = restClient.getConfig().environment;
14
+ this.baseUrl =
15
+ env === 'sandbox'
16
+ ? 'https://api.sandbox.ebay.com'
17
+ : 'https://api.ebay.com';
18
+ this.parser = new XMLParser({
19
+ ignoreAttributes: false,
20
+ removeNSPrefix: true,
21
+ parseTagValue: true,
22
+ isArray: (_name) => {
23
+ const arrayTags = [
24
+ 'Item',
25
+ 'Errors',
26
+ 'Error',
27
+ 'NameValueList',
28
+ 'Value',
29
+ 'ShippingServiceOptions',
30
+ 'InternationalShippingServiceOption',
31
+ 'PaymentMethods',
32
+ 'PictureURL',
33
+ 'CompatibilityList',
34
+ 'Variation',
35
+ ];
36
+ return arrayTags.includes(_name);
37
+ },
38
+ });
39
+ this.builder = new XMLBuilder({
40
+ ignoreAttributes: false,
41
+ format: true,
42
+ suppressEmptyNode: true,
43
+ });
44
+ }
45
+ getBaseUrl() {
46
+ return this.baseUrl;
47
+ }
48
+ async execute(callName, params) {
49
+ const token = await this.restClient.getOAuthClient().getAccessToken();
50
+ const requestTag = `${callName}Request`;
51
+ const responseTag = `${callName}Response`;
52
+ const xmlObj = {};
53
+ xmlObj[requestTag] = {
54
+ '@_xmlns': 'urn:ebay:apis:eBLBaseComponents',
55
+ ...params,
56
+ };
57
+ const xmlBody = `<?xml version="1.0" encoding="utf-8"?>\n${this.builder.build(xmlObj)}`;
58
+ apiLogger.debug(`Trading API ${callName}`, { xmlBody });
59
+ let response;
60
+ try {
61
+ response = await axios.post(`${this.baseUrl}/ws/api.dll`, xmlBody, {
62
+ headers: {
63
+ 'X-EBAY-API-SITEID': SITE_ID,
64
+ 'X-EBAY-API-COMPATIBILITY-LEVEL': COMPAT_LEVEL,
65
+ 'X-EBAY-API-CALL-NAME': callName,
66
+ 'X-EBAY-API-IAF-TOKEN': token,
67
+ 'Content-Type': 'text/xml',
68
+ },
69
+ timeout: 30000,
70
+ });
71
+ }
72
+ catch (error) {
73
+ const message = error instanceof Error ? error.message : 'Unknown HTTP error';
74
+ throw new Error(`Trading API ${callName} request failed: ${message}`);
75
+ }
76
+ let parsed;
77
+ try {
78
+ parsed = this.parser.parse(response.data);
79
+ }
80
+ catch (e) {
81
+ throw new Error(`Failed to parse Trading API ${callName} response: ${e instanceof Error ? e.message : String(e)}`);
82
+ }
83
+ const result = (parsed[responseTag] || parsed);
84
+ // Log warnings without failing
85
+ if (result.Ack === 'Warning') {
86
+ apiLogger.warn(`Trading API ${callName} returned warnings`, {
87
+ errors: result.Errors,
88
+ });
89
+ }
90
+ // Check for eBay errors
91
+ if (result.Ack === 'Failure' || result.Ack === 'PartialFailure') {
92
+ const errors = result.Errors;
93
+ const firstError = Array.isArray(errors) ? errors[0] : errors;
94
+ const message = firstError?.ShortMessage ||
95
+ firstError?.LongMessage ||
96
+ 'Unknown Trading API error';
97
+ throw new Error(message);
98
+ }
99
+ return result;
100
+ }
101
+ }
@@ -18,6 +18,8 @@ import { EDeliveryApi } from '../api/other/edelivery.js';
18
18
  import { IdentityApi } from '../api/other/identity.js';
19
19
  import { TranslationApi } from '../api/other/translation.js';
20
20
  import { VeroApi } from '../api/other/vero.js';
21
+ import { TradingApiClient } from '../api/client-trading.js';
22
+ import { TradingApi } from '../api/trading/trading.js';
21
23
  /**
22
24
  * Main API facade providing access to all eBay APIs
23
25
  */
@@ -43,6 +45,7 @@ export class EbaySellerApi {
43
45
  translation;
44
46
  edelivery;
45
47
  developer;
48
+ trading;
46
49
  constructor(config) {
47
50
  this.client = new EbayApiClient(config);
48
51
  // Initialize API category handlers
@@ -65,6 +68,8 @@ export class EbaySellerApi {
65
68
  this.translation = new TranslationApi(this.client);
66
69
  this.edelivery = new EDeliveryApi(this.client);
67
70
  this.developer = new DeveloperApi(this.client);
71
+ const tradingClient = new TradingApiClient(this.client);
72
+ this.trading = new TradingApi(tradingClient);
68
73
  }
69
74
  /**
70
75
  * Initialize the API (load tokens from storage)
@@ -123,3 +128,5 @@ export * from '../api/other/identity.js';
123
128
  export * from '../api/other/translation.js';
124
129
  export * from '../api/other/vero.js';
125
130
  export * from '../api/developer/developer.js';
131
+ export * from '../api/trading/trading.js';
132
+ export * from '../api/client-trading.js';
@@ -0,0 +1,78 @@
1
+ export class TradingApi {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async getActiveListings(page = 1, entriesPerPage = 50) {
7
+ const result = await this.client.execute('GetMyeBaySelling', {
8
+ ActiveList: {
9
+ Sort: 'TimeLeft',
10
+ Pagination: {
11
+ EntriesPerPage: entriesPerPage,
12
+ PageNumber: page,
13
+ },
14
+ },
15
+ });
16
+ const activeList = result.ActiveList;
17
+ const itemArray = activeList?.ItemArray;
18
+ const items = itemArray?.Item || [];
19
+ const pagination = activeList?.PaginationResult;
20
+ const listings = items.map((item) => {
21
+ const sellingStatus = item.SellingStatus;
22
+ const currentPrice = sellingStatus?.CurrentPrice;
23
+ const priceValue = typeof currentPrice === 'object' && currentPrice !== null
24
+ ? Number(currentPrice['#text'] || 0)
25
+ : Number(currentPrice || 0);
26
+ return {
27
+ itemId: String(item.ItemID || ''),
28
+ title: String(item.Title || ''),
29
+ sku: String(item.SKU || ''),
30
+ quantity: Number(item.Quantity || 0),
31
+ quantityAvailable: Number(item.QuantityAvailable || 0),
32
+ currentPrice: priceValue,
33
+ watchCount: Number(item.WatchCount || 0),
34
+ listingType: String(item.ListingType || ''),
35
+ };
36
+ });
37
+ return {
38
+ listings,
39
+ total: Number(pagination?.TotalNumberOfEntries || 0),
40
+ totalPages: Number(pagination?.TotalNumberOfPages || 0),
41
+ };
42
+ }
43
+ async getListing(itemId) {
44
+ if (!itemId)
45
+ throw new Error('itemId is required');
46
+ const result = await this.client.execute('GetItem', {
47
+ ItemID: itemId,
48
+ DetailLevel: 'ReturnAll',
49
+ });
50
+ const items = result.Item;
51
+ return items?.[0] || result;
52
+ }
53
+ async createListing(item) {
54
+ return await this.client.execute('AddFixedPriceItem', { Item: item });
55
+ }
56
+ async reviseListing(itemId, fields) {
57
+ if (!itemId)
58
+ throw new Error('itemId is required');
59
+ return await this.client.execute('ReviseFixedPriceItem', {
60
+ Item: { ...fields, ItemID: itemId },
61
+ });
62
+ }
63
+ async endListing(itemId, reason = 'NotAvailable') {
64
+ if (!itemId)
65
+ throw new Error('itemId is required');
66
+ return await this.client.execute('EndFixedPriceItem', {
67
+ ItemID: itemId,
68
+ EndingReason: reason,
69
+ });
70
+ }
71
+ async relistItem(itemId, modifications) {
72
+ if (!itemId)
73
+ throw new Error('itemId is required');
74
+ return await this.client.execute('RelistFixedPriceItem', {
75
+ Item: { ...modifications, ItemID: itemId },
76
+ });
77
+ }
78
+ }
@@ -1,6 +1,8 @@
1
1
  import axios from 'axios';
2
2
  import { getBaseUrl, getDefaultScopes } from '../config/environment.js';
3
3
  import { LocaleEnum } from '../types/ebay-enums.js';
4
+ import dotenv from 'dotenv';
5
+ import stringify from 'dotenv-stringify';
4
6
  import { existsSync, readFileSync, writeFileSync } from 'fs';
5
7
  import { join } from 'path';
6
8
  import { authLogger } from '../utils/logger.js';
@@ -10,23 +12,13 @@ import { authLogger } from '../utils/logger.js';
10
12
  function updateEnvFile(updates) {
11
13
  try {
12
14
  const envPath = join(process.cwd(), '.env');
13
- let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
14
- // Update each key-value pair
15
- for (const [key, value] of Object.entries(updates)) {
16
- // Match the key with or without value, handling comments
17
- const regex = new RegExp(`^(#\\s*)?${key}=.*$`, 'gm');
18
- const newLine = `${key}=${value}`;
19
- if (regex.test(envContent)) {
20
- // Update existing key (uncomment if needed)
21
- envContent = envContent.replace(regex, newLine);
22
- }
23
- else {
24
- // Add new key at the end
25
- envContent += `\n${newLine}`;
26
- }
27
- }
28
- writeFileSync(envPath, envContent, 'utf-8');
29
- // Tokens updated silently - console output interferes with MCP JSON protocol
15
+ const existingEnv = existsSync(envPath) ? dotenv.parse(readFileSync(envPath, 'utf-8')) : {};
16
+ // Merge new updates into existing environment object
17
+ const mergedEnv = { ...existingEnv, ...updates };
18
+ // Securely stringify the merged environment object
19
+ const safeEnvContent = stringify(mergedEnv);
20
+ // Write the updated content back to the .env file
21
+ writeFileSync(envPath, safeEnvContent, 'utf-8');
30
22
  }
31
23
  catch (_error) {
32
24
  // Silent failure - error logging interferes with MCP JSON protocol
@@ -21,8 +21,9 @@ import { taxonomyTools } from './taxonomy.js';
21
21
  import { communicationTools } from './communication.js';
22
22
  import { otherApiTools } from './other.js';
23
23
  import { developerTools } from './developer.js';
24
+ import { tradingTools } from './trading.js';
24
25
  // Export individual categories
25
- export { tokenManagementTools, accountTools, inventoryTools, fulfillmentTools, marketingTools, analyticsTools, metadataTools, taxonomyTools, communicationTools, otherApiTools, developerTools, };
26
+ export { tokenManagementTools, accountTools, inventoryTools, fulfillmentTools, marketingTools, analyticsTools, metadataTools, taxonomyTools, communicationTools, otherApiTools, developerTools, tradingTools, };
26
27
  // Export all tools as a single array
27
28
  export const allTools = [
28
29
  ...tokenManagementTools,
@@ -36,4 +37,5 @@ export const allTools = [
36
37
  ...communicationTools,
37
38
  ...otherApiTools,
38
39
  ...developerTools,
40
+ ...tradingTools,
39
41
  ];
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+ export const tradingTools = [
3
+ {
4
+ name: 'ebay_get_active_listings',
5
+ description: 'Get all active fixed-price listings with SKU, quantity, price, and watch count.\n\nUses the Trading API (GetMyeBaySelling). Returns listings created via any method (UI, Trading API, or REST API).\n\nRequired: User OAuth token.',
6
+ inputSchema: {
7
+ page: z.number().optional().describe('Page number (default 1)'),
8
+ entriesPerPage: z
9
+ .number()
10
+ .optional()
11
+ .describe('Items per page, max 200 (default 50)'),
12
+ },
13
+ annotations: { readOnlyHint: true },
14
+ },
15
+ {
16
+ name: 'ebay_get_listing',
17
+ description: 'Get full details for a single listing by item ID.\n\nUses the Trading API (GetItem). Returns all listing fields including description, specifics, shipping, and images.\n\nRequired: User OAuth token.',
18
+ inputSchema: {
19
+ itemId: z
20
+ .string()
21
+ .describe('The eBay item ID (e.g., "167382780779")'),
22
+ },
23
+ annotations: { readOnlyHint: true },
24
+ },
25
+ {
26
+ name: 'ebay_create_listing',
27
+ description: 'Create a new fixed-price listing.\n\nUses the Trading API (AddFixedPriceItem). Requires complete item details.\n\nRequired: User OAuth token.',
28
+ inputSchema: {
29
+ item: z
30
+ .record(z.unknown())
31
+ .describe('Item details object. Required fields: Title, PrimaryCategory.CategoryID, StartPrice, ConditionID, Country, Currency, DispatchTimeMax, ListingDuration, ListingType ("FixedPriceItem"), Quantity, SKU.'),
32
+ },
33
+ annotations: { readOnlyHint: false },
34
+ },
35
+ {
36
+ name: 'ebay_revise_listing',
37
+ description: 'Revise an existing fixed-price listing. Update quantity, price, title, description, or any other field.\n\nUses the Trading API (ReviseFixedPriceItem). Only send the fields you want to change.\n\nExamples:\n- Update quantity: { "Quantity": 10 }\n- Update price: { "StartPrice": 14.99 }\n- Update title: { "Title": "New Title" }\n- Multiple fields: { "Quantity": 10, "StartPrice": 14.99 }\n\nRequired: User OAuth token.',
38
+ inputSchema: {
39
+ itemId: z.string().describe('The eBay item ID to revise'),
40
+ fields: z
41
+ .record(z.unknown())
42
+ .describe('Fields to update (e.g., { "Quantity": 10, "StartPrice": 14.99 })'),
43
+ },
44
+ annotations: { readOnlyHint: false },
45
+ },
46
+ {
47
+ name: 'ebay_end_listing',
48
+ description: 'End/remove an active fixed-price listing.\n\nUses the Trading API (EndFixedPriceItem).\n\nRequired: User OAuth token.',
49
+ inputSchema: {
50
+ itemId: z.string().describe('The eBay item ID to end'),
51
+ reason: z
52
+ .enum([
53
+ 'NotAvailable',
54
+ 'Incorrect',
55
+ 'LostOrBroken',
56
+ 'OtherListingError',
57
+ 'SellToHighBidder',
58
+ ])
59
+ .optional()
60
+ .describe('Reason for ending (default: NotAvailable)'),
61
+ },
62
+ annotations: { readOnlyHint: false, destructiveHint: true },
63
+ },
64
+ {
65
+ name: 'ebay_relist_item',
66
+ description: 'Relist an ended fixed-price listing, optionally with modifications.\n\nUses the Trading API (RelistFixedPriceItem).\n\nRequired: User OAuth token.',
67
+ inputSchema: {
68
+ itemId: z.string().describe('The eBay item ID to relist'),
69
+ modifications: z
70
+ .record(z.unknown())
71
+ .optional()
72
+ .describe('Optional fields to change when relisting (e.g., { "Quantity": 20 })'),
73
+ },
74
+ annotations: { readOnlyHint: false },
75
+ },
76
+ ];
@@ -1,5 +1,5 @@
1
1
  import { getOAuthAuthorizationUrl, validateScopes } from '../config/environment.js';
2
- import { accountTools, analyticsTools, communicationTools, developerTools, fulfillmentTools, inventoryTools, marketingTools, metadataTools, otherApiTools, taxonomyTools, tokenManagementTools, } from '../tools/definitions/index.js';
2
+ import { accountTools, analyticsTools, communicationTools, developerTools, fulfillmentTools, inventoryTools, marketingTools, metadataTools, otherApiTools, taxonomyTools, tradingTools, tokenManagementTools, } from '../tools/definitions/index.js';
3
3
  import { chatGptTools } from '../tools/tool-definitions.js';
4
4
  import { getApiStatusFeed } from '../utils/api-status-feed.js';
5
5
  import { convertToTimestamp, validateTokenExpiry } from '../utils/date-converter.js';
@@ -26,6 +26,7 @@ export function getToolDefinitions() {
26
26
  ...communicationTools,
27
27
  ...otherApiTools,
28
28
  ...developerTools,
29
+ ...tradingTools,
29
30
  ];
30
31
  }
31
32
  /**
@@ -1180,6 +1181,19 @@ export async function executeTool(api, toolName, args) {
1180
1181
  return await api.developer.createSigningKey(args.signingKeyCipher ? { signingKeyCipher: args.signingKeyCipher } : undefined);
1181
1182
  case 'ebay_get_signing_key':
1182
1183
  return await api.developer.getSigningKey(args.signingKeyId);
1184
+ // Trading API - Listing Management
1185
+ case 'ebay_get_active_listings':
1186
+ return await api.trading.getActiveListings(args.page, args.entriesPerPage);
1187
+ case 'ebay_get_listing':
1188
+ return await api.trading.getListing(args.itemId);
1189
+ case 'ebay_create_listing':
1190
+ return await api.trading.createListing(args.item);
1191
+ case 'ebay_revise_listing':
1192
+ return await api.trading.reviseListing(args.itemId, args.fields);
1193
+ case 'ebay_end_listing':
1194
+ return await api.trading.endListing(args.itemId, args.reason);
1195
+ case 'ebay_relist_item':
1196
+ return await api.trading.relistItem(args.itemId, args.modifications);
1183
1197
  default:
1184
1198
  throw new Error(`Unknown tool: ${toolName}`);
1185
1199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ebay-mcp",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "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",
@@ -54,11 +54,12 @@
54
54
  "url": "https://github.com/YosefHayim/ebay-mcp/issues"
55
55
  },
56
56
  "dependencies": {
57
- "@modelcontextprotocol/sdk": "1.21.1",
57
+ "@modelcontextprotocol/sdk": "1.26.0",
58
58
  "axios": "^1.7.9",
59
59
  "chalk": "^5.6.2",
60
60
  "cors": "^2.8.5",
61
61
  "dotenv": "^17.2.3",
62
+ "dotenv-stringify": "^3.0.1",
62
63
  "express": "^5.1.0",
63
64
  "fast-xml-parser": "^5.3.4",
64
65
  "helmet": "^8.1.0",