ebay-mcp 1.13.2 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.de.md +3 -3
  2. package/README.es.md +3 -3
  3. package/README.fr.md +3 -3
  4. package/README.ja.md +3 -3
  5. package/README.ko.md +3 -3
  6. package/README.md +163 -41
  7. package/README.pt-BR.md +3 -3
  8. package/README.ru.md +3 -3
  9. package/README.zh-CN.md +3 -3
  10. package/build/api/account-management/account.js +633 -297
  11. package/build/api/analytics-and-report/analytics.js +111 -65
  12. package/build/api/client.js +199 -140
  13. package/build/api/clientRequestError.js +23 -0
  14. package/build/api/clientTrading.js +213 -0
  15. package/build/api/clientTradingError.js +4 -0
  16. package/build/api/communication/feedback.js +147 -59
  17. package/build/api/communication/message.js +134 -81
  18. package/build/api/communication/negotiation.js +65 -53
  19. package/build/api/communication/notification.js +458 -164
  20. package/build/api/developer/developer.js +99 -45
  21. package/build/api/index.js +23 -8
  22. package/build/api/listing-management/inventory.js +884 -338
  23. package/build/api/listing-metadata/metadata.js +416 -170
  24. package/build/api/listing-metadata/taxonomy.js +187 -42
  25. package/build/api/marketing-and-promotions/marketing.js +1392 -598
  26. package/build/api/marketing-and-promotions/recommendation.js +49 -24
  27. package/build/api/order-management/dispute.js +226 -45
  28. package/build/api/order-management/fulfillment.js +144 -71
  29. package/build/api/order-management/orderIssueHelpers.js +61 -0
  30. package/build/api/other/compliance.js +50 -38
  31. package/build/api/other/edelivery.js +603 -159
  32. package/build/api/other/finding.js +280 -0
  33. package/build/api/other/identity.js +30 -15
  34. package/build/api/other/translation.js +29 -14
  35. package/build/api/other/vero.js +113 -27
  36. package/build/api/rateLimitTracker.js +55 -0
  37. package/build/api/shared/request.js +189 -98
  38. package/build/api/trading/trading.js +154 -87
  39. package/build/auth/credentialSession.js +250 -0
  40. package/build/auth/oauth.js +298 -171
  41. package/build/auth/{oauth-metadata.js → oauthMetadata.js} +22 -6
  42. package/build/auth/oauthMiddleware.js +104 -0
  43. package/build/auth/{scope-utils.js → scopeUtils.js} +101 -28
  44. package/build/auth/tokenVerifier.js +243 -0
  45. package/build/auth/tokenVerifierError.js +21 -0
  46. package/build/config/environment.js +148 -113
  47. package/build/config/{tool-families.js → toolFamilies.js} +30 -14
  48. package/build/index.js +25 -19
  49. package/build/mcp/{http-transport.js → httpTransport.js} +102 -52
  50. package/build/mcp/readOnlyFilter.js +67 -0
  51. package/build/mcp/runtime.js +31 -13
  52. package/build/mcp/{tool-gating.js → toolGating.js} +77 -49
  53. package/build/mcp/{ui-bridge.js → uiBridge.js} +80 -23
  54. package/build/schemas/account-management/account.js +144 -6
  55. package/build/schemas/analytics/analytics.js +33 -19
  56. package/build/schemas/communication/messages.js +12 -42
  57. package/build/schemas/developer/developer.js +135 -0
  58. package/build/schemas/fulfillment/orders.js +183 -21
  59. package/build/schemas/index.js +27 -19
  60. package/build/schemas/inventory-management/inventory.js +17 -9
  61. package/build/schemas/marketing/marketing.js +480 -25
  62. package/build/schemas/metadata/metadata.js +11 -6
  63. package/build/schemas/other/finding.js +27 -0
  64. package/build/schemas/other/otherApis.js +424 -0
  65. package/build/schemas/taxonomy/taxonomy.js +11 -5
  66. package/build/scripts/{auto-setup.js → autoSetup.js} +87 -74
  67. package/build/scripts/{build-ui.js → buildUi.js} +6 -1
  68. package/build/scripts/{dev-sync.js → devSync.js} +169 -110
  69. package/build/scripts/diagnostics.js +52 -44
  70. package/build/scripts/{download-specs.js → downloadSpecs.js} +29 -21
  71. package/build/scripts/{interactive-setup.js → interactiveSetup.js} +84 -78
  72. package/build/scripts/setup.js +241 -178
  73. package/build/scripts/{setup-shared.js → setupShared.js} +29 -7
  74. package/build/scripts/skills.js +35 -27
  75. package/build/scripts/updateApiStatusDoc.js +71 -0
  76. package/build/serverHttp.js +90 -0
  77. package/build/skills/content.js +124 -118
  78. package/build/skills/index.js +22 -10
  79. package/build/skills/install.js +21 -10
  80. package/build/skills/markers.js +50 -15
  81. package/build/skills/{registry-snapshot.js → registrySnapshot.js} +8 -2
  82. package/build/skills/render.js +41 -14
  83. package/build/skills/targets.js +49 -17
  84. package/build/tools/categories/account.js +166 -264
  85. package/build/tools/categories/analytics.js +11 -22
  86. package/build/tools/categories/browse.js +17 -0
  87. package/build/tools/categories/communication.js +124 -399
  88. package/build/tools/categories/connector.js +23 -20
  89. package/build/tools/categories/developer.js +46 -159
  90. package/build/tools/categories/fulfillment.js +84 -215
  91. package/build/tools/categories/index.js +6 -14
  92. package/build/tools/categories/inventory.js +238 -293
  93. package/build/tools/categories/marketing.js +677 -39
  94. package/build/tools/categories/metadata.js +190 -117
  95. package/build/tools/categories/other.js +81 -208
  96. package/build/tools/categories/taxonomy.js +29 -20
  97. package/build/tools/categories/{token-management.js → tokenManagement.js} +187 -192
  98. package/build/tools/categories/trading.js +16 -47
  99. package/build/tools/contracts.js +34 -24
  100. package/build/tools/{define-tool.js → defineTool.js} +44 -29
  101. package/build/tools/registry.js +87 -29
  102. package/build/tools/schemas.js +3 -57
  103. package/build/tools/ui/archetypes.js +1 -0
  104. package/build/tools/ui/{map-helpers.js → mapHelpers.js} +69 -19
  105. package/build/tools/ui/maps.js +294 -80
  106. package/build/types/index.js +1 -1
  107. package/build/ui/card.html +14 -14
  108. package/build/ui/chart.html +14 -14
  109. package/build/ui/stat.html +87 -0
  110. package/build/ui/table.html +14 -14
  111. package/build/utils/apiStatusFeed.js +95 -0
  112. package/build/utils/cliOutput.js +15 -0
  113. package/build/utils/cliUi.js +108 -0
  114. package/build/utils/communication/feedback.js +57 -77
  115. package/build/utils/communication/message.js +69 -98
  116. package/build/utils/communication/negotiation.js +30 -57
  117. package/build/utils/communication/notification.js +112 -252
  118. package/build/utils/{date-converter.js → dateConverter.js} +62 -30
  119. package/build/utils/effectSchema.js +203 -0
  120. package/build/utils/effectSchemaAdapter.js +121 -0
  121. package/build/utils/effectSchemaChainMethods.js +146 -0
  122. package/build/utils/effectSchemaRefinements.js +104 -0
  123. package/build/utils/effectSchemaStringMethods.js +63 -0
  124. package/build/utils/effectSchemaTypes.js +47 -0
  125. package/build/utils/{env-parser.js → envParser.js} +10 -2
  126. package/build/utils/errors.js +7 -3
  127. package/build/utils/http.js +148 -89
  128. package/build/utils/{llm-client-detector.js → llmClientDetector.js} +295 -274
  129. package/build/utils/logger.js +127 -51
  130. package/build/utils/{oauth-helper.js → oauthHelper.js} +127 -90
  131. package/build/utils/other/compliance.js +18 -38
  132. package/build/utils/other/identity.js +2 -2
  133. package/build/utils/other/translation.js +4 -4
  134. package/build/utils/other/vero.js +36 -20
  135. package/build/utils/schemaHelpers.js +22 -0
  136. package/build/utils/scopeHelper.js +270 -0
  137. package/build/utils/securityChecker.js +316 -0
  138. package/build/utils/{setup-validator.js → setupValidator.js} +113 -94
  139. package/build/utils/{setup-wizard.js → setupWizard.js} +57 -46
  140. package/build/utils/{token-utils.js → tokenUtils.js} +2 -2
  141. package/build/utils/trading/trading.js +35 -0
  142. package/build/utils/typeGuards.js +12 -0
  143. package/build/utils/version.js +65 -29
  144. package/package.json +10 -8
  145. package/build/api/client-trading.js +0 -124
  146. package/build/api/communication/shared.js +0 -59
  147. package/build/api/shared/query-params.js +0 -16
  148. package/build/auth/credential-session.js +0 -166
  149. package/build/auth/oauth-middleware.js +0 -100
  150. package/build/auth/token-verifier.js +0 -186
  151. package/build/schemas/other/other-apis.js +0 -390
  152. package/build/scripts/update-api-status-doc.js +0 -44
  153. package/build/server-http.js +0 -85
  154. package/build/tools/definitions/marketing.js +0 -1381
  155. package/build/tools/tool-handlers/marketing.js +0 -312
  156. package/build/utils/account-management/account.js +0 -818
  157. package/build/utils/api-status-feed.js +0 -84
  158. package/build/utils/cli-ui.js +0 -47
  159. package/build/utils/order-management/dispute.js +0 -369
  160. package/build/utils/order-management/fulfillment.js +0 -205
  161. package/build/utils/other/edelivery.js +0 -235
  162. package/build/utils/schema-helpers.js +0 -13
  163. package/build/utils/scope-helper.js +0 -207
  164. package/build/utils/security-checker.js +0 -248
  165. package/build/utils/type-guards.js +0 -6
  166. /package/build/auth/{oauth-types.js → oauthTypes.js} +0 -0
  167. /package/build/{server-http.d.ts → serverHttp.d.ts} +0 -0
  168. /package/build/tools/ui/{view-models.js → viewModels.js} +0 -0
  169. /package/build/types/{ebay-enums.js → ebayEnums.js} +0 -0
@@ -4,20 +4,22 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
4
4
  import { homedir, platform } from 'os';
5
5
  import { randomUUID } from 'crypto';
6
6
  import chalk from 'chalk';
7
+ import { Data, Effect, Either } from 'effect';
7
8
  import { checkForUpdates } from '../utils/version.js';
8
9
  import { describeHttpError, httpRequest } from '../utils/http.js';
9
10
  import { config } from 'dotenv';
10
11
  import { fileURLToPath } from 'url';
11
12
  import { getOAuthAuthorizationUrl } from '../config/environment.js';
12
- import { startCallbackServer } from '../utils/oauth-helper.js';
13
- import { defineWizard, runWizard, ClackRenderer } from '../utils/setup-wizard.js';
14
- import { loadExistingConfig, quoteEnvValue } from './setup-shared.js';
13
+ import { startCallbackServer } from '../utils/oauthHelper.js';
14
+ import { defineWizard, runWizard } from '../utils/setupWizard.js';
15
+ import { loadExistingConfig, quoteEnvValue } from './setupShared.js';
15
16
  import { runSkillsWizard } from './skills.js';
16
17
  import prompts from 'prompts';
17
- import { configureLLMClient, detectLLMClients } from '../utils/llm-client-detector.js';
18
- import { runSecurityChecks, displaySecurityResults } from '../utils/security-checker.js';
19
- import { validateSetup, displayRecommendations } from '../utils/setup-validator.js';
18
+ import { configureLLMClient, detectLLMClients } from '../utils/llmClientDetector.js';
19
+ import { runSecurityChecks, displaySecurityResults } from '../utils/securityChecker.js';
20
+ import { validateSetup, displayRecommendations } from '../utils/setupValidator.js';
20
21
  import { getErrorMessage } from '../utils/errors.js';
22
+ import process from 'node:process';
21
23
  config({ quiet: true });
22
24
  checkForUpdates();
23
25
  const __filename = fileURLToPath(import.meta.url);
@@ -67,6 +69,9 @@ const ui = {
67
69
  };
68
70
  const DEVELOPER_NAME = 'Yosef Hayim Sabag';
69
71
  const DEVELOPER_LINKEDIN = 'https://www.linkedin.com/in/yosef-hayim-sabag/';
72
+ /** Tagged failure raised while the setup wizard captures the OAuth redirect. */
73
+ class SetupCaptureError extends Data.TaggedError('SetupCaptureError') {
74
+ }
70
75
  /** Render the wizard's branding header: logo, title, and developer credit. */
71
76
  function printWizardBanner() {
72
77
  console.log(LOGO);
@@ -79,29 +84,26 @@ function printWizardBanner() {
79
84
  function parseAuthorizationCode(input) {
80
85
  const trimmed = input.trim();
81
86
  if (trimmed.includes('code=') || trimmed.includes('?') || trimmed.includes('&')) {
82
- try {
83
- let searchParams;
84
- if (trimmed.startsWith('http')) {
85
- searchParams = new URL(trimmed).searchParams;
86
- }
87
- else {
88
- searchParams = new URLSearchParams(trimmed.startsWith('?') ? trimmed.slice(1) : trimmed);
89
- }
90
- const code = searchParams.get('code');
91
- if (code)
92
- return decodeURIComponent(code);
93
- }
94
- catch {
95
- // fall through
87
+ const parsedCode = Effect.runSync(Effect.either(Effect.try({
88
+ try: () => {
89
+ const searchParams = trimmed.startsWith('http')
90
+ ? new URL(trimmed).searchParams
91
+ : new URLSearchParams(trimmed.startsWith('?') ? trimmed.slice(1) : trimmed);
92
+ const code = searchParams.get('code');
93
+ return code ? decodeURIComponent(code) : null;
94
+ },
95
+ catch: (error) => error,
96
+ })));
97
+ if (Either.isRight(parsedCode) && parsedCode.right) {
98
+ return parsedCode.right;
96
99
  }
97
100
  }
98
101
  if (trimmed.startsWith('v^1.1#') || trimmed.startsWith('v%5E1.1')) {
99
- try {
100
- return decodeURIComponent(trimmed);
101
- }
102
- catch {
103
- return trimmed;
104
- }
102
+ const decodedToken = Effect.runSync(Effect.either(Effect.try({
103
+ try: () => decodeURIComponent(trimmed),
104
+ catch: (error) => error,
105
+ })));
106
+ return Either.isRight(decodedToken) ? decodedToken.right : trimmed;
105
107
  }
106
108
  return null;
107
109
  }
@@ -210,63 +212,69 @@ function updateClaudeDesktopConfig(envConfig, environment) {
210
212
  const configPath = getClaudeDesktopConfigPath();
211
213
  if (!existsSync(dirname(configPath)))
212
214
  return { success: false, configPath, error: 'Claude Desktop not installed' };
213
- try {
214
- let existing = {};
215
- if (existsSync(configPath)) {
216
- try {
217
- existing = JSON.parse(readFileSync(configPath, 'utf-8'));
218
- }
219
- catch (e) {
220
- return {
221
- success: false,
222
- configPath,
223
- error: `Invalid JSON in config: ${getErrorMessage(e, 'parse error')}`,
224
- details: 'Please fix the JSON syntax in your Claude config file',
225
- };
215
+ const updated = Effect.runSync(Effect.either(Effect.try({
216
+ try: () => {
217
+ let existing = {};
218
+ if (existsSync(configPath)) {
219
+ const parsed = Effect.runSync(Effect.either(Effect.try({
220
+ try: () => JSON.parse(readFileSync(configPath, 'utf-8')),
221
+ catch: (error) => error,
222
+ })));
223
+ if (Either.isLeft(parsed)) {
224
+ return {
225
+ success: false,
226
+ configPath,
227
+ error: `Invalid JSON in config: ${getErrorMessage(parsed.left, 'parse error')}`,
228
+ details: 'Please fix the JSON syntax in your Claude config file',
229
+ };
230
+ }
231
+ existing = parsed.right;
226
232
  }
227
- }
228
- if (!existing.mcpServers || typeof existing.mcpServers !== 'object')
229
- existing.mcpServers = {};
230
- const mcpServers = existing.mcpServers;
231
- const envVars = { EBAY_ENVIRONMENT: environment };
232
- if (envConfig.EBAY_CLIENT_ID)
233
- envVars.EBAY_CLIENT_ID = envConfig.EBAY_CLIENT_ID;
234
- if (envConfig.EBAY_CLIENT_SECRET)
235
- envVars.EBAY_CLIENT_SECRET = envConfig.EBAY_CLIENT_SECRET;
236
- if (envConfig.EBAY_REDIRECT_URI)
237
- envVars.EBAY_REDIRECT_URI = envConfig.EBAY_REDIRECT_URI;
238
- if (envConfig.EBAY_MARKETPLACE_ID)
239
- envVars.EBAY_MARKETPLACE_ID = envConfig.EBAY_MARKETPLACE_ID;
240
- if (envConfig.EBAY_CONTENT_LANGUAGE)
241
- envVars.EBAY_CONTENT_LANGUAGE = envConfig.EBAY_CONTENT_LANGUAGE;
242
- if (envConfig.EBAY_USER_REFRESH_TOKEN)
243
- envVars.EBAY_USER_REFRESH_TOKEN = envConfig.EBAY_USER_REFRESH_TOKEN;
244
- if (envConfig.EBAY_USER_ACCESS_TOKEN?.startsWith('v^'))
245
- envVars.EBAY_USER_ACCESS_TOKEN = envConfig.EBAY_USER_ACCESS_TOKEN;
246
- if (envConfig.EBAY_APP_ACCESS_TOKEN?.startsWith('v^'))
247
- envVars.EBAY_APP_ACCESS_TOKEN = envConfig.EBAY_APP_ACCESS_TOKEN;
248
- mcpServers.ebay = {
249
- command: 'npx',
250
- args: ['--yes', '--quiet', 'ebay-mcp'],
251
- env: { ...envVars, NODE_NO_WARNINGS: '1', NPM_CONFIG_UPDATE_NOTIFIER: 'false' },
252
- };
253
- writeFileSync(configPath, JSON.stringify(existing, null, 2));
254
- const otherServers = Object.keys(mcpServers).filter((k) => k !== 'ebay');
255
- return {
256
- success: true,
257
- configPath,
258
- details: otherServers.length > 0
259
- ? `Preserved ${otherServers.length} existing server(s): ${otherServers.join(', ')}`
260
- : `Added ebay server (${Object.keys(mcpServers).length} total)`,
261
- };
262
- }
263
- catch (error) {
233
+ if (!existing.mcpServers || typeof existing.mcpServers !== 'object')
234
+ existing.mcpServers = {};
235
+ const mcpServers = existing.mcpServers;
236
+ const envVars = { EBAY_ENVIRONMENT: environment };
237
+ if (envConfig.EBAY_CLIENT_ID)
238
+ envVars.EBAY_CLIENT_ID = envConfig.EBAY_CLIENT_ID;
239
+ if (envConfig.EBAY_CLIENT_SECRET)
240
+ envVars.EBAY_CLIENT_SECRET = envConfig.EBAY_CLIENT_SECRET;
241
+ if (envConfig.EBAY_REDIRECT_URI)
242
+ envVars.EBAY_REDIRECT_URI = envConfig.EBAY_REDIRECT_URI;
243
+ if (envConfig.EBAY_MARKETPLACE_ID)
244
+ envVars.EBAY_MARKETPLACE_ID = envConfig.EBAY_MARKETPLACE_ID;
245
+ if (envConfig.EBAY_CONTENT_LANGUAGE)
246
+ envVars.EBAY_CONTENT_LANGUAGE = envConfig.EBAY_CONTENT_LANGUAGE;
247
+ if (envConfig.EBAY_USER_REFRESH_TOKEN)
248
+ envVars.EBAY_USER_REFRESH_TOKEN = envConfig.EBAY_USER_REFRESH_TOKEN;
249
+ if (envConfig.EBAY_USER_ACCESS_TOKEN?.startsWith('v^'))
250
+ envVars.EBAY_USER_ACCESS_TOKEN = envConfig.EBAY_USER_ACCESS_TOKEN;
251
+ if (envConfig.EBAY_APP_ACCESS_TOKEN?.startsWith('v^'))
252
+ envVars.EBAY_APP_ACCESS_TOKEN = envConfig.EBAY_APP_ACCESS_TOKEN;
253
+ mcpServers.ebay = {
254
+ command: 'npx',
255
+ args: ['--yes', '--quiet', 'ebay-mcp'],
256
+ env: { ...envVars, NODE_NO_WARNINGS: '1', NPM_CONFIG_UPDATE_NOTIFIER: 'false' },
257
+ };
258
+ writeFileSync(configPath, JSON.stringify(existing, null, 2));
259
+ const otherServers = Object.keys(mcpServers).filter((k) => k !== 'ebay');
260
+ return {
261
+ success: true,
262
+ configPath,
263
+ details: otherServers.length > 0
264
+ ? `Preserved ${otherServers.length} existing server(s): ${otherServers.join(', ')}`
265
+ : `Added ebay server (${Object.keys(mcpServers).length} total)`,
266
+ };
267
+ },
268
+ catch: (error) => error,
269
+ })));
270
+ if (Either.isLeft(updated)) {
264
271
  return {
265
272
  success: false,
266
273
  configPath,
267
- error: getErrorMessage(error, 'Unknown'),
274
+ error: getErrorMessage(updated.left, 'Unknown'),
268
275
  };
269
276
  }
277
+ return updated.right;
270
278
  }
271
279
  function formatDate(date) {
272
280
  return date.toLocaleString('en-US', {
@@ -333,8 +341,8 @@ function showSetupProgress(message) {
333
341
  function displayUserInfo(userInfo) {
334
342
  const name = userInfo.individualAccount?.firstName && userInfo.individualAccount?.lastName
335
343
  ? `${userInfo.individualAccount.firstName} ${userInfo.individualAccount.lastName}`
336
- : userInfo.businessAccount?.name || 'N/A';
337
- const email = userInfo.individualAccount?.email || userInfo.businessAccount?.email || 'N/A';
344
+ : userInfo.businessAccount?.name || 'No account name';
345
+ const email = userInfo.individualAccount?.email || userInfo.businessAccount?.email || 'No account email';
338
346
  const marketplaceMap = {
339
347
  EBAY_US: 'eBay United States',
340
348
  EBAY_GB: 'eBay United Kingdom',
@@ -347,12 +355,12 @@ function displayUserInfo(userInfo) {
347
355
  };
348
356
  const marketplace = marketplaceMap[userInfo.registrationMarketplaceId || ''] ||
349
357
  userInfo.registrationMarketplaceId ||
350
- 'N/A';
358
+ 'No marketplace';
351
359
  console.log(` ${ui.dim('┌─ eBay Account Verified ─────────────────────────')}`);
352
360
  console.log(` ${ui.dim('│')} Username: ${userInfo.username}`);
353
361
  console.log(` ${ui.dim('│')} Name: ${name}`);
354
362
  console.log(` ${ui.dim('│')} Email: ${email}`);
355
- console.log(` ${ui.dim('│')} Type: ${userInfo.accountType || 'N/A'}`);
363
+ console.log(` ${ui.dim('│')} Type: ${userInfo.accountType || 'No account type'}`);
356
364
  console.log(` ${ui.dim('│')} Marketplace: ${marketplace}`);
357
365
  console.log(` ${ui.dim('└─────────────────────────────────────────────────')}`);
358
366
  console.log('');
@@ -372,7 +380,7 @@ const DEFAULT_CALLBACK_PORT = 3000;
372
380
  function getCallbackPort() {
373
381
  const raw = process.env.EBAY_OAUTH_CALLBACK_PORT;
374
382
  const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
375
- return Number.isInteger(parsed) && parsed > 0 && parsed < 65536 ? parsed : DEFAULT_CALLBACK_PORT;
383
+ return Number.isInteger(parsed) && parsed > 0 && parsed < 65_536 ? parsed : DEFAULT_CALLBACK_PORT;
376
384
  }
377
385
  /**
378
386
  * Exchange an authorization code for tokens, verify the eBay account, mint an app
@@ -382,62 +390,76 @@ function getCallbackPort() {
382
390
  async function completeOAuthWithCode(authCode, params, tokens) {
383
391
  const { clientId, clientSecret, redirectUri, environment, answers } = params;
384
392
  const stopSpinner = showSetupProgress('Exchanging authorization code for tokens');
385
- try {
386
- const result = await exchangeAuthorizationCode(authCode, clientId, clientSecret, redirectUri, environment);
387
- stopSpinner();
388
- showSuccess('Authorization code exchanged successfully!');
389
- tokens.refreshToken = result.refreshToken;
390
- tokens.accessToken = result.accessToken;
391
- try {
392
- const userInfo = await fetchEbayUserInfo(result.accessToken, environment);
393
- showSuccess('Account verified!');
394
- displayUserInfo(userInfo);
395
- }
396
- catch (userError) {
397
- const userMsg = describeHttpError(userError, 'Unknown');
398
- showWarning(`Could not fetch account info: ${userMsg}`);
399
- if (userMsg.toLowerCase().includes('access denied'))
400
- showInfo('Normal if RuName lacks commerce.identity.readonly scope — tokens are valid.');
401
- }
402
- try {
403
- tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
404
- showSuccess('App access token obtained.');
405
- }
406
- catch {
407
- showWarning('Could not obtain app access token (user tokens still work).');
408
- }
409
- showInfo(`Access token expires in: ${Math.floor(result.expiresIn / 60)} minutes`);
410
- showInfo(`Refresh token expires in: ${Math.floor(result.refreshTokenExpiresIn / 60 / 60 / 24)} days`);
411
- if (isClaudeDesktopInstalled()) {
412
- const r = updateClaudeDesktopConfig({
413
- ...answers,
414
- EBAY_USER_REFRESH_TOKEN: tokens.refreshToken ?? '',
415
- EBAY_USER_ACCESS_TOKEN: tokens.accessToken ?? '',
416
- }, environment);
417
- if (r.success) {
418
- showSuccess('Claude Desktop config updated.');
419
- if (r.details)
420
- showInfo(r.details);
421
- }
422
- else
423
- showWarning(`Could not update Claude Desktop: ${r.error}`);
424
- }
425
- }
426
- catch (error) {
427
- stopSpinner();
428
- const msg = describeHttpError(error, 'Unknown error');
393
+ const exchanged = await Effect.runPromise(Effect.either(Effect.tryPromise({
394
+ try: () => exchangeAuthorizationCode(authCode, clientId, clientSecret, redirectUri, environment),
395
+ catch: (error) => error,
396
+ }).pipe(Effect.ensuring(Effect.sync(stopSpinner)))));
397
+ if (Either.isLeft(exchanged)) {
398
+ const msg = describeHttpError(exchanged.left, 'Unknown error');
429
399
  showError(`Failed to exchange code: ${msg}`);
430
400
  console.log(' Common issues:');
431
401
  console.log(' • Authorization code expired (codes are valid for ~5 minutes)');
432
402
  console.log(' • Code was already used (each code can only be used once)');
433
403
  console.log(' • Redirect URI mismatch\n');
404
+ return;
405
+ }
406
+ const result = exchanged.right;
407
+ showSuccess('Authorization code exchanged successfully!');
408
+ tokens.refreshToken = result.refreshToken;
409
+ tokens.accessToken = result.accessToken;
410
+ const user = await Effect.runPromise(Effect.either(Effect.tryPromise({
411
+ try: () => fetchEbayUserInfo(result.accessToken, environment),
412
+ catch: (error) => error,
413
+ })));
414
+ if (Either.isRight(user)) {
415
+ showSuccess('Account verified!');
416
+ displayUserInfo(user.right);
417
+ }
418
+ else {
419
+ const userMsg = describeHttpError(user.left, 'Unknown');
420
+ showWarning(`Could not fetch account info: ${userMsg}`);
421
+ if (userMsg.toLowerCase().includes('access denied'))
422
+ showInfo('Normal if RuName lacks commerce.identity.readonly scope — tokens are valid.');
423
+ }
424
+ const appToken = await Effect.runPromise(Effect.either(Effect.tryPromise({
425
+ try: () => getAppAccessToken(clientId, clientSecret, environment),
426
+ catch: (error) => error,
427
+ })));
428
+ if (Either.isRight(appToken)) {
429
+ tokens.appAccessToken = appToken.right;
430
+ showSuccess('App access token obtained.');
431
+ }
432
+ else {
433
+ showWarning('Could not obtain app access token (user tokens still work).');
434
+ }
435
+ showInfo(`Access token expires in: ${Math.floor(result.expiresIn / 60)} minutes`);
436
+ showInfo(`Refresh token expires in: ${Math.floor(result.refreshTokenExpiresIn / 60 / 60 / 24)} days`);
437
+ if (isClaudeDesktopInstalled()) {
438
+ const r = updateClaudeDesktopConfig({
439
+ ...answers,
440
+ EBAY_USER_REFRESH_TOKEN: tokens.refreshToken ?? '',
441
+ EBAY_USER_ACCESS_TOKEN: tokens.accessToken ?? '',
442
+ }, environment);
443
+ if (r.success) {
444
+ showSuccess('Claude Desktop config updated.');
445
+ if (r.details)
446
+ showInfo(r.details);
447
+ }
448
+ else
449
+ showWarning(`Could not update Claude Desktop: ${r.error}`);
434
450
  }
435
451
  }
436
452
  // ─── Setup wizard ─────────────────────────────────────────────────────────────
437
453
  /**
438
454
  * Runs the interactive eBay MCP setup wizard from the script entry point.
455
+ *
456
+ * @returns A promise that resolves after setup completes.
457
+ * @example
458
+ * ```ts
459
+ * await runSetup();
460
+ * ```
439
461
  */
440
- export async function runSetup() {
462
+ export const runSetup = async () => {
441
463
  const existingConfig = loadExistingConfig(PROJECT_ROOT);
442
464
  const detectedClients = detectLLMClients();
443
465
  const availableClients = detectedClients.filter((c) => c.detected);
@@ -604,8 +626,6 @@ export async function runSetup() {
604
626
  ],
605
627
  });
606
628
  const answers = await runWizard(wizardConfig, {
607
- renderer: new ClackRenderer(),
608
- quiet: true,
609
629
  optionsProvider: (stepId) => {
610
630
  if (stepId === 'oauth-method') {
611
631
  const hasToken = existingConfig.EBAY_USER_REFRESH_TOKEN?.startsWith('v^1.1#');
@@ -623,7 +643,6 @@ export async function runSetup() {
623
643
  ];
624
644
  }
625
645
  }
626
- return undefined;
627
646
  },
628
647
  asyncValidate: (stepId, value) => {
629
648
  if (stepId === 'oauth-token') {
@@ -658,18 +677,25 @@ export async function runSetup() {
658
677
  switch (method) {
659
678
  case 'keep': {
660
679
  const stopSpinner = showSetupProgress('Verifying existing refresh token...');
661
- try {
662
- const { accessToken, userInfo } = await verifyRefreshToken(existingConfig.EBAY_USER_REFRESH_TOKEN, clientId, clientSecret, environment);
663
- stopSpinner();
680
+ const verified = await Effect.runPromise(Effect.either(Effect.tryPromise({
681
+ try: () => verifyRefreshToken(existingConfig.EBAY_USER_REFRESH_TOKEN, clientId, clientSecret, environment),
682
+ catch: (error) => error,
683
+ }).pipe(Effect.ensuring(Effect.sync(stopSpinner)))));
684
+ if (Either.isRight(verified)) {
685
+ const { accessToken, userInfo } = verified.right;
664
686
  showSuccess('Refresh token verified!');
665
687
  tokens.refreshToken = existingConfig.EBAY_USER_REFRESH_TOKEN;
666
688
  tokens.accessToken = accessToken;
667
689
  displayUserInfo(userInfo);
668
- try {
669
- tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
690
+ const appToken = await Effect.runPromise(Effect.either(Effect.tryPromise({
691
+ try: () => getAppAccessToken(clientId, clientSecret, environment),
692
+ catch: (error) => error,
693
+ })));
694
+ if (Either.isRight(appToken)) {
695
+ tokens.appAccessToken = appToken.right;
670
696
  showSuccess('App access token obtained.');
671
697
  }
672
- catch {
698
+ else {
673
699
  showWarning('Could not obtain app access token (user tokens still work).');
674
700
  }
675
701
  if (isClaudeDesktopInstalled()) {
@@ -686,9 +712,8 @@ export async function runSetup() {
686
712
  showWarning(`Could not update Claude Desktop: ${r.error}`);
687
713
  }
688
714
  }
689
- catch (error) {
690
- stopSpinner();
691
- const msg = describeHttpError(error, 'Unknown error');
715
+ else {
716
+ const msg = describeHttpError(verified.left, 'Unknown error');
692
717
  showError(`Token verification failed: ${msg}`);
693
718
  if (msg.toLowerCase().includes('access denied'))
694
719
  showWarning('Token may be missing required OAuth scopes.');
@@ -720,24 +745,52 @@ export async function runSetup() {
720
745
  "The tunnel forwards eBay's redirect here, where the code is captured.",
721
746
  'Override the local port with EBAY_OAUTH_CALLBACK_PORT.',
722
747
  ].join('\n'));
723
- const authUrl = getOAuthAuthorizationUrl(clientId, redirectUri, environment, undefined, undefined, state);
748
+ const authUrl = getOAuthAuthorizationUrl(clientId, redirectUri, environment, undefined, state);
724
749
  let server;
725
750
  const stopSpinner = showSetupProgress('Waiting for eBay to redirect back (up to 5 min)');
726
- try {
727
- const started = await startCallbackServer(callbackPort, 300000, {
728
- expectedState: state,
751
+ const captured = await Effect.runPromise(Effect.either(Effect.gen(function* () {
752
+ const started = yield* Effect.tryPromise({
753
+ try: () => startCallbackServer(callbackPort, 300_000, {
754
+ expectedState: state,
755
+ }),
756
+ catch: (cause) => new SetupCaptureError({
757
+ message: 'Failed to start OAuth callback server',
758
+ cause,
759
+ }),
729
760
  });
730
761
  server = started.server;
731
- await context.openBrowser(authUrl);
762
+ yield* Effect.tryPromise({
763
+ try: () => context.openBrowser(authUrl),
764
+ catch: (cause) => new SetupCaptureError({
765
+ message: 'Failed to open OAuth authorization URL',
766
+ cause,
767
+ }),
768
+ });
732
769
  showInfo('1. Sign in to your eBay account in the browser');
733
770
  showInfo('2. Grant permissions to your app');
734
771
  showInfo('3. eBay redirects back automatically — no copy-paste needed');
735
- const result = await started.codePromise;
772
+ const result = yield* Effect.tryPromise({
773
+ try: () => started.codePromise,
774
+ catch: (cause) => new SetupCaptureError({
775
+ message: 'Failed while waiting for eBay OAuth redirect',
776
+ cause,
777
+ }),
778
+ });
779
+ if (!result.code) {
780
+ return yield* Effect.fail(new SetupCaptureError({
781
+ message: result.errorDescription ??
782
+ result.error ??
783
+ 'No authorization code received',
784
+ }));
785
+ }
786
+ return result.code;
787
+ }).pipe(Effect.ensuring(Effect.sync(() => {
736
788
  stopSpinner();
737
- if (!result.code)
738
- throw new Error(result.errorDescription ?? result.error ?? 'No authorization code received');
789
+ server?.close();
790
+ })))));
791
+ if (Either.isRight(captured)) {
739
792
  showSuccess('Authorization code captured automatically!');
740
- await completeOAuthWithCode(result.code, {
793
+ await completeOAuthWithCode(captured.right, {
741
794
  clientId,
742
795
  clientSecret,
743
796
  redirectUri,
@@ -746,16 +799,12 @@ export async function runSetup() {
746
799
  }, tokens);
747
800
  context.setNextStep(finalStepId);
748
801
  }
749
- catch (error) {
750
- stopSpinner();
751
- showError(`Auto-capture failed: ${getErrorMessage(error, 'Unknown error')}`);
802
+ else {
803
+ showError(`Auto-capture failed: ${getErrorMessage(captured.left, 'Unknown error')}`);
752
804
  showInfo('Falling back to manual paste — copy the redirect URL from your browser.');
753
805
  context.showNote('OAuth Authorization URL', authUrl);
754
806
  context.setNextStep('oauth-code');
755
807
  }
756
- finally {
757
- server?.close();
758
- }
759
808
  break;
760
809
  }
761
810
  case 'manual': {
@@ -785,17 +834,24 @@ export async function runSetup() {
785
834
  .replace(/^["']|["']$/g, '');
786
835
  tokens.refreshToken = rawToken;
787
836
  const stopSpinner = showSetupProgress('Verifying refresh token...');
788
- try {
789
- const { accessToken, userInfo } = await verifyRefreshToken(rawToken, clientId, clientSecret, environment);
790
- stopSpinner();
837
+ const verified = await Effect.runPromise(Effect.either(Effect.tryPromise({
838
+ try: () => verifyRefreshToken(rawToken, clientId, clientSecret, environment),
839
+ catch: (error) => error,
840
+ }).pipe(Effect.ensuring(Effect.sync(stopSpinner)))));
841
+ if (Either.isRight(verified)) {
842
+ const { accessToken, userInfo } = verified.right;
791
843
  showSuccess('Refresh token verified!');
792
844
  tokens.accessToken = accessToken;
793
845
  displayUserInfo(userInfo);
794
- try {
795
- tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
846
+ const appToken = await Effect.runPromise(Effect.either(Effect.tryPromise({
847
+ try: () => getAppAccessToken(clientId, clientSecret, environment),
848
+ catch: (error) => error,
849
+ })));
850
+ if (Either.isRight(appToken)) {
851
+ tokens.appAccessToken = appToken.right;
796
852
  showSuccess('App access token obtained.');
797
853
  }
798
- catch {
854
+ else {
799
855
  showWarning('Could not obtain app access token (user tokens still work).');
800
856
  }
801
857
  if (isClaudeDesktopInstalled()) {
@@ -809,9 +865,8 @@ export async function runSetup() {
809
865
  showWarning(`Could not update Claude Desktop: ${r.error}`);
810
866
  }
811
867
  }
812
- catch (error) {
813
- stopSpinner();
814
- const msg = describeHttpError(error, 'Unknown error');
868
+ else {
869
+ const msg = describeHttpError(verified.left, 'Unknown error');
815
870
  showError(`Token verification failed: ${msg}`);
816
871
  showWarning("Token saved anyway — you may need to re-authenticate if it doesn't work.");
817
872
  }
@@ -864,6 +919,13 @@ export async function runSetup() {
864
919
  ? answers['content-language-custom']
865
920
  : answers['content-language'];
866
921
  const environment = answers.environment;
922
+ const refreshTokenConfig = {};
923
+ if (tokens.refreshToken) {
924
+ refreshTokenConfig.EBAY_USER_REFRESH_TOKEN = tokens.refreshToken;
925
+ }
926
+ else if (existingConfig.EBAY_USER_REFRESH_TOKEN) {
927
+ refreshTokenConfig.EBAY_USER_REFRESH_TOKEN = existingConfig.EBAY_USER_REFRESH_TOKEN;
928
+ }
867
929
  const finalConfig = {
868
930
  EBAY_CLIENT_ID: answers['client-id'],
869
931
  EBAY_CLIENT_SECRET: answers['client-secret'] || existingConfig.EBAY_CLIENT_SECRET || '',
@@ -871,11 +933,7 @@ export async function runSetup() {
871
933
  EBAY_ENVIRONMENT: environment,
872
934
  ...(marketplaceId ? { EBAY_MARKETPLACE_ID: marketplaceId } : {}),
873
935
  ...(contentLanguage ? { EBAY_CONTENT_LANGUAGE: contentLanguage } : {}),
874
- ...(tokens.refreshToken
875
- ? { EBAY_USER_REFRESH_TOKEN: tokens.refreshToken }
876
- : existingConfig.EBAY_USER_REFRESH_TOKEN
877
- ? { EBAY_USER_REFRESH_TOKEN: existingConfig.EBAY_USER_REFRESH_TOKEN }
878
- : {}),
936
+ ...refreshTokenConfig,
879
937
  ...(tokens.accessToken ? { EBAY_USER_ACCESS_TOKEN: tokens.accessToken } : {}),
880
938
  ...(tokens.appAccessToken ? { EBAY_APP_ACCESS_TOKEN: tokens.appAccessToken } : {}),
881
939
  };
@@ -926,17 +984,17 @@ export async function runSetup() {
926
984
  showInfo('You can install them later with: npm run skills');
927
985
  }
928
986
  }
929
- }
987
+ };
930
988
  // ─── Entry point ──────────────────────────────────────────────────────────────
931
- function parseArgs() {
989
+ const parseArgs = () => {
932
990
  const args = process.argv.slice(2);
933
991
  return {
934
992
  help: args.includes('--help') || args.includes('-h'),
935
993
  quick: args.includes('--quick') || args.includes('-q'),
936
994
  diagnose: args.includes('--diagnose') || args.includes('-d'),
937
995
  };
938
- }
939
- function showHelp() {
996
+ };
997
+ const showHelp = () => {
940
998
  console.log(`
941
999
  ${chalk.bold('eBay MCP Server Setup')} ${chalk.dim('interactive setup')}
942
1000
 
@@ -953,8 +1011,8 @@ ${chalk.bold('Examples:')}
953
1011
  npm run setup --quick Skip optional configuration
954
1012
  npm run setup --diagnose Check system health
955
1013
  `);
956
- }
957
- async function main() {
1014
+ };
1015
+ const main = async () => {
958
1016
  const args = parseArgs();
959
1017
  if (args.help) {
960
1018
  showHelp();
@@ -970,7 +1028,7 @@ async function main() {
970
1028
  process.exit(0);
971
1029
  }
972
1030
  await runSetup();
973
- }
1031
+ };
974
1032
  process.on('SIGINT', () => {
975
1033
  console.log(ui.warning('\n\n Setup interrupted.\n'));
976
1034
  process.exit(0);
@@ -978,8 +1036,13 @@ process.on('SIGINT', () => {
978
1036
  const entryPath = process.argv[1] ? resolve(process.argv[1]) : undefined;
979
1037
  const modulePath = resolve(fileURLToPath(import.meta.url));
980
1038
  if (entryPath && modulePath === entryPath) {
981
- main().catch((error) => {
982
- console.error(ui.error('\n Setup failed:'), error);
983
- process.exit(1);
1039
+ void Effect.runPromise(Effect.either(Effect.tryPromise({
1040
+ try: () => main(),
1041
+ catch: (error) => error,
1042
+ }))).then((result) => {
1043
+ if (Either.isLeft(result)) {
1044
+ console.error(ui.error('\n Setup failed:'), getErrorMessage(result.left));
1045
+ process.exitCode = 1;
1046
+ }
984
1047
  });
985
1048
  }