ebay-mcp 1.7.1 → 1.7.2

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
@@ -223,6 +223,8 @@ EBAY_CLIENT_ID=your_client_id
223
223
  EBAY_CLIENT_SECRET=your_client_secret
224
224
  EBAY_ENVIRONMENT=sandbox # or "production"
225
225
  EBAY_REDIRECT_URI=your_runame
226
+ EBAY_MARKETPLACE_ID=EBAY_US # Default marketplace (overridable by many tools)
227
+ EBAY_CONTENT_LANGUAGE=en-US # Default request content language (global)
226
228
  EBAY_USER_REFRESH_TOKEN=your_refresh_token # For higher rate limits
227
229
  ```
228
230
 
@@ -37,6 +37,22 @@ export class EbayApiClient {
37
37
  baseUrl;
38
38
  rateLimitTracker;
39
39
  config;
40
+ /**
41
+ * Build default request headers based on configured marketplace and language.
42
+ */
43
+ getDefaultHeaders() {
44
+ const headers = {
45
+ 'Content-Type': 'application/json',
46
+ Accept: 'application/json',
47
+ };
48
+ if (this.config.contentLanguage) {
49
+ headers['Content-Language'] = this.config.contentLanguage;
50
+ }
51
+ if (this.config.marketplaceId) {
52
+ headers['X-EBAY-C-MARKETPLACE-ID'] = this.config.marketplaceId;
53
+ }
54
+ return headers;
55
+ }
40
56
  constructor(config) {
41
57
  this.config = config;
42
58
  this.authClient = new EbayOAuthClient(config);
@@ -45,10 +61,7 @@ export class EbayApiClient {
45
61
  this.httpClient = axios.create({
46
62
  baseURL: this.baseUrl,
47
63
  timeout: 30000,
48
- headers: {
49
- 'Content-Type': 'application/json',
50
- Accept: 'application/json',
51
- },
64
+ headers: this.getDefaultHeaders(),
52
65
  });
53
66
  // Add request interceptor to inject auth token and check rate limits
54
67
  this.httpClient.interceptors.request.use(async (config) => {
@@ -283,8 +296,7 @@ export class EbayApiClient {
283
296
  params,
284
297
  headers: {
285
298
  Authorization: `Bearer ${token}`,
286
- 'Content-Type': 'application/json',
287
- Accept: 'application/json',
299
+ ...this.getDefaultHeaders(),
288
300
  },
289
301
  timeout: 30000,
290
302
  });
@@ -305,8 +317,7 @@ export class EbayApiClient {
305
317
  params,
306
318
  headers: {
307
319
  Authorization: `Bearer ${token}`,
308
- 'Content-Type': 'application/json',
309
- Accept: 'application/json',
320
+ ...this.getDefaultHeaders(),
310
321
  },
311
322
  timeout: 30000,
312
323
  });
@@ -60,11 +60,7 @@ export class InventoryApi {
60
60
  throw new Error('inventoryItem is required and must be an object');
61
61
  }
62
62
  try {
63
- return await this.client.put(`${this.basePath}/inventory_item/${sku}`, inventoryItem, {
64
- headers: {
65
- 'Content-Language': 'en-US',
66
- },
67
- });
63
+ return await this.client.put(`${this.basePath}/inventory_item/${sku}`, inventoryItem);
68
64
  }
69
65
  catch (error) {
70
66
  throw new Error(`Failed to create or replace inventory item: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -95,11 +91,7 @@ export class InventoryApi {
95
91
  throw new Error('requests is required and must be an object');
96
92
  }
97
93
  try {
98
- return await this.client.post(`${this.basePath}/bulk_create_or_replace_inventory_item`, requests, {
99
- headers: {
100
- 'Content-Language': 'en-US',
101
- },
102
- });
94
+ return await this.client.post(`${this.basePath}/bulk_create_or_replace_inventory_item`, requests);
103
95
  }
104
96
  catch (error) {
105
97
  throw new Error(`Failed to bulk create or replace inventory items: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -166,11 +158,7 @@ export class InventoryApi {
166
158
  throw new Error('compatibility is required and must be an object');
167
159
  }
168
160
  try {
169
- return await this.client.put(`${this.basePath}/inventory_item/${sku}/product_compatibility`, compatibility, {
170
- headers: {
171
- 'Content-Language': 'en-US',
172
- },
173
- });
161
+ return await this.client.put(`${this.basePath}/inventory_item/${sku}/product_compatibility`, compatibility);
174
162
  }
175
163
  catch (error) {
176
164
  throw new Error(`Failed to create or replace product compatibility: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -221,11 +209,7 @@ export class InventoryApi {
221
209
  throw new Error('inventoryItemGroup is required and must be an object');
222
210
  }
223
211
  try {
224
- return await this.client.put(`${this.basePath}/inventory_item_group/${inventoryItemGroupKey}`, inventoryItemGroup, {
225
- headers: {
226
- 'Content-Language': 'en-US',
227
- },
228
- });
212
+ return await this.client.put(`${this.basePath}/inventory_item_group/${inventoryItemGroupKey}`, inventoryItemGroup);
229
213
  }
230
214
  catch (error) {
231
215
  throw new Error(`Failed to create or replace inventory item group: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -431,11 +415,7 @@ export class InventoryApi {
431
415
  throw new Error('offer is required and must be an object');
432
416
  }
433
417
  try {
434
- return await this.client.post(`${this.basePath}/offer`, offer, {
435
- headers: {
436
- 'Content-Language': 'en-US',
437
- },
438
- });
418
+ return await this.client.post(`${this.basePath}/offer`, offer);
439
419
  }
440
420
  catch (error) {
441
421
  throw new Error(`Failed to create offer: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -454,11 +434,7 @@ export class InventoryApi {
454
434
  throw new Error('offer is required and must be an object');
455
435
  }
456
436
  try {
457
- return await this.client.put(`${this.basePath}/offer/${offerId}`, offer, {
458
- headers: {
459
- 'Content-Language': 'en-US',
460
- },
461
- });
437
+ return await this.client.put(`${this.basePath}/offer/${offerId}`, offer);
462
438
  }
463
439
  catch (error) {
464
440
  throw new Error(`Failed to update offer: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -521,11 +497,7 @@ export class InventoryApi {
521
497
  throw new Error('requests is required and must be an object');
522
498
  }
523
499
  try {
524
- return await this.client.post(`${this.basePath}/bulk_create_offer`, requests, {
525
- headers: {
526
- 'Content-Language': 'en-US',
527
- },
528
- });
500
+ return await this.client.post(`${this.basePath}/bulk_create_offer`, requests);
529
501
  }
530
502
  catch (error) {
531
503
  throw new Error(`Failed to bulk create offers: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -62,13 +62,7 @@ export function getDefaultScopes(environment) {
62
62
  if (environment === 'production') {
63
63
  return getProductionScopes();
64
64
  }
65
- const sandboxScopes = getSandboxScopes();
66
- const productionScopes = getProductionScopes();
67
- const productionWithoutEdelivery = productionScopes.filter((scope) => scope !== 'https://api.ebay.com/oauth/scope/sell.edelivery');
68
- const mergedScopes = new Set([...sandboxScopes, ...productionWithoutEdelivery]);
69
- mergedScopes.add('https://api.ebay.com/oauth/api_scope/sell.edelivery');
70
- mergedScopes.add('https://api.ebay.com/oauth/scope/sell.edelivery');
71
- return Array.from(mergedScopes);
65
+ return getSandboxScopes();
72
66
  }
73
67
  /**
74
68
  * Validate scopes against environment and return warnings for invalid scopes
@@ -144,6 +138,9 @@ export function validateEnvironmentConfig() {
144
138
  errors,
145
139
  };
146
140
  }
141
+ /**
142
+ * Build EbayConfig from environment variables with safe defaults.
143
+ */
147
144
  export function getEbayConfig() {
148
145
  const clientId = process.env.EBAY_CLIENT_ID ?? '';
149
146
  const clientSecret = process.env.EBAY_CLIENT_SECRET ?? '';
@@ -151,6 +148,8 @@ export function getEbayConfig() {
151
148
  const accessToken = process.env.EBAY_USER_ACCESS_TOKEN ?? '';
152
149
  const refreshToken = process.env.EBAY_USER_REFRESH_TOKEN ?? '';
153
150
  const appAccessToken = process.env.EBAY_APP_ACCESS_TOKEN ?? '';
151
+ const marketplaceId = (process.env.EBAY_MARKETPLACE_ID ?? '').trim() || 'EBAY_US';
152
+ const contentLanguage = (process.env.EBAY_CONTENT_LANGUAGE ?? '').trim() || 'en-US';
154
153
  // Only require client credentials - tokens can be optional (generated from refresh token)
155
154
  if (clientId === '' || clientSecret === '') {
156
155
  console.error('Missing required eBay credentials. Please set:\n1) EBAY_CLIENT_ID\n2) EBAY_CLIENT_SECRET\nin your .env file at project root');
@@ -159,6 +158,8 @@ export function getEbayConfig() {
159
158
  clientId,
160
159
  clientSecret,
161
160
  redirectUri: process.env.EBAY_REDIRECT_URI,
161
+ marketplaceId,
162
+ contentLanguage,
162
163
  environment,
163
164
  accessToken,
164
165
  refreshToken,
@@ -188,21 +189,17 @@ export function getAuthUrl(clientIdOrEnvironment, redirectUri, environment, loca
188
189
  console.error('clientId, redirectUri (RuName), and scope are required,please initialize the class properly.');
189
190
  return '';
190
191
  }
191
- // Build consent URL with auth2 endpoint (eBay's OAuth consent endpoint)
192
- const consentDomain = env === 'production' ? 'https://auth2.ebay.com' : 'https://auth2.sandbox.ebay.com';
193
- const consentParams = new URLSearchParams({
192
+ const authBase = env === 'production' ? 'https://auth.ebay.com' : 'https://auth.sandbox.ebay.com';
193
+ const scopeList = scopes?.join('%20') || scope.join('%20');
194
+ const authorizeParams = new URLSearchParams({
194
195
  client_id: clientId,
195
196
  redirect_uri: redirectUri,
196
197
  response_type: responseType,
197
- scope: scopes?.join(' ') || scope.join(' '),
198
198
  prompt,
199
199
  locale,
200
200
  ...(state ? { state } : {}),
201
201
  });
202
- const consentUrl = `${consentDomain}/oauth2/consents?${consentParams.toString()}`;
203
- // Build signin URL that redirects to consent
204
- const signinDomain = env === 'production' ? 'https://signin.ebay.com' : 'https://signin.sandbox.ebay.com';
205
- return `${signinDomain}/signin?ru=${encodeURIComponent(consentUrl)}&sgfl=oauth2&AppName=${encodeURIComponent(clientId)}`;
202
+ return `${authBase}/oauth2/authorize?${authorizeParams.toString()}&scope=${scopeList}`;
206
203
  }
207
204
  /**
208
205
  * Generate the OAuth authorization URL for user consent
@@ -213,33 +210,22 @@ export function getAuthUrl(clientIdOrEnvironment, redirectUri, environment, loca
213
210
  */
214
211
  export function getOAuthAuthorizationUrl(clientId, redirectUri, // MUST be eBay RuName, NOT a URL
215
212
  environment, scopes, locale, state) {
216
- // Build the authorize URL using auth2 endpoint (correct eBay OAuth endpoint)
217
- const authDomain = environment === 'production' ? 'https://auth2.ebay.com' : 'https://auth2.sandbox.ebay.com';
218
- const authorizeEndpoint = `${authDomain}/oauth2/authorize`;
219
- // Build query parameters for the authorize endpoint
220
- const params = new URLSearchParams({
221
- client_id: clientId,
222
- redirect_uri: redirectUri,
223
- });
224
- // Add scopes only if provided (optional - eBay handles automatically if not specified)
213
+ const authBase = environment === 'production' ? 'https://auth.ebay.com' : 'https://auth.sandbox.ebay.com';
214
+ let scopeList;
225
215
  if (scopes && scopes.length > 0) {
226
- params.append('scope', scopes.join(' '));
216
+ scopeList = scopes.join('%20');
227
217
  }
228
218
  else {
229
- // Use default scopes for the environment if no scopes are specified
230
219
  const defaultScopes = getDefaultScopes(environment);
231
- params.append('scope', defaultScopes.join(' '));
220
+ scopeList = defaultScopes.join('%20');
232
221
  }
233
- // Always add state parameter (empty if not provided)
234
- params.append('state', state || '');
235
- // Add response_type
236
- params.append('response_type', 'code');
237
- // Add hd parameter (required by eBay)
238
- params.append('hd', '');
239
- // Build the signin URL that redirects to authorize
240
- const signinDomain = environment === 'production' ? 'https://signin.ebay.com' : 'https://signin.sandbox.ebay.com';
241
- const ruParam = encodeURIComponent(`${authorizeEndpoint}?${params.toString()}`);
242
- return `${signinDomain}/signin?ru=${ruParam}&sgfl=oauth2_login&AppName=${clientId}`;
222
+ const params = new URLSearchParams({
223
+ client_id: clientId,
224
+ redirect_uri: redirectUri,
225
+ response_type: 'code',
226
+ ...(state ? { state } : {}),
227
+ });
228
+ return `${authBase}/oauth2/authorize?${params.toString()}&scope=${scopeList}`;
243
229
  }
244
230
  const iconUrl = (size) => {
245
231
  const url = new URL(`../../public/icons/${size}.png`, import.meta.url);
@@ -135,6 +135,12 @@ function generateMCPServerConfig() {
135
135
  if (process.env.EBAY_REDIRECT_URI) {
136
136
  config.env.EBAY_REDIRECT_URI = process.env.EBAY_REDIRECT_URI;
137
137
  }
138
+ if (process.env.EBAY_MARKETPLACE_ID) {
139
+ config.env.EBAY_MARKETPLACE_ID = process.env.EBAY_MARKETPLACE_ID;
140
+ }
141
+ if (process.env.EBAY_CONTENT_LANGUAGE) {
142
+ config.env.EBAY_CONTENT_LANGUAGE = process.env.EBAY_CONTENT_LANGUAGE;
143
+ }
138
144
  if (process.env.EBAY_USER_ACCESS_TOKEN) {
139
145
  config.env.EBAY_USER_ACCESS_TOKEN = process.env.EBAY_USER_ACCESS_TOKEN;
140
146
  }
@@ -118,6 +118,8 @@ function displayConfigurationStatus(envVars) {
118
118
  { key: 'EBAY_CLIENT_SECRET', label: 'Client Secret', redact: true },
119
119
  { key: 'EBAY_REDIRECT_URI', label: 'Redirect URI', redact: false },
120
120
  { key: 'EBAY_ENVIRONMENT', label: 'Environment', redact: false },
121
+ { key: 'EBAY_MARKETPLACE_ID', label: 'Marketplace ID', redact: false },
122
+ { key: 'EBAY_CONTENT_LANGUAGE', label: 'Content Language', redact: false },
121
123
  { key: 'EBAY_USER_REFRESH_TOKEN', label: 'User Refresh Token', redact: true },
122
124
  { key: 'EBAY_USER_ACCESS_TOKEN', label: 'User Access Token', redact: true },
123
125
  { key: 'EBAY_APP_ACCESS_TOKEN', label: 'App Access Token', redact: true },
@@ -143,6 +143,10 @@ EBAY_REDIRECT_URI=${config.EBAY_REDIRECT_URI || 'http://localhost:3000/oauth/cal
143
143
  # ═══════════════════════════════════════════════════════════════════
144
144
 
145
145
  EBAY_ENVIRONMENT=${config.EBAY_ENVIRONMENT || 'sandbox'}
146
+ # Optional: e.g. EBAY_US, EBAY_DE, EBAY_FR
147
+ EBAY_MARKETPLACE_ID=${config.EBAY_MARKETPLACE_ID || ''}
148
+ # Optional: e.g. en-US, de-DE, fr-FR
149
+ EBAY_CONTENT_LANGUAGE=${config.EBAY_CONTENT_LANGUAGE || ''}
146
150
 
147
151
  # ═══════════════════════════════════════════════════════════════════
148
152
  # User Tokens (Auto-generated from refresh token)
@@ -667,6 +671,8 @@ async function runInteractiveSetup(args) {
667
671
  EBAY_CLIENT_SECRET: credentials.EBAY_CLIENT_SECRET,
668
672
  EBAY_REDIRECT_URI: credentials.EBAY_REDIRECT_URI,
669
673
  EBAY_ENVIRONMENT: environment,
674
+ EBAY_MARKETPLACE_ID: existingConfig.EBAY_MARKETPLACE_ID || '',
675
+ EBAY_CONTENT_LANGUAGE: existingConfig.EBAY_CONTENT_LANGUAGE || '',
670
676
  EBAY_USER_REFRESH_TOKEN: refreshToken,
671
677
  EBAY_USER_ACCESS_TOKEN: '',
672
678
  EBAY_APP_ACCESS_TOKEN: '',
@@ -4,18 +4,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
4
  import { homedir, platform } from 'os';
5
5
  import axios from 'axios';
6
6
  import chalk from 'chalk';
7
+ import { checkForUpdates } from '../utils/version.js';
7
8
  import { config } from 'dotenv';
8
9
  import { exec } from 'child_process';
9
10
  import { fileURLToPath } from 'url';
11
+ import { getOAuthAuthorizationUrl } from '../config/environment.js';
10
12
  import prompts from 'prompts';
11
- import { getDefaultScopes } from '../config/environment.js';
12
- import { checkForUpdates } from '../utils/version.js';
13
13
  config({ quiet: true });
14
14
  checkForUpdates();
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = dirname(__filename);
17
17
  const PROJECT_ROOT = join(__dirname, '../..');
18
- const TOTAL_STEPS = 5;
18
+ const TOTAL_STEPS = 6;
19
19
  const ebay = {
20
20
  red: chalk.hex('#E53238'),
21
21
  blue: chalk.hex('#0064D2'),
@@ -31,6 +31,26 @@ const ui = {
31
31
  info: chalk.cyan,
32
32
  hint: chalk.gray,
33
33
  };
34
+ const MARKETPLACE_OPTIONS = [
35
+ { value: 'EBAY_US', label: 'EBAY_US — United States' },
36
+ { value: 'EBAY_GB', label: 'EBAY_GB — United Kingdom' },
37
+ { value: 'EBAY_DE', label: 'EBAY_DE — Germany' },
38
+ { value: 'EBAY_FR', label: 'EBAY_FR — France' },
39
+ { value: 'EBAY_IT', label: 'EBAY_IT — Italy' },
40
+ { value: 'EBAY_ES', label: 'EBAY_ES — Spain' },
41
+ { value: 'EBAY_CA', label: 'EBAY_CA — Canada' },
42
+ { value: 'EBAY_AU', label: 'EBAY_AU — Australia' },
43
+ ];
44
+ const CONTENT_LANGUAGE_OPTIONS = [
45
+ { value: 'en-US', label: 'en-US — English (United States)' },
46
+ { value: 'en-GB', label: 'en-GB — English (United Kingdom)' },
47
+ { value: 'de-DE', label: 'de-DE — German (Germany)' },
48
+ { value: 'fr-FR', label: 'fr-FR — French (France)' },
49
+ { value: 'it-IT', label: 'it-IT — Italian (Italy)' },
50
+ { value: 'es-ES', label: 'es-ES — Spanish (Spain)' },
51
+ { value: 'fr-CA', label: 'fr-CA — French (Canada)' },
52
+ { value: 'nl-BE', label: 'nl-BE — Dutch (Belgium)' },
53
+ ];
34
54
  const LOGO = `
35
55
  ${ebay.red('███████╗')}${ebay.blue('██████╗ ')}${ebay.yellow('█████╗ ')}${ebay.green('██╗ ██╗')}
36
56
  ${ebay.red('██╔════╝')}${ebay.blue('██╔══██╗')}${ebay.yellow('██╔══██╗')}${ebay.green('╚██╗ ██╔╝')}
@@ -39,13 +59,22 @@ const LOGO = `
39
59
  ${ebay.red('███████╗')}${ebay.blue('██████╔╝')}${ebay.yellow('██║ ██║')}${ebay.green(' ██║ ')}
40
60
  ${ebay.red('╚══════╝')}${ebay.blue('╚═════╝ ')}${ebay.yellow('╚═╝ ╚═╝')}${ebay.green(' ╚═╝ ')}
41
61
  `;
62
+ /**
63
+ * Clear the terminal screen.
64
+ */
42
65
  function clearScreen() {
43
66
  console.clear();
44
67
  }
68
+ /**
69
+ * Render the eBay ASCII logo and heading.
70
+ */
45
71
  function showLogo() {
46
72
  console.log(LOGO);
47
- console.log(ui.bold.white(' MCP Server Setup Wizard\n'));
73
+ console.log(ui.bold.white(' MCP Server Setup Wizard by Yosef Hayim Sabag\n'));
48
74
  }
75
+ /**
76
+ * Render a step progress bar with title.
77
+ */
49
78
  function showProgress(step, title) {
50
79
  const filled = '●'.repeat(step);
51
80
  const empty = '○'.repeat(TOTAL_STEPS - step);
@@ -54,21 +83,33 @@ function showProgress(step, title) {
54
83
  console.log(` ${progress} ${ui.bold(`Step ${step}/${TOTAL_STEPS}`)}: ${title}`);
55
84
  console.log(ui.dim('─'.repeat(60)) + '\n');
56
85
  }
86
+ /**
87
+ * Render keyboard hints for the current step.
88
+ */
57
89
  function showKeyboardHints(hints) {
58
90
  const hintText = hints.map((h) => ui.dim(h)).join(' │ ');
59
91
  console.log(`\n ${hintText}\n`);
60
92
  }
93
+ /**
94
+ * Render a tip callout.
95
+ */
61
96
  function showTip(message) {
62
97
  console.log(` ${ebay.yellow('💡 Tip:')} ${ui.dim(message)}\n`);
63
98
  }
99
+ /**
100
+ * Render a success line.
101
+ */
64
102
  function showSuccess(message) {
65
103
  console.log(` ${ui.success('✓')} ${message}`);
66
104
  }
105
+ /**
106
+ * Render an error line.
107
+ */
67
108
  function showError(message) {
68
109
  console.log(` ${ui.error('✗')} ${message}`);
69
110
  }
70
111
  /**
71
- * Open a URL in the default browser (cross-platform)
112
+ * Open a URL in the default browser (cross-platform).
72
113
  */
73
114
  function openBrowser(url) {
74
115
  return new Promise((resolve, reject) => {
@@ -94,6 +135,9 @@ function openBrowser(url) {
94
135
  });
95
136
  });
96
137
  }
138
+ /**
139
+ * Render a warning line.
140
+ */
97
141
  function showWarning(message) {
98
142
  console.log(` ${ui.warning('⚠')} ${message}`);
99
143
  }
@@ -260,9 +304,15 @@ function displayUserInfo(userInfo) {
260
304
  `User ID: ${userInfo.userId?.slice(0, 30)}...`,
261
305
  ]);
262
306
  }
307
+ /**
308
+ * Render an informational line.
309
+ */
263
310
  function showInfo(message) {
264
311
  console.log(` ${ui.info('ℹ')} ${message}`);
265
312
  }
313
+ /**
314
+ * Render a spinner and return a stop callback.
315
+ */
266
316
  function showSpinner(message) {
267
317
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
268
318
  let i = 0;
@@ -276,6 +326,9 @@ function showSpinner(message) {
276
326
  process.stdout.write('\r' + ' '.repeat(message.length + 10) + '\r');
277
327
  };
278
328
  }
329
+ /**
330
+ * Render a bordered info box.
331
+ */
279
332
  function showBox(title, content) {
280
333
  const width = 60;
281
334
  const line = '─'.repeat(width - 2);
@@ -288,6 +341,9 @@ function showBox(title, content) {
288
341
  }
289
342
  console.log(` ${ui.dim('└' + line + '┘')}\n`);
290
343
  }
344
+ /**
345
+ * Compute MCP client config paths by OS.
346
+ */
291
347
  function getConfigPaths() {
292
348
  const home = homedir();
293
349
  const os = platform();
@@ -328,6 +384,9 @@ function getConfigPaths() {
328
384
  };
329
385
  return paths;
330
386
  }
387
+ /**
388
+ * Detect installed MCP-compatible clients.
389
+ */
331
390
  function detectLLMClients() {
332
391
  const paths = getConfigPaths();
333
392
  const clients = [];
@@ -344,6 +403,9 @@ function detectLLMClients() {
344
403
  }
345
404
  return clients;
346
405
  }
406
+ /**
407
+ * Write MCP server config for a detected client.
408
+ */
347
409
  function configureLLMClient(client, projectRoot) {
348
410
  try {
349
411
  const configDir = dirname(client.configPath);
@@ -462,6 +524,12 @@ function updateClaudeDesktopConfig(envConfig, environment) {
462
524
  if (envConfig.EBAY_REDIRECT_URI) {
463
525
  envVars.EBAY_REDIRECT_URI = envConfig.EBAY_REDIRECT_URI;
464
526
  }
527
+ if (envConfig.EBAY_MARKETPLACE_ID) {
528
+ envVars.EBAY_MARKETPLACE_ID = envConfig.EBAY_MARKETPLACE_ID;
529
+ }
530
+ if (envConfig.EBAY_CONTENT_LANGUAGE) {
531
+ envVars.EBAY_CONTENT_LANGUAGE = envConfig.EBAY_CONTENT_LANGUAGE;
532
+ }
465
533
  if (envConfig.EBAY_USER_REFRESH_TOKEN) {
466
534
  envVars.EBAY_USER_REFRESH_TOKEN = envConfig.EBAY_USER_REFRESH_TOKEN;
467
535
  }
@@ -504,6 +572,9 @@ function updateClaudeDesktopConfig(envConfig, environment) {
504
572
  };
505
573
  }
506
574
  }
575
+ /**
576
+ * Load existing environment variables from the .env file.
577
+ */
507
578
  function loadExistingConfig() {
508
579
  const envPath = join(PROJECT_ROOT, '.env');
509
580
  const envConfig = {};
@@ -521,6 +592,9 @@ function loadExistingConfig() {
521
592
  }
522
593
  return envConfig;
523
594
  }
595
+ /**
596
+ * Format a date for display in the .env header.
597
+ */
524
598
  function formatDate(date) {
525
599
  const options = {
526
600
  weekday: 'long',
@@ -534,9 +608,18 @@ function formatDate(date) {
534
608
  };
535
609
  return date.toLocaleString('en-US', options);
536
610
  }
611
+ /**
612
+ * Persist setup configuration to the project .env file.
613
+ */
537
614
  function saveConfig(envConfig, environment) {
538
615
  const envPath = join(PROJECT_ROOT, '.env');
539
616
  const now = new Date();
617
+ const marketplaceLine = envConfig.EBAY_MARKETPLACE_ID
618
+ ? `EBAY_MARKETPLACE_ID=${envConfig.EBAY_MARKETPLACE_ID}`
619
+ : '# EBAY_MARKETPLACE_ID=EBAY_US';
620
+ const contentLanguageLine = envConfig.EBAY_CONTENT_LANGUAGE
621
+ ? `EBAY_CONTENT_LANGUAGE=${envConfig.EBAY_CONTENT_LANGUAGE}`
622
+ : '# EBAY_CONTENT_LANGUAGE=en-US';
540
623
  const content = `# eBay MCP Server Configuration
541
624
  # Last Updated: ${formatDate(now)}
542
625
  # Environment: ${environment}
@@ -545,6 +628,8 @@ EBAY_CLIENT_ID=${envConfig.EBAY_CLIENT_ID || ''}
545
628
  EBAY_CLIENT_SECRET=${envConfig.EBAY_CLIENT_SECRET || ''}
546
629
  EBAY_REDIRECT_URI=${envConfig.EBAY_REDIRECT_URI || ''}
547
630
  EBAY_ENVIRONMENT=${environment}
631
+ ${marketplaceLine}
632
+ ${contentLanguageLine}
548
633
 
549
634
  EBAY_USER_REFRESH_TOKEN=${envConfig.EBAY_USER_REFRESH_TOKEN || ''}
550
635
  EBAY_USER_ACCESS_TOKEN=${envConfig.EBAY_USER_ACCESS_TOKEN || ''}
@@ -557,10 +642,12 @@ async function stepWelcome(state) {
557
642
  showLogo();
558
643
  console.log(ui.dim(' Welcome to the eBay MCP Server setup wizard!\n'));
559
644
  console.log(' This wizard will help you:\n');
560
- console.log(` ${ui.success('1.')} Configure your eBay Developer credentials`);
561
- console.log(` ${ui.success('2.')} Set up OAuth authentication`);
562
- console.log(` ${ui.success('3.')} Configure your MCP client (Claude, Cline, etc.)`);
563
- console.log(` ${ui.success('4.')} Validate your setup\n`);
645
+ console.log(` ${ui.success('1.')} Choose environment (sandbox/production)`);
646
+ console.log(` ${ui.success('2.')} Set default marketplace and language (optional)`);
647
+ console.log(` ${ui.success('3.')} Configure your eBay Developer credentials`);
648
+ console.log(` ${ui.success('4.')} Set up OAuth authentication`);
649
+ console.log(` ${ui.success('5.')} Configure your MCP client (Claude, Cline, etc.)`);
650
+ console.log(` ${ui.success('6.')} Validate your setup\n`);
564
651
  if (state.hasExistingConfig) {
565
652
  showInfo('Existing configuration detected. You can update or keep current values.');
566
653
  }
@@ -573,6 +660,9 @@ async function stepWelcome(state) {
573
660
  });
574
661
  return response.continue !== false ? 'continue' : 'cancel';
575
662
  }
663
+ /**
664
+ * Select the eBay environment for this configuration.
665
+ */
576
666
  async function stepEnvironment(state) {
577
667
  clearScreen();
578
668
  showLogo();
@@ -609,10 +699,128 @@ async function stepEnvironment(state) {
609
699
  state.config.EBAY_ENVIRONMENT = response.environment;
610
700
  return 'continue';
611
701
  }
702
+ /**
703
+ * Configure optional marketplace and content-language defaults.
704
+ */
705
+ async function stepMarketplaceSettings(state) {
706
+ clearScreen();
707
+ showLogo();
708
+ showProgress(2, 'Marketplace Settings');
709
+ if (state.isQuickMode) {
710
+ showInfo('Quick setup enabled. Skipping optional marketplace configuration.');
711
+ await new Promise((r) => setTimeout(r, 600));
712
+ return 'continue';
713
+ }
714
+ console.log(' Configure default marketplace and language for API requests.\n');
715
+ showBox('Marketplace Settings', [
716
+ 'These are optional defaults used for request headers.',
717
+ 'Marketplace can be overridden in many tools; language is global.',
718
+ ]);
719
+ const marketplaceChoices = [
720
+ { title: ui.dim('← Go back'), value: '__back__' },
721
+ { title: 'Skip (leave unset)', value: '' },
722
+ ...MARKETPLACE_OPTIONS.map((option) => ({
723
+ title: option.label,
724
+ value: option.value,
725
+ })),
726
+ { title: 'Other (enter manually)', value: '__custom__' },
727
+ ];
728
+ const currentMarketplace = state.config.EBAY_MARKETPLACE_ID || '';
729
+ const marketplaceDefault = currentMarketplace || 'EBAY_US';
730
+ const marketplaceMatchIndex = marketplaceChoices.findIndex((choice) => choice.value === marketplaceDefault);
731
+ const marketplaceInitial = marketplaceMatchIndex >= 0
732
+ ? marketplaceMatchIndex
733
+ : marketplaceChoices.findIndex((choice) => choice.value === '__custom__');
734
+ const marketplaceResponse = await prompts({
735
+ type: 'select',
736
+ name: 'marketplaceId',
737
+ message: 'Select your default eBay marketplace:',
738
+ choices: marketplaceChoices,
739
+ initial: marketplaceInitial >= 0 ? marketplaceInitial : 0,
740
+ });
741
+ if (marketplaceResponse.marketplaceId === undefined) {
742
+ return 'cancel';
743
+ }
744
+ if (marketplaceResponse.marketplaceId === '__back__') {
745
+ return 'back';
746
+ }
747
+ let marketplaceId = marketplaceResponse.marketplaceId;
748
+ if (marketplaceId === '__custom__') {
749
+ const customMarketplace = await prompts({
750
+ type: 'text',
751
+ name: 'customMarketplaceId',
752
+ message: 'Enter marketplace ID (e.g., EBAY_US, EBAY_DE):',
753
+ initial: currentMarketplace || 'EBAY_US',
754
+ validate: (value) => value.trim().length === 0 ? 'Marketplace ID cannot be empty' : true,
755
+ });
756
+ if (customMarketplace.customMarketplaceId === undefined) {
757
+ return 'cancel';
758
+ }
759
+ marketplaceId = customMarketplace.customMarketplaceId.trim();
760
+ }
761
+ if (marketplaceId) {
762
+ state.config.EBAY_MARKETPLACE_ID = marketplaceId;
763
+ }
764
+ else {
765
+ delete state.config.EBAY_MARKETPLACE_ID;
766
+ }
767
+ const languageChoices = [
768
+ { title: ui.dim('← Go back'), value: '__back__' },
769
+ { title: 'Skip (leave unset)', value: '' },
770
+ ...CONTENT_LANGUAGE_OPTIONS.map((option) => ({
771
+ title: option.label,
772
+ value: option.value,
773
+ })),
774
+ { title: 'Other (enter manually)', value: '__custom__' },
775
+ ];
776
+ const currentLanguage = state.config.EBAY_CONTENT_LANGUAGE || '';
777
+ const languageDefault = currentLanguage || 'en-US';
778
+ const languageMatchIndex = languageChoices.findIndex((choice) => choice.value === languageDefault);
779
+ const languageInitial = languageMatchIndex >= 0
780
+ ? languageMatchIndex
781
+ : languageChoices.findIndex((choice) => choice.value === '__custom__');
782
+ const languageResponse = await prompts({
783
+ type: 'select',
784
+ name: 'contentLanguage',
785
+ message: 'Select your preferred Content-Language:',
786
+ choices: languageChoices,
787
+ initial: languageInitial >= 0 ? languageInitial : 0,
788
+ });
789
+ if (languageResponse.contentLanguage === undefined) {
790
+ return 'cancel';
791
+ }
792
+ if (languageResponse.contentLanguage === '__back__') {
793
+ return 'back';
794
+ }
795
+ let contentLanguage = languageResponse.contentLanguage;
796
+ if (contentLanguage === '__custom__') {
797
+ const customLanguage = await prompts({
798
+ type: 'text',
799
+ name: 'customContentLanguage',
800
+ message: 'Enter Content-Language (e.g., en-US, de-DE):',
801
+ initial: currentLanguage || 'en-US',
802
+ validate: (value) => value.trim().length === 0 ? 'Content-Language cannot be empty' : true,
803
+ });
804
+ if (customLanguage.customContentLanguage === undefined) {
805
+ return 'cancel';
806
+ }
807
+ contentLanguage = customLanguage.customContentLanguage.trim();
808
+ }
809
+ if (contentLanguage) {
810
+ state.config.EBAY_CONTENT_LANGUAGE = contentLanguage;
811
+ }
812
+ else {
813
+ delete state.config.EBAY_CONTENT_LANGUAGE;
814
+ }
815
+ return 'continue';
816
+ }
817
+ /**
818
+ * Collect eBay app credentials from the user.
819
+ */
612
820
  async function stepCredentials(state) {
613
821
  clearScreen();
614
822
  showLogo();
615
- showProgress(2, 'eBay Credentials');
823
+ showProgress(3, 'eBay Credentials');
616
824
  console.log(' Enter your eBay Developer credentials:\n');
617
825
  showTip('Get credentials at: https://developer.ebay.com/my/keys');
618
826
  showKeyboardHints(['Tab: Next field', 'Enter: Submit', 'Ctrl+C: Cancel']);
@@ -661,10 +869,13 @@ async function stepCredentials(state) {
661
869
  state.config.EBAY_REDIRECT_URI = responses.redirectUri;
662
870
  return 'continue';
663
871
  }
872
+ /**
873
+ * Acquire and validate OAuth tokens for the configured credentials.
874
+ */
664
875
  async function stepOAuth(state) {
665
876
  clearScreen();
666
877
  showLogo();
667
- showProgress(3, 'OAuth Setup');
878
+ showProgress(4, 'OAuth Setup');
668
879
  console.log(' Configure user authentication for higher API rate limits:\n');
669
880
  showBox('Rate Limits by Auth Type', [
670
881
  'App Credentials Only: 1,000 req/day',
@@ -855,25 +1066,7 @@ async function stepOAuth(state) {
855
1066
  }
856
1067
  }
857
1068
  else if (tokenChoice.method === 'manual') {
858
- // Build the OAuth consent URL (auth2 endpoint)
859
- const consentDomain = state.environment === 'production'
860
- ? 'https://auth2.ebay.com'
861
- : 'https://auth2.sandbox.ebay.com';
862
- // Get scopes from environment config
863
- const scopes = getDefaultScopes(state.environment);
864
- // Build the consent URL parameters
865
- const consentParams = new URLSearchParams({
866
- client_id: state.config.EBAY_CLIENT_ID,
867
- redirect_uri: state.config.EBAY_REDIRECT_URI,
868
- response_type: 'code',
869
- scope: scopes.join(' '),
870
- });
871
- const consentUrl = `${consentDomain}/oauth2/consents?${consentParams.toString()}`;
872
- // Build the signin URL that redirects to consent
873
- const signinDomain = state.environment === 'production'
874
- ? 'https://signin.ebay.com'
875
- : 'https://signin.sandbox.ebay.com';
876
- const authUrl = `${signinDomain}/signin?ru=${encodeURIComponent(consentUrl)}&sgfl=oauth2&AppName=${encodeURIComponent(state.config.EBAY_CLIENT_ID)}`;
1069
+ const authUrl = getOAuthAuthorizationUrl(state.config.EBAY_CLIENT_ID, state.config.EBAY_REDIRECT_URI, state.environment);
877
1070
  console.log('\n ' + ui.bold('OAuth Authorization URL:'));
878
1071
  console.log(ui.dim(' ' + '─'.repeat(56)));
879
1072
  console.log(` ${ui.info(authUrl)}`);
@@ -1094,10 +1287,13 @@ async function stepOAuth(state) {
1094
1287
  }
1095
1288
  return 'continue';
1096
1289
  }
1290
+ /**
1291
+ * Configure MCP clients with the generated environment variables.
1292
+ */
1097
1293
  async function stepMCPClients(state) {
1098
1294
  clearScreen();
1099
1295
  showLogo();
1100
- showProgress(4, 'MCP Client Setup');
1296
+ showProgress(5, 'MCP Client Setup');
1101
1297
  console.log(' Configure your AI assistant to use the eBay MCP server:\n');
1102
1298
  state.detectedClients = detectLLMClients();
1103
1299
  const detected = state.detectedClients.filter((c) => c.detected);
@@ -1184,10 +1380,13 @@ async function stepMCPClients(state) {
1184
1380
  }
1185
1381
  return 'continue';
1186
1382
  }
1383
+ /**
1384
+ * Finalize setup and display summary information.
1385
+ */
1187
1386
  async function stepComplete(state) {
1188
1387
  clearScreen();
1189
1388
  showLogo();
1190
- showProgress(5, 'Setup Complete');
1389
+ showProgress(6, 'Setup Complete');
1191
1390
  const stopSpinner = showSpinner('Saving configuration...');
1192
1391
  await new Promise((r) => setTimeout(r, 300));
1193
1392
  saveConfig(state.config, state.environment);
@@ -1196,6 +1395,8 @@ async function stepComplete(state) {
1196
1395
  console.log(ui.bold.green('\n 🎉 Setup Complete!\n'));
1197
1396
  showBox('Configuration Summary', [
1198
1397
  `Environment: ${state.environment}`,
1398
+ `Marketplace ID: ${state.config.EBAY_MARKETPLACE_ID || 'Not set'}`,
1399
+ `Content-Lang: ${state.config.EBAY_CONTENT_LANGUAGE || 'Not set'}`,
1199
1400
  `Client ID: ${state.config.EBAY_CLIENT_ID?.slice(0, 20)}...`,
1200
1401
  `Redirect URI: ${state.config.EBAY_REDIRECT_URI?.slice(0, 30)}...`,
1201
1402
  `OAuth Token: ${state.config.EBAY_USER_REFRESH_TOKEN ? '✓ Configured' : '✗ Not set'}`,
@@ -1268,7 +1469,14 @@ async function main() {
1268
1469
  hasExistingConfig: Object.keys(existingConfig).length > 0,
1269
1470
  isQuickMode: args.quick,
1270
1471
  };
1271
- const steps = [stepWelcome, stepEnvironment, stepCredentials, stepOAuth, stepMCPClients];
1472
+ const steps = [
1473
+ stepWelcome,
1474
+ stepEnvironment,
1475
+ stepMarketplaceSettings,
1476
+ stepCredentials,
1477
+ stepOAuth,
1478
+ stepMCPClients,
1479
+ ];
1272
1480
  let stepIndex = 0;
1273
1481
  while (stepIndex < steps.length) {
1274
1482
  const result = await steps[stepIndex](state);
@@ -126,7 +126,7 @@ export const tokenManagementTools = [
126
126
  'IMPORTANT NOTES:\n' +
127
127
  '- Authorization codes expire in ~5 minutes - if you get "invalid grant" error, get a fresh code\n' +
128
128
  '- Codes can be URL-encoded (e.g., v%5E1.1%23...) - this tool automatically decodes them\n' +
129
- '- Extract the code parameter from the redirect URL: https://auth2.ebay.com/...&code=YOUR_CODE&expires_in=299\n' +
129
+ '- Extract the code parameter from the redirect URL (your RuName Accept URL): https://your-redirect-uri?code=YOUR_CODE&expires_in=299\n' +
130
130
  '- Tokens are saved to .env file and will auto-refresh every 2 hours\n' +
131
131
  '- Refresh tokens last 18 months before requiring re-authorization\n\n' +
132
132
  'COMMON ERRORS:\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ebay-mcp",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
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",