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
@@ -2,11 +2,13 @@ import winston from 'winston';
2
2
  import { join } from 'path';
3
3
  import { existsSync, mkdirSync } from 'fs';
4
4
  import { homedir } from 'os';
5
+ import { getLoggingConfig } from '../config/environment.js';
5
6
  /**
6
7
  * Log directory for eBay MCP Server
7
8
  * Stored in user's home directory under .ebay-mcp/logs
8
9
  */
9
10
  const LOG_DIR = join(homedir(), '.ebay-mcp', 'logs');
11
+ const loggingConfig = getLoggingConfig();
10
12
  // Ensure log directory exists
11
13
  if (!existsSync(LOG_DIR)) {
12
14
  mkdirSync(LOG_DIR, { recursive: true });
@@ -15,11 +17,11 @@ if (!existsSync(LOG_DIR)) {
15
17
  * Log level from environment variable or default to 'info'
16
18
  * Levels: error, warn, info, http, verbose, debug, silly
17
19
  */
18
- const LOG_LEVEL = process.env.EBAY_LOG_LEVEL || 'info';
20
+ const LOG_LEVEL = loggingConfig.logLevel;
19
21
  /**
20
22
  * Whether to enable file logging (disabled in production MCP mode by default)
21
23
  */
22
- const ENABLE_FILE_LOGGING = process.env.EBAY_ENABLE_FILE_LOGGING === 'true';
24
+ let fileLoggingEnabled = loggingConfig.enableFileLogging;
23
25
  /**
24
26
  * Custom log format with timestamp and colored output
25
27
  */
@@ -43,7 +45,7 @@ const transports = [
43
45
  }),
44
46
  ];
45
47
  // Add file transports if enabled
46
- if (ENABLE_FILE_LOGGING) {
48
+ if (fileLoggingEnabled) {
47
49
  transports.push(
48
50
  // Error log - only errors
49
51
  new winston.transports.File({
@@ -82,29 +84,36 @@ const logger = winston.createLogger({
82
84
  });
83
85
  /**
84
86
  * Create a child logger for a specific component
87
+ *
88
+ * @param component - Component label prefixed to every emitted message.
89
+ * @returns Component-scoped logger methods.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const logger = createLogger('API');
94
+ * logger.info('Ready');
95
+ * ```
85
96
  */
86
- export function createLogger(component) {
87
- return {
88
- error: (message, meta) => {
89
- logger.error(`[${component}] ${message}`, meta);
90
- },
91
- warn: (message, meta) => {
92
- logger.warn(`[${component}] ${message}`, meta);
93
- },
94
- info: (message, meta) => {
95
- logger.info(`[${component}] ${message}`, meta);
96
- },
97
- http: (message, meta) => {
98
- logger.http(`[${component}] ${message}`, meta);
99
- },
100
- debug: (message, meta) => {
101
- logger.debug(`[${component}] ${message}`, meta);
102
- },
103
- verbose: (message, meta) => {
104
- logger.verbose(`[${component}] ${message}`, meta);
105
- },
106
- };
107
- }
97
+ export const createLogger = (component) => ({
98
+ error: (message, meta) => {
99
+ logger.error(`[${component}] ${message}`, meta);
100
+ },
101
+ warn: (message, meta) => {
102
+ logger.warn(`[${component}] ${message}`, meta);
103
+ },
104
+ info: (message, meta) => {
105
+ logger.info(`[${component}] ${message}`, meta);
106
+ },
107
+ http: (message, meta) => {
108
+ logger.http(`[${component}] ${message}`, meta);
109
+ },
110
+ debug: (message, meta) => {
111
+ logger.debug(`[${component}] ${message}`, meta);
112
+ },
113
+ verbose: (message, meta) => {
114
+ logger.verbose(`[${component}] ${message}`, meta);
115
+ },
116
+ });
108
117
  /**
109
118
  * Pre-configured loggers for different components
110
119
  */
@@ -120,17 +129,40 @@ export const toolLogger = createLogger('Tool');
120
129
  export const setupLogger = createLogger('Setup');
121
130
  /**
122
131
  * Log HTTP request details
132
+ *
133
+ * @param method - HTTP method being sent.
134
+ * @param url - Request URL.
135
+ * @param params - Optional query parameters.
136
+ * @param body - Optional request body.
137
+ * @returns Nothing; writes to configured logger transports.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * logRequest('GET', url, params);
142
+ * ```
123
143
  */
124
- export function logRequest(method, url, params, body) {
144
+ export const logRequest = (method, url, params, body) => {
125
145
  apiLogger.http(`Request: ${method.toUpperCase()} ${url}`, {
126
146
  params: params && Object.keys(params).length > 0 ? params : undefined,
127
147
  body: body ? truncateData(body) : undefined,
128
148
  });
129
- }
149
+ };
130
150
  /**
131
151
  * Log HTTP response details
152
+ *
153
+ * @param status - HTTP response status code.
154
+ * @param statusText - HTTP response status text.
155
+ * @param data - Optional response payload.
156
+ * @param rateLimitRemaining - Optional remaining request quota.
157
+ * @param rateLimitTotal - Optional total request quota.
158
+ * @returns Nothing; writes to configured logger transports.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * logResponse(200, 'OK', data);
163
+ * ```
132
164
  */
133
- export function logResponse(status, statusText, data, rateLimitRemaining, rateLimitTotal) {
165
+ export const logResponse = (status, statusText, data, rateLimitRemaining, rateLimitTotal) => {
134
166
  const meta = {};
135
167
  if (rateLimitRemaining && rateLimitTotal) {
136
168
  meta.rateLimit = `${rateLimitRemaining}/${rateLimitTotal}`;
@@ -139,42 +171,81 @@ export function logResponse(status, statusText, data, rateLimitRemaining, rateLi
139
171
  meta.data = truncateData(data);
140
172
  }
141
173
  apiLogger.http(`Response: ${status} ${statusText}`, meta);
142
- }
174
+ };
143
175
  /**
144
176
  * Log HTTP error response
177
+ *
178
+ * @param status - HTTP status code, when a response exists.
179
+ * @param statusText - HTTP status text, when a response exists.
180
+ * @param url - Request URL that failed.
181
+ * @param errorData - Optional parsed error payload.
182
+ * @returns Nothing; writes to configured logger transports.
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * logErrorResponse(error.status, error.statusText, url, error.data);
187
+ * ```
145
188
  */
146
- export function logErrorResponse(status, statusText, url, errorData) {
147
- apiLogger.error(`Error Response: ${status || 'N/A'} ${statusText || 'No response'}`, {
189
+ export const logErrorResponse = (status, statusText, url, errorData) => {
190
+ const statusLabel = status === undefined ? 'No status' : String(status);
191
+ const statusTextLabel = statusText ?? 'No response';
192
+ apiLogger.error(`Error Response: ${statusLabel} ${statusTextLabel}`, {
148
193
  url,
149
194
  error: errorData ? truncateData(errorData) : undefined,
150
195
  });
151
- }
196
+ };
152
197
  /**
153
198
  * Truncate large data objects for logging
154
199
  */
155
- function truncateData(data, maxLength = 1000) {
200
+ const truncateData = (data, maxLength = 1000) => {
156
201
  const str = JSON.stringify(data);
157
202
  if (str.length <= maxLength) {
158
203
  return data;
159
204
  }
160
205
  return `${str.substring(0, maxLength)}... [truncated]`;
161
- }
206
+ };
162
207
  /**
163
208
  * Get log file paths for user reference
209
+ *
210
+ * @returns Paths to the log directory and known log files.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * const paths = getLogPaths();
215
+ * ```
164
216
  */
165
- export function getLogPaths() {
166
- return {
167
- logDir: LOG_DIR,
168
- errorLog: join(LOG_DIR, 'error.log'),
169
- combinedLog: join(LOG_DIR, 'combined.log'),
170
- debugLog: join(LOG_DIR, 'debug.log'),
171
- };
172
- }
217
+ export const getLogPaths = () => ({
218
+ logDir: LOG_DIR,
219
+ errorLog: join(LOG_DIR, 'error.log'),
220
+ combinedLog: join(LOG_DIR, 'combined.log'),
221
+ debugLog: join(LOG_DIR, 'debug.log'),
222
+ });
223
+ /**
224
+ * Reports whether file logging is currently enabled.
225
+ *
226
+ * @returns True when Winston file transports are enabled.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * if (isFileLoggingEnabled()) {
231
+ * serverLogger.info('File logging enabled');
232
+ * }
233
+ * ```
234
+ */
235
+ export const isFileLoggingEnabled = () => fileLoggingEnabled;
173
236
  /**
174
237
  * Enable or disable file logging at runtime
238
+ *
239
+ * @param enabled - Whether file logging should be enabled.
240
+ * @returns Nothing; mutates Winston transports when enabling.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * setFileLogging(true);
245
+ * ```
175
246
  */
176
- export function setFileLogging(enabled) {
177
- if (enabled && !ENABLE_FILE_LOGGING) {
247
+ export const setFileLogging = (enabled) => {
248
+ if (enabled && !fileLoggingEnabled) {
178
249
  logger.add(new winston.transports.File({
179
250
  filename: join(LOG_DIR, 'error.log'),
180
251
  level: 'error',
@@ -190,17 +261,22 @@ export function setFileLogging(enabled) {
190
261
  maxFiles: 5,
191
262
  tailable: true,
192
263
  }));
264
+ fileLoggingEnabled = true;
193
265
  logger.info('File logging enabled');
194
266
  }
195
- }
267
+ };
196
268
  /**
197
269
  * Set log level at runtime
270
+ *
271
+ * @param level - Winston log level name.
272
+ * @returns Nothing; mutates the shared Winston logger level.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * setLogLevel('debug');
277
+ * ```
198
278
  */
199
- export function setLogLevel(level) {
279
+ export const setLogLevel = (level) => {
200
280
  logger.level = level;
201
281
  logger.info(`Log level set to: ${level}`);
202
- }
203
- /**
204
- * Default Winston logger instance.
205
- */
206
- export default logger;
282
+ };
@@ -4,18 +4,25 @@
4
4
  import chalk from 'chalk';
5
5
  import { createServer } from 'http';
6
6
  import { getOAuthAuthorizationUrl } from '../config/environment.js';
7
+ import { writeCliLine } from './cliOutput.js';
7
8
  /**
8
9
  * Generate terminal hyperlink (if supported)
9
10
  */
10
- function hyperlink(text, url) {
11
- return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
12
- }
11
+ const hyperlink = (text, url) => `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
13
12
  /**
14
- * Generate eBay OAuth authorization URL
13
+ * Generates an eBay OAuth authorization URL.
14
+ *
15
+ * @param clientId eBay application client ID.
16
+ * @param redirectUri Redirect URI or RuName configured for the application.
17
+ * @param environment eBay environment that should receive the authorization request.
18
+ * @param scopes OAuth scopes to request; defaults come from the environment helper.
19
+ * @returns Fully qualified eBay OAuth authorization URL.
20
+ * @example
21
+ * ```ts
22
+ * const url = generateAuthUrl(clientId, redirectUri, 'sandbox');
23
+ * ```
15
24
  */
16
- export function generateAuthUrl(clientId, redirectUri, environment, scopes) {
17
- return getOAuthAuthorizationUrl(clientId, redirectUri, environment, scopes);
18
- }
25
+ export const generateAuthUrl = (clientId, redirectUri, environment, scopes) => getOAuthAuthorizationUrl(clientId, redirectUri, environment, scopes);
19
26
  /**
20
27
  * Escape a string for safe interpolation into HTML text/attribute context.
21
28
  *
@@ -25,14 +32,12 @@ export function generateAuthUrl(clientId, redirectUri, environment, scopes) {
25
32
  * HTML-significant characters neutralizes the markup while keeping the message
26
33
  * legible.
27
34
  */
28
- function escapeHtml(value) {
29
- return value
30
- .replace(/&/g, '&amp;')
31
- .replace(/</g, '&lt;')
32
- .replace(/>/g, '&gt;')
33
- .replace(/"/g, '&quot;')
34
- .replace(/'/g, '&#39;');
35
- }
35
+ const escapeHtml = (value) => value
36
+ .replace(/&/g, '&amp;')
37
+ .replace(/</g, '&lt;')
38
+ .replace(/>/g, '&gt;')
39
+ .replace(/"/g, '&quot;')
40
+ .replace(/'/g, '&#39;');
36
41
  /**
37
42
  * Render the small HTML page the user lands on after eBay redirects back.
38
43
  *
@@ -41,7 +46,7 @@ function escapeHtml(value) {
41
46
  * potentially attacker-controlled (eBay's `error_description`), so it is
42
47
  * HTML-escaped before interpolation.
43
48
  */
44
- function renderCallbackPage(success, detail = '') {
49
+ const renderCallbackPage = (success, detail = '') => {
45
50
  const gradient = success
46
51
  ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
47
52
  : 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
@@ -78,7 +83,7 @@ function renderCallbackPage(success, detail = '') {
78
83
  ${autoClose}
79
84
  </body>
80
85
  </html>`;
81
- }
86
+ };
82
87
  /**
83
88
  * Start a loopback HTTP server that captures the eBay OAuth redirect.
84
89
  *
@@ -94,8 +99,12 @@ function renderCallbackPage(success, detail = '') {
94
99
  * @param timeoutMs How long to wait for the redirect before settling with `timeout`.
95
100
  * @param options Path and CSRF-`state` tuning — see {@link CallbackServerOptions}.
96
101
  * @returns The live server plus a promise resolving to the callback result.
102
+ * @example
103
+ * ```ts
104
+ * const { server, codePromise } = await startCallbackServer(3000);
105
+ * ```
97
106
  */
98
- export async function startCallbackServer(port = 3000, timeoutMs = 300000, options = {}) {
107
+ export const startCallbackServer = async (port = 3000, timeoutMs = 300_000, options = {}) => {
99
108
  const callbackPath = options.path ?? '/oauth/callback';
100
109
  const { expectedState } = options;
101
110
  return await new Promise((resolve, reject) => {
@@ -160,76 +169,99 @@ export async function startCallbackServer(port = 3000, timeoutMs = 300000, optio
160
169
  resolve({ server, codePromise });
161
170
  });
162
171
  });
163
- }
172
+ };
164
173
  /**
165
- * Interactive OAuth flow with local callback server
174
+ * Runs an interactive OAuth flow with a local callback server.
175
+ *
176
+ * @param config eBay application configuration used to build the authorization URL.
177
+ * @param scopes OAuth scopes to request for the user token.
178
+ * @returns The authorization code when the browser callback succeeds, otherwise `null`.
179
+ * @example
180
+ * ```ts
181
+ * const code = await interactiveOAuthFlow(config);
182
+ * ```
166
183
  */
167
- export async function interactiveOAuthFlow(config, scopes) {
168
- console.log(chalk.bold.cyan('\n🔐 Interactive OAuth Flow\n'));
184
+ export const interactiveOAuthFlow = async (config, scopes) => {
185
+ writeCliLine(chalk.bold.cyan('\n🔐 Interactive OAuth Flow\n'));
169
186
  // Check if redirect URI is localhost
170
187
  const redirectUri = config.redirectUri || 'http://localhost:3000/oauth/callback';
171
188
  const isLocalhost = redirectUri.includes('localhost') || redirectUri.includes('127.0.0.1');
172
189
  if (!isLocalhost) {
173
- console.log(chalk.yellow('⚠️ Your redirect URI is not localhost. Interactive flow requires localhost callback.\n'));
190
+ writeCliLine(chalk.yellow('⚠️ Your redirect URI is not localhost. Interactive flow requires localhost callback.\n'));
174
191
  return null;
175
192
  }
176
193
  // Extract port from redirect URI
177
194
  const portMatch = /:(\d+)/.exec(redirectUri);
178
- const port = portMatch ? parseInt(portMatch[1], 10) : 3000;
195
+ const port = portMatch ? Number.parseInt(portMatch[1], 10) : 3000;
179
196
  // Start callback server
180
- console.log(chalk.cyan('Starting local OAuth callback server...\n'));
197
+ writeCliLine(chalk.cyan('Starting local OAuth callback server...\n'));
181
198
  const { server, codePromise } = await startCallbackServer(port);
182
199
  // Generate auth URL
183
200
  const authUrl = generateAuthUrl(config.clientId, redirectUri, config.environment, scopes);
184
- console.log(chalk.bold.white('📋 Step 1: Authorize the Application\n'));
185
- console.log(chalk.gray('Open this URL in your browser:\n'));
186
- console.log(chalk.blue.underline(hyperlink(authUrl.substring(0, 20) + '...', authUrl)));
187
- console.log('');
188
- console.log(chalk.gray('Waiting for authorization...'));
189
- console.log(chalk.gray('(This window will update automatically after you authorize)\n'));
201
+ writeCliLine(chalk.bold.white('📋 Step 1: Authorize the Application\n'));
202
+ writeCliLine(chalk.gray('Open this URL in your browser:\n'));
203
+ writeCliLine(chalk.blue.underline(hyperlink(authUrl.substring(0, 20) + '...', authUrl)));
204
+ writeCliLine('');
205
+ writeCliLine(chalk.gray('Waiting for authorization...'));
206
+ writeCliLine(chalk.gray('(This window will update automatically after you authorize)\n'));
190
207
  // Wait for callback
191
208
  const result = await codePromise;
192
209
  // Close server
193
210
  server.close();
194
211
  if (result.error) {
195
- console.log(chalk.red(`\n✗ Authorization failed: ${result.errorDescription || result.error}\n`));
212
+ writeCliLine(chalk.red(`\n✗ Authorization failed: ${result.errorDescription || result.error}\n`));
196
213
  return null;
197
214
  }
198
215
  if (result.code) {
199
- console.log(chalk.green('\n✓ Authorization successful!\n'));
216
+ writeCliLine(chalk.green('\n✓ Authorization successful!\n'));
200
217
  return result.code;
201
218
  }
202
- console.log(chalk.yellow('\n⚠️ No authorization code received.\n'));
219
+ writeCliLine(chalk.yellow('\n⚠️ No authorization code received.\n'));
203
220
  return null;
204
- }
221
+ };
205
222
  /**
206
- * Display manual OAuth instructions
223
+ * Displays manual OAuth instructions in the setup CLI.
224
+ *
225
+ * @param clientId eBay application client ID.
226
+ * @param redirectUri Redirect URI or RuName configured for the application.
227
+ * @param environment eBay environment for the authorization URL.
228
+ * @param scopes OAuth scopes to include in the manual authorization URL.
229
+ * @returns Nothing; instructions are written to stdout.
230
+ * @example
231
+ * ```ts
232
+ * displayManualOAuthInstructions(clientId, redirectUri, 'sandbox');
233
+ * ```
207
234
  */
208
- export function displayManualOAuthInstructions(clientId, redirectUri, environment, scopes) {
235
+ export const displayManualOAuthInstructions = (clientId, redirectUri, environment, scopes) => {
209
236
  const authUrl = generateAuthUrl(clientId, redirectUri, environment, scopes);
210
- console.log(chalk.bold.cyan('\n📖 Manual OAuth Token Acquisition Guide\n'));
211
- console.log(chalk.white('Step 1: Generate Authorization URL\n'));
212
- console.log(chalk.gray('Copy this URL and open it in your browser:\n'));
213
- console.log(chalk.blue.underline(authUrl));
214
- console.log('');
215
- console.log(chalk.white('\nStep 2: Authorize the Application\n'));
216
- console.log(chalk.gray(' • Log in to your eBay account'));
217
- console.log(chalk.gray(' • Review the permissions requested'));
218
- console.log(chalk.gray(' • Click "Agree" to authorize\n'));
219
- console.log(chalk.white('Step 3: Get the Authorization Code\n'));
220
- console.log(chalk.gray(' • After authorization, you will be redirected to your redirect URI'));
221
- console.log(chalk.gray(' • The URL will contain a "code" parameter'));
222
- console.log(chalk.gray(' • Example: https://your-redirect-uri?code=v^1.1#i^1...\n'));
223
- console.log(chalk.white('Step 4: Exchange Code for Tokens\n'));
224
- console.log(chalk.gray(' • Use the code to get your refresh token'));
225
- console.log(chalk.gray(' • This can be done through the MCP tool: ebay_exchange_auth_code'));
226
- console.log(chalk.gray(' • Or paste the code in the setup wizard when prompted\n'));
227
- }
237
+ writeCliLine(chalk.bold.cyan('\n📖 Manual OAuth Token Acquisition Guide\n'));
238
+ writeCliLine(chalk.white('Step 1: Generate Authorization URL\n'));
239
+ writeCliLine(chalk.gray('Copy this URL and open it in your browser:\n'));
240
+ writeCliLine(chalk.blue.underline(authUrl));
241
+ writeCliLine('');
242
+ writeCliLine(chalk.white('\nStep 2: Authorize the Application\n'));
243
+ writeCliLine(chalk.gray(' • Log in to your eBay account'));
244
+ writeCliLine(chalk.gray(' • Review the permissions requested'));
245
+ writeCliLine(chalk.gray(' • Click "Agree" to authorize\n'));
246
+ writeCliLine(chalk.white('Step 3: Get the Authorization Code\n'));
247
+ writeCliLine(chalk.gray(' • After authorization, you will be redirected to your redirect URI'));
248
+ writeCliLine(chalk.gray(' • The URL will contain a "code" parameter'));
249
+ writeCliLine(chalk.gray(' • Example: https://your-redirect-uri?code=v^1.1#i^1...\n'));
250
+ writeCliLine(chalk.white('Step 4: Exchange Code for Tokens\n'));
251
+ writeCliLine(chalk.gray(' • Use the code to get your refresh token'));
252
+ writeCliLine(chalk.gray(' • This can be done through the MCP tool: ebay_exchange_auth_code'));
253
+ writeCliLine(chalk.gray(' • Or paste the code in the setup wizard when prompted\n'));
254
+ };
228
255
  /**
229
- * Get help text for RuName (Redirect URI)
256
+ * Gets help text for RuName (Redirect URI) setup.
257
+ *
258
+ * @returns A formatted help block explaining eBay RuName creation.
259
+ * @example
260
+ * ```ts
261
+ * writeCliLine(getRuNameHelp());
262
+ * ```
230
263
  */
231
- export function getRuNameHelp() {
232
- return `
264
+ export const getRuNameHelp = () => `
233
265
  ${chalk.bold.cyan('What is a RuName (Redirect URI)?')}
234
266
 
235
267
  A RuName (Redirect URL name) is a unique identifier for your OAuth redirect URI.
@@ -260,37 +292,42 @@ ${chalk.bold.white('Common RuName formats:')}
260
292
  ${chalk.yellow('Note:')} The RuName is NOT the same as the redirect URI itself.
261
293
  It's a reference name that eBay associates with your redirect URI.
262
294
  `;
263
- }
264
295
  /**
265
- * Display first-time developer guide
296
+ * Displays the first-time eBay developer guide in the setup CLI.
297
+ *
298
+ * @returns Nothing; the guide is written to stdout.
299
+ * @example
300
+ * ```ts
301
+ * displayFirstTimeDeveloperGuide();
302
+ * ```
266
303
  */
267
- export function displayFirstTimeDeveloperGuide() {
268
- console.log(chalk.bold.cyan('\n🆕 First-Time eBay Developer Guide\n'));
269
- console.log(chalk.white("Welcome! Here's how to get started:\n"));
270
- console.log(chalk.bold.yellow('Step 1: Create eBay Developer Account\n'));
271
- console.log(chalk.gray(' 1. Visit: ') + chalk.blue.underline('https://developer.ebay.com/'));
272
- console.log(chalk.gray(' 2. Click "Register" or "Join"'));
273
- console.log(chalk.gray(' 3. Complete the registration form'));
274
- console.log(chalk.gray(' 4. Verify your email address\n'));
275
- console.log(chalk.bold.yellow('Step 2: Create an Application\n'));
276
- console.log(chalk.gray(' 1. Go to: ') + chalk.blue.underline('https://developer.ebay.com/my/keys'));
277
- console.log(chalk.gray(' 2. Click "Create Application"'));
278
- console.log(chalk.gray(' 3. Fill in application details (name, description)'));
279
- console.log(chalk.gray(' 4. Choose Sandbox environment to start\n'));
280
- console.log(chalk.bold.yellow('Step 3: Get Your Credentials\n'));
281
- console.log(chalk.gray(' After creating the app, you will see:'));
282
- console.log(chalk.gray(' • App ID (Client ID) - Copy this'));
283
- console.log(chalk.gray(' • Cert ID (Client Secret) - Copy this'));
284
- console.log(chalk.gray(' • These are needed for the setup wizard\n'));
285
- console.log(chalk.bold.yellow('Step 4: Create RuName (Redirect URI)\n'));
286
- console.log(chalk.gray(' 1. In your application settings'));
287
- console.log(chalk.gray(' 2. Navigate to "User Tokens" section'));
288
- console.log(chalk.gray(' 3. Click "Add RuName"'));
289
- console.log(chalk.gray(' 4. Enter: http://localhost:3000/oauth/callback'));
290
- console.log(chalk.gray(' 5. Save and copy the generated RuName\n'));
291
- console.log(chalk.bold.yellow('Step 5: Get User Token\n'));
292
- console.log(chalk.gray(' Option A: Use this setup wizard (recommended)'));
293
- console.log(chalk.gray(' Option B: Manual OAuth flow through eBay Developer Portal\n'));
294
- console.log(chalk.green.bold("✅ Once you have these, you're ready to continue!\n"));
295
- console.log(chalk.gray('Press Enter to continue when you have your credentials ready...'));
296
- }
304
+ export const displayFirstTimeDeveloperGuide = () => {
305
+ writeCliLine(chalk.bold.cyan('\n🆕 First-Time eBay Developer Guide\n'));
306
+ writeCliLine(chalk.white("Welcome! Here's how to get started:\n"));
307
+ writeCliLine(chalk.bold.yellow('Step 1: Create eBay Developer Account\n'));
308
+ writeCliLine(chalk.gray(' 1. Visit: ') + chalk.blue.underline('https://developer.ebay.com/'));
309
+ writeCliLine(chalk.gray(' 2. Click "Register" or "Join"'));
310
+ writeCliLine(chalk.gray(' 3. Complete the registration form'));
311
+ writeCliLine(chalk.gray(' 4. Verify your email address\n'));
312
+ writeCliLine(chalk.bold.yellow('Step 2: Create an Application\n'));
313
+ writeCliLine(chalk.gray(' 1. Go to: ') + chalk.blue.underline('https://developer.ebay.com/my/keys'));
314
+ writeCliLine(chalk.gray(' 2. Click "Create Application"'));
315
+ writeCliLine(chalk.gray(' 3. Fill in application details (name, description)'));
316
+ writeCliLine(chalk.gray(' 4. Choose Sandbox environment to start\n'));
317
+ writeCliLine(chalk.bold.yellow('Step 3: Get Your Credentials\n'));
318
+ writeCliLine(chalk.gray(' After creating the app, you will see:'));
319
+ writeCliLine(chalk.gray(' • App ID (Client ID) - Copy this'));
320
+ writeCliLine(chalk.gray(' • Cert ID (Client Secret) - Copy this'));
321
+ writeCliLine(chalk.gray(' • These are needed for the setup wizard\n'));
322
+ writeCliLine(chalk.bold.yellow('Step 4: Create RuName (Redirect URI)\n'));
323
+ writeCliLine(chalk.gray(' 1. In your application settings'));
324
+ writeCliLine(chalk.gray(' 2. Navigate to "User Tokens" section'));
325
+ writeCliLine(chalk.gray(' 3. Click "Add RuName"'));
326
+ writeCliLine(chalk.gray(' 4. Enter: http://localhost:3000/oauth/callback'));
327
+ writeCliLine(chalk.gray(' 5. Save and copy the generated RuName\n'));
328
+ writeCliLine(chalk.bold.yellow('Step 5: Get User Token\n'));
329
+ writeCliLine(chalk.gray(' Option A: Use this setup wizard (recommended)'));
330
+ writeCliLine(chalk.gray(' Option B: Manual OAuth flow through eBay Developer Portal\n'));
331
+ writeCliLine(chalk.green.bold("✅ Once you have these, you're ready to continue!\n"));
332
+ writeCliLine(chalk.gray('Press Enter to continue when you have your credentials ready...'));
333
+ };
@@ -1,51 +1,44 @@
1
- import { z } from 'zod';
1
+ import { z } from '../../utils/effectSchema.js';
2
2
  /**
3
- * Zod schemas for Compliance API input validation
3
+ * Effect-backed schemas for Compliance API input validation
4
4
  * Based on: src/api/other/compliance.ts
5
5
  * OpenAPI spec: docs/sell-apps/other-apis/sell_compliance_v1_oas3.json
6
6
  * Types from: src/types/sell_compliance_v1_oas3.ts
7
7
  */
8
- // Reusable schema for compliance_type parameter
8
+ // Reusable schema for complianceType parameter
9
9
  const complianceTypeSchema = z
10
10
  .string({
11
- invalid_type_error: 'compliance_type must be a string',
11
+ invalid_type_error: 'complianceType must be a string',
12
12
  description: 'Compliance type(s) to filter violations (e.g., ASPECTS_ADOPTION, HTTPS, OUTSIDE_EBAY_BUYING_AND_SELLING, RETURNS_POLICY)',
13
13
  })
14
14
  .optional();
15
- // Reusable schema for offset parameter (string in API)
15
+ // Reusable schema for offset parameter
16
16
  const offsetSchema = z
17
- .string({
18
- invalid_type_error: 'offset must be a string',
17
+ .number({
18
+ invalid_type_error: 'offset must be a number',
19
19
  description: 'Number of items to skip (zero-based index)',
20
20
  })
21
+ .int()
22
+ .nonnegative()
21
23
  .optional();
22
- // Reusable schema for limit parameter (string in API)
24
+ // Reusable schema for limit parameter
23
25
  const limitSchema = z
24
- .string({
25
- invalid_type_error: 'limit must be a string',
26
+ .number({
27
+ invalid_type_error: 'limit must be a number',
26
28
  description: 'Maximum number of items to return (default: 100, max: 200)',
27
29
  })
30
+ .int()
31
+ .positive()
28
32
  .optional();
29
33
  /**
30
34
  * Schema for getListingViolations method
31
35
  * Endpoint: GET /listing_violation
32
- * Query: GetListingViolationsParams - compliance_type (required), offset, limit, listing_id, filter
36
+ * Query: complianceType, offset, limit, filter
33
37
  */
34
38
  export const getListingViolationsSchema = z.object({
35
- compliance_type: z.string({
36
- message: 'Compliance type is required',
37
- required_error: 'compliance_type is required',
38
- invalid_type_error: 'compliance_type must be a string',
39
- description: 'The compliance type of violations to retrieve (only one type allowed per call)',
40
- }),
39
+ complianceType: complianceTypeSchema,
41
40
  offset: offsetSchema,
42
41
  limit: limitSchema,
43
- listing_id: z
44
- .string({
45
- invalid_type_error: 'listing_id must be a string',
46
- description: 'Filter by specific eBay listing ID (not yet supported)',
47
- })
48
- .optional(),
49
42
  filter: z
50
43
  .string({
51
44
  invalid_type_error: 'filter must be a string',
@@ -56,21 +49,8 @@ export const getListingViolationsSchema = z.object({
56
49
  /**
57
50
  * Schema for getListingViolationsSummary method
58
51
  * Endpoint: GET /listing_violation_summary
59
- * Query: GetListingViolationsSummaryParams - compliance_type
52
+ * Query: complianceType
60
53
  */
61
54
  export const getListingViolationsSummarySchema = z.object({
62
- compliance_type: complianceTypeSchema,
63
- });
64
- /**
65
- * Schema for suppressViolation method
66
- * Endpoint: POST /suppress_violation
67
- * Body: listing_violation_id (required)
68
- */
69
- export const suppressViolationSchema = z.object({
70
- listing_violation_id: z.string({
71
- message: 'Listing violation ID is required',
72
- required_error: 'listing_violation_id is required',
73
- invalid_type_error: 'listing_violation_id must be a string',
74
- description: 'The unique identifier of the listing violation to suppress',
75
- }),
55
+ complianceType: complianceTypeSchema,
76
56
  });