ebay-mcp 1.18.0 → 1.18.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.
Files changed (161) hide show
  1. package/README.md +24 -35
  2. package/build/api/account-management/accountV2.js +22 -22
  3. package/build/api/account-management/finances.js +10 -10
  4. package/build/api/analytics-and-report/analytics.js +1 -1
  5. package/build/api/{client.js → client/ebayApiClient.js} +37 -41
  6. package/build/api/{clientTrading.js → client/tradingApiClient.js} +22 -22
  7. package/build/api/communication/feedback.js +21 -21
  8. package/build/api/communication/message.js +23 -23
  9. package/build/api/communication/negotiation.js +7 -7
  10. package/build/api/communication/notification.js +52 -52
  11. package/build/api/developer/developer.js +3 -3
  12. package/build/api/{index.js → ebaySellerApi.js} +4 -4
  13. package/build/api/listing-management/feedReportTasks.js +6 -6
  14. package/build/api/listing-management/feedRequest.js +6 -6
  15. package/build/api/listing-management/feedSchedules.js +4 -4
  16. package/build/api/listing-management/feedTasks.js +2 -2
  17. package/build/api/listing-management/items.js +22 -22
  18. package/build/api/listing-management/locations.js +4 -4
  19. package/build/api/listing-management/media.js +14 -14
  20. package/build/api/listing-management/mediaAttach.js +14 -14
  21. package/build/api/listing-management/mediaResources.js +15 -13
  22. package/build/api/listing-management/offerFormat.js +1 -1
  23. package/build/api/listing-management/offers.js +23 -23
  24. package/build/api/listing-management/stores.js +9 -9
  25. package/build/api/listing-metadata/metadata.js +10 -10
  26. package/build/api/marketing-and-promotions/ads.js +34 -34
  27. package/build/api/marketing-and-promotions/campaigns.js +19 -19
  28. package/build/api/marketing-and-promotions/promotions.js +22 -22
  29. package/build/api/marketing-and-promotions/reports.js +7 -7
  30. package/build/api/order-management/dispute.js +11 -9
  31. package/build/api/order-management/fulfillment.js +4 -4
  32. package/build/api/order-management/logistics.js +7 -7
  33. package/build/api/other/browseMappers.js +15 -13
  34. package/build/api/other/browseValidators.js +4 -4
  35. package/build/api/other/edelivery.js +19 -19
  36. package/build/api/other/finding.js +13 -13
  37. package/build/api/other/translation.js +2 -2
  38. package/build/api/other/vero.js +2 -2
  39. package/build/api/shared/download.js +7 -7
  40. package/build/api/shared/request.js +28 -19
  41. package/build/api/trading/listingFormat.js +37 -30
  42. package/build/api/trading/trading.js +33 -33
  43. package/build/auth/credentialSession.js +5 -5
  44. package/build/auth/oauth.js +3 -3
  45. package/build/auth/oauthHelper.js +24 -24
  46. package/build/auth/oauthMetadata.js +4 -4
  47. package/build/auth/oauthMiddleware.js +16 -16
  48. package/build/auth/scopeUtils.js +2 -2
  49. package/build/auth/tokenVerifier.js +23 -23
  50. package/build/config/environment.js +4 -45
  51. package/build/config/oauthScopes.js +132 -0
  52. package/build/index.js +5 -5
  53. package/build/mcp/httpTransport.js +32 -20
  54. package/build/mcp/runtime.js +12 -12
  55. package/build/mcp/toolGating.js +6 -12
  56. package/build/mcp/uiBridge.js +3 -3
  57. package/build/schemas/account-management/account.js +2 -52
  58. package/build/schemas/account-management/accountV2.js +2 -2
  59. package/build/schemas/account-management/finances.js +1 -1
  60. package/build/schemas/analytics/analytics.js +2 -48
  61. package/build/schemas/communication/messages.js +2 -46
  62. package/build/schemas/developer/developer.js +1 -27
  63. package/build/schemas/fulfillment/logistics.js +1 -1
  64. package/build/schemas/fulfillment/orders.js +2 -47
  65. package/build/schemas/inventory-management/feed.js +1 -1
  66. package/build/schemas/inventory-management/inventory.js +2 -50
  67. package/build/schemas/inventory-management/media.js +2 -2
  68. package/build/schemas/inventory-management/mediaDocuments.js +1 -1
  69. package/build/schemas/inventory-management/stores.js +1 -1
  70. package/build/schemas/marketing/ads.js +2 -2
  71. package/build/schemas/marketing/campaigns.js +2 -2
  72. package/build/schemas/marketing/common.js +2 -2
  73. package/build/schemas/marketing/marketing.js +0 -1
  74. package/build/schemas/marketing/promotions.js +2 -2
  75. package/build/schemas/marketing/reports.js +2 -2
  76. package/build/schemas/metadata/metadata.js +2 -90
  77. package/build/schemas/metadata/shipping.js +1 -1
  78. package/build/schemas/other/browse.js +2 -2
  79. package/build/schemas/other/finding.js +2 -2
  80. package/build/schemas/other/otherApis.js +2 -114
  81. package/build/schemas/taxonomy/categoryTree.js +1 -1
  82. package/build/schemas/taxonomy/charity.js +1 -1
  83. package/build/schemas/taxonomy/taxonomy.js +2 -69
  84. package/build/scripts/autoSetup.js +3 -3
  85. package/build/scripts/{buildUi.js → buildMcpApps.js} +13 -13
  86. package/build/scripts/devSync.js +19 -68
  87. package/build/scripts/diagnostics.js +15 -16
  88. package/build/scripts/interactiveSetup.js +25 -25
  89. package/build/scripts/scopeHelper.js +2 -2
  90. package/build/scripts/securityChecker.js +20 -20
  91. package/build/scripts/setup.js +76 -76
  92. package/build/scripts/setupShared.js +5 -30
  93. package/build/scripts/setupValidator.js +9 -9
  94. package/build/scripts/setupWizard.js +23 -23
  95. package/build/scripts/skills.js +14 -17
  96. package/build/scripts/specRefs.js +1 -1
  97. package/build/scripts/specSources.js +144 -0
  98. package/build/serverHttp.js +4 -7
  99. package/build/skills/content.js +13 -13
  100. package/build/skills/install.js +9 -9
  101. package/build/skills/render.js +2 -2
  102. package/build/tools/categories/communication.js +1 -1
  103. package/build/tools/categories/connector.js +19 -16
  104. package/build/tools/categories/fulfillment.js +4 -4
  105. package/build/tools/categories/inventory.js +1 -1
  106. package/build/tools/categories/metadata.js +1 -1
  107. package/build/tools/categories/taxonomy.js +1 -1
  108. package/build/tools/categories/tokenManagement.js +8 -8
  109. package/build/tools/contracts.js +2 -2
  110. package/build/tools/defineTool.js +27 -12
  111. package/build/tools/registry.js +2 -2
  112. package/build/tools/schemas.js +2 -2
  113. package/build/tools/ui/mapHelpers.js +12 -12
  114. package/build/tools/ui/maps.js +101 -97
  115. package/build/tools/ui/viewModels.js +2 -2
  116. package/build/utils/apiStatusFeed.js +12 -12
  117. package/build/utils/communication/feedback.js +3 -3
  118. package/build/utils/communication/message.js +3 -3
  119. package/build/utils/communication/negotiation.js +3 -3
  120. package/build/utils/communication/notification.js +3 -3
  121. package/build/utils/entryModule.js +33 -0
  122. package/build/utils/envFile.js +47 -0
  123. package/build/utils/envParser.js +5 -5
  124. package/build/utils/errors.js +4 -4
  125. package/build/utils/http.js +38 -38
  126. package/build/utils/localFeedFiles.js +3 -3
  127. package/build/utils/logRedaction.js +21 -0
  128. package/build/utils/logger.js +49 -35
  129. package/build/utils/other/compliance.js +3 -3
  130. package/build/utils/other/identity.js +3 -3
  131. package/build/utils/other/translation.js +3 -3
  132. package/build/utils/other/vero.js +3 -3
  133. package/build/utils/schemaHelpers.js +2 -2
  134. package/build/utils/tokenUtils.js +2 -2
  135. package/build/utils/trading/trading.js +1 -1
  136. package/build/utils/version.js +1 -1
  137. package/build/utils/zodIssues.js +14 -0
  138. package/package.json +8 -11
  139. package/README.de.md +0 -426
  140. package/README.es.md +0 -426
  141. package/README.fr.md +0 -426
  142. package/README.ja.md +0 -426
  143. package/README.ko.md +0 -426
  144. package/README.pt-BR.md +0 -426
  145. package/README.ru.md +0 -426
  146. package/README.zh-CN.md +0 -426
  147. package/build/schemas/index.js +0 -76
  148. package/build/schemas/marketing/jsonSchemas.js +0 -118
  149. package/build/scripts/downloadSpecs.js +0 -117
  150. package/build/scripts/updateApiStatusDoc.js +0 -71
  151. package/build/utils/effectSchema.js +0 -203
  152. package/build/utils/effectSchemaAdapter.js +0 -121
  153. package/build/utils/effectSchemaChainMethods.js +0 -146
  154. package/build/utils/effectSchemaRefinements.js +0 -104
  155. package/build/utils/effectSchemaStringMethods.js +0 -63
  156. package/build/utils/effectSchemaTypes.js +0 -47
  157. package/docs/auth/production_scopes.json +0 -111
  158. package/docs/auth/sandbox_scopes.json +0 -142
  159. /package/build/api/{clientRequestError.js → client/ebayClientRequestError.js} +0 -0
  160. /package/build/api/{rateLimitTracker.js → client/rateLimitTracker.js} +0 -0
  161. /package/build/api/{clientTradingError.js → client/tradingApiFailure.js} +0 -0
@@ -43,7 +43,7 @@ export class DownloadTooLargeError extends Data.TaggedError('DownloadTooLargeErr
43
43
  }
44
44
  }
45
45
  /** Size of a download: what was read, or the larger declared length when reading stopped early. */
46
- const downloadSize = (response) => Math.max(response.data.length, Number(response.headers['content-length']) || 0);
46
+ const downloadSize = (fileResponse) => Math.max(fileResponse.data.length, Number(fileResponse.headers['content-length']) || 0);
47
47
  /**
48
48
  * Downloads a binary eBay resource as an Effect, keeping its content type and file name.
49
49
  * Reading stops as soon as the file is known to exceed {@link MAX_INLINE_DOWNLOAD_BYTES}.
@@ -68,12 +68,12 @@ export const requestDownloadEffect = (client, path, config = {}) => Effect.tryPr
68
68
  maxBytes: MAX_INLINE_DOWNLOAD_BYTES,
69
69
  }),
70
70
  catch: (cause) => new EbayApiError({ method: 'GET', path, cause }), // allow-duplicate
71
- }).pipe(Effect.filterOrFail((response) => downloadSize(response) <= MAX_INLINE_DOWNLOAD_BYTES, (response) => new DownloadTooLargeError({
71
+ }).pipe(Effect.filterOrFail((fileResponse) => downloadSize(fileResponse) <= MAX_INLINE_DOWNLOAD_BYTES, (fileResponse) => new DownloadTooLargeError({
72
72
  path,
73
- bytes: downloadSize(response),
73
+ bytes: downloadSize(fileResponse),
74
74
  limit: MAX_INLINE_DOWNLOAD_BYTES,
75
- })), Effect.map((response) => ({
76
- bytes: response.data,
77
- contentType: response.headers['content-type']?.split(';')[0]?.trim() || DEFAULT_CONTENT_TYPE,
78
- fileName: fileNameFromDisposition(response.headers['content-disposition']),
75
+ })), Effect.map((fileResponse) => ({
76
+ bytes: fileResponse.data,
77
+ contentType: fileResponse.headers['content-type']?.split(';')[0]?.trim() || DEFAULT_CONTENT_TYPE,
78
+ fileName: fileNameFromDisposition(fileResponse.headers['content-disposition']),
79
79
  })));
@@ -1,4 +1,4 @@
1
- import { decodeEffectSchema } from '../../utils/effectSchema.js';
1
+ import { formatZodIssues } from '../../utils/zodIssues.js';
2
2
  import { Data, Effect } from 'effect';
3
3
  /** Tagged failure returned by migrated eBay API endpoint Effects. */
4
4
  export class EbayApiError extends Data.TaggedError('EbayApiError') {
@@ -11,10 +11,10 @@ export class EbayApiError extends Data.TaggedError('EbayApiError') {
11
11
  export class EndpointInputError extends Data.TaggedError('EndpointInputError') {
12
12
  }
13
13
  /**
14
- * Decodes endpoint input through its Effect-backed schema (the tool's SSOT), reporting a
14
+ * Parses endpoint input through its Zod schema (the tool's SSOT), reporting a
15
15
  * failure as EndpointInputError so endpoint Effects keep one input-failure type.
16
16
  *
17
- * @param schema - Effect-backed endpoint input schema.
17
+ * @param schema - Zod endpoint input schema.
18
18
  * @param input - Raw endpoint input.
19
19
  * @param parameter - Parameter named in the failure; defaults to `input`.
20
20
  * @returns An Effect with the decoded input, or EndpointInputError.
@@ -24,7 +24,12 @@ export class EndpointInputError extends Data.TaggedError('EndpointInputError') {
24
24
  * const { documentId } = yield* decodeEndpointInputEffect(documentIdInputSchema, input);
25
25
  * ```
26
26
  */
27
- export const decodeEndpointInputEffect = (schema, input, parameter = 'input') => decodeEffectSchema(schema, input).pipe(Effect.mapError((cause) => new EndpointInputError({ parameter, message: cause.message })));
27
+ export const decodeEndpointInputEffect = (schema, input, parameter = 'input') => {
28
+ const parsedInput = schema.safeParse(input);
29
+ return parsedInput.success
30
+ ? Effect.succeed(parsedInput.data)
31
+ : Effect.fail(new EndpointInputError({ parameter, message: formatZodIssues(parsedInput.error) }));
32
+ };
28
33
  /**
29
34
  * Builds the per-call `X-EBAY-C-MARKETPLACE-ID` header for operations whose marketplace
30
35
  * differs from the client's configured `EBAY_MARKETPLACE_ID`.
@@ -164,7 +169,7 @@ export const optionalNonNegativeNumberEffect = (value, name) => {
164
169
  *
165
170
  * @example
166
171
  * ```ts
167
- * const request = yield* requireObjectEffect<CreateOffersRequest>(body, 'offerData');
172
+ * const createOffersRequest = yield* requireObjectEffect<CreateOffersRequest>(offerData, 'offerData');
168
173
  * ```
169
174
  */
170
175
  export const requireObjectEffect = (value, name) => {
@@ -187,7 +192,7 @@ export const requireObjectEffect = (value, name) => {
187
192
  *
188
193
  * @example
189
194
  * ```ts
190
- * const response = await Effect.runPromise(requestGetEffect(client, path, params));
195
+ * const policies = await Effect.runPromise(requestGetEffect(client, path, params));
191
196
  * ```
192
197
  */
193
198
  export const requestGetEffect = (client, path, params, config) => Effect.tryPromise({
@@ -204,21 +209,21 @@ export const requestGetEffect = (client, path, params, config) => Effect.tryProm
204
209
  *
205
210
  * @param client - eBay REST client that owns auth and transport details.
206
211
  * @param path - eBay REST path to request.
207
- * @param body - Optional JSON body to send.
212
+ * @param requestBody - Optional JSON body to send.
208
213
  * @param config - Optional per-request headers or params, used for endpoint-specific headers.
209
214
  * @returns An Effect that succeeds with the generated eBay response DTO.
210
215
  *
211
216
  * @example
212
217
  * ```ts
213
- * const response = await Effect.runPromise(requestPostEffect(client, path, body));
218
+ * const createdPolicy = await Effect.runPromise(requestPostEffect(client, path, requestBody));
214
219
  * ```
215
220
  */
216
- export const requestPostEffect = (client, path, body, config) => Effect.tryPromise({
221
+ export const requestPostEffect = (client, path, requestBody, config) => Effect.tryPromise({
217
222
  try: () => {
218
223
  if (config !== undefined) {
219
- return client.post(path, body, config);
224
+ return client.post(path, requestBody, config);
220
225
  }
221
- return body === undefined ? client.post(path) : client.post(path, body);
226
+ return requestBody === undefined ? client.post(path) : client.post(path, requestBody);
222
227
  },
223
228
  catch: (cause) => new EbayApiError({ method: 'POST', path, cause }),
224
229
  });
@@ -227,16 +232,18 @@ export const requestPostEffect = (client, path, body, config) => Effect.tryPromi
227
232
  *
228
233
  * @param client - eBay REST client that owns auth and transport details.
229
234
  * @param path - eBay REST path to request.
230
- * @param body - Optional JSON body to send.
235
+ * @param requestBody - Optional JSON body to send.
231
236
  * @returns An Effect that succeeds with the generated eBay response DTO.
232
237
  *
233
238
  * @example
234
239
  * ```ts
235
- * const response = await Effect.runPromise(requestPutEffect(client, path, body));
240
+ * const updatedPolicy = await Effect.runPromise(requestPutEffect(client, path, requestBody));
236
241
  * ```
237
242
  */
238
- export const requestPutEffect = (client, path, body, config) => Effect.tryPromise({
239
- try: () => config === undefined ? client.put(path, body) : client.put(path, body, config),
243
+ export const requestPutEffect = (client, path, requestBody, config) => Effect.tryPromise({
244
+ try: () => config === undefined
245
+ ? client.put(path, requestBody)
246
+ : client.put(path, requestBody, config),
240
247
  catch: (cause) => new EbayApiError({ method: 'PUT', path, cause }),
241
248
  });
242
249
  /**
@@ -244,17 +251,19 @@ export const requestPutEffect = (client, path, body, config) => Effect.tryPromis
244
251
  *
245
252
  * @param client - eBay REST client that owns auth and transport details.
246
253
  * @param path - eBay REST path to request.
247
- * @param body - Optional JSON body to send.
254
+ * @param requestBody - Optional JSON body to send.
248
255
  * @param config - Optional per-request headers or params, used for endpoint-specific headers.
249
256
  * @returns An Effect that succeeds with the generated eBay response DTO.
250
257
  *
251
258
  * @example
252
259
  * ```ts
253
- * await Effect.runPromise(requestPatchEffect(client, path, body, { headers }));
260
+ * await Effect.runPromise(requestPatchEffect(client, path, requestBody, { headers }));
254
261
  * ```
255
262
  */
256
- export const requestPatchEffect = (client, path, body, config) => Effect.tryPromise({
257
- try: () => config === undefined ? client.patch(path, body) : client.patch(path, body, config),
263
+ export const requestPatchEffect = (client, path, requestBody, config) => Effect.tryPromise({
264
+ try: () => config === undefined
265
+ ? client.patch(path, requestBody)
266
+ : client.patch(path, requestBody, config),
258
267
  catch: (cause) => new EbayApiError({ method: 'PATCH', path, cause }),
259
268
  });
260
269
  /**
@@ -21,68 +21,70 @@ const inputError = (parameter, message) => new EndpointInputError({ parameter, m
21
21
  * Reads a Trading amount, which callers may pass as a bare number, a numeric
22
22
  * string, or an attributed XML node such as `{ '#text': 9.99, '@_currencyID': 'USD' }`.
23
23
  */
24
- const parseTradingAmount = (value) => {
25
- const raw = isRecord(value) ? value['#text'] : value;
26
- if (raw === undefined || raw === null || raw === '') {
24
+ const parseTradingAmount = (rawAmount) => {
25
+ const amount = isRecord(rawAmount) ? rawAmount['#text'] : rawAmount;
26
+ if (amount === undefined || amount === null || amount === '') {
27
27
  return;
28
28
  }
29
- const parsed = Number(raw);
29
+ const parsed = Number(amount);
30
30
  return Number.isFinite(parsed) ? parsed : undefined;
31
31
  };
32
- const isBestOfferEnabled = (item) => {
33
- const details = item.BestOfferDetails;
32
+ const isBestOfferEnabled = (tradingItem) => {
33
+ const details = tradingItem.BestOfferDetails;
34
34
  if (!isRecord(details)) {
35
35
  return false;
36
36
  }
37
37
  return details.BestOfferEnabled === true || details.BestOfferEnabled === 'true';
38
38
  };
39
- const auctionViolation = (item, parameter) => {
40
- if (item.ListingType !== undefined && item.ListingType !== TRADING_AUCTION_LISTING_TYPE) {
41
- return inputError(`${parameter}.ListingType`, `AUCTION listings use ListingType ${TRADING_AUCTION_LISTING_TYPE}; omit it or pass format FIXED_PRICE for ${String(item.ListingType)}`);
39
+ const auctionViolation = (tradingItem, parameter) => {
40
+ if (tradingItem.ListingType !== undefined &&
41
+ tradingItem.ListingType !== TRADING_AUCTION_LISTING_TYPE) {
42
+ return inputError(`${parameter}.ListingType`, `AUCTION listings use ListingType ${TRADING_AUCTION_LISTING_TYPE}; omit it or pass format FIXED_PRICE for ${String(tradingItem.ListingType)}`);
42
43
  }
43
- if (item.ListingDuration === TRADING_GTC_DURATION) {
44
+ if (tradingItem.ListingDuration === TRADING_GTC_DURATION) {
44
45
  return inputError(`${parameter}.ListingDuration`, 'AUCTION listings need a day-count ListingDuration such as Days_7; GTC is only valid for FIXED_PRICE listings');
45
46
  }
46
- if (item.Quantity !== undefined && Number(item.Quantity) !== 1) {
47
+ if (tradingItem.Quantity !== undefined && Number(tradingItem.Quantity) !== 1) {
47
48
  return inputError(`${parameter}.Quantity`, 'AUCTION listings sell a single unit; omit Quantity or set it to 1');
48
49
  }
49
- if (isBestOfferEnabled(item) && item.BuyItNowPrice !== undefined) {
50
+ if (isBestOfferEnabled(tradingItem) && tradingItem.BuyItNowPrice !== undefined) {
50
51
  return inputError(`${parameter}.BestOfferDetails.BestOfferEnabled`, 'an AUCTION listing can carry Best Offer or a BuyItNowPrice, not both');
51
52
  }
52
53
  };
53
54
  /** Fields eBay requires when an auction is created with AddItem. */
54
- const auctionCreateViolation = (item, parameter) => {
55
- if (item.ListingDuration === undefined) {
55
+ const auctionCreateViolation = (tradingItem, parameter) => {
56
+ if (tradingItem.ListingDuration === undefined) {
56
57
  return inputError(`${parameter}.ListingDuration`, 'ListingDuration is required for AUCTION listings (a day count such as Days_7)');
57
58
  }
58
- if (parseTradingAmount(item.StartPrice) === undefined) {
59
+ if (parseTradingAmount(tradingItem.StartPrice) === undefined) {
59
60
  return inputError(`${parameter}.StartPrice`, 'StartPrice (the opening bid) is required for AUCTION listings');
60
61
  }
61
62
  };
62
- const fixedPriceViolation = (item, parameter) => {
63
- if (item.ListingType === TRADING_AUCTION_LISTING_TYPE) {
63
+ const fixedPriceViolation = (tradingItem, parameter) => {
64
+ if (tradingItem.ListingType === TRADING_AUCTION_LISTING_TYPE) {
64
65
  return inputError(`${parameter}.ListingType`, `ListingType ${TRADING_AUCTION_LISTING_TYPE} is an auction; pass format AUCTION instead`);
65
66
  }
66
- if (item.ListingDuration !== undefined && item.ListingDuration !== TRADING_GTC_DURATION) {
67
+ if (tradingItem.ListingDuration !== undefined &&
68
+ tradingItem.ListingDuration !== TRADING_GTC_DURATION) {
67
69
  return inputError(`${parameter}.ListingDuration`, `FIXED_PRICE listings must use ListingDuration ${TRADING_GTC_DURATION} (the only duration eBay accepts for fixed-price listings); day counts such as Days_7 are for AUCTION listings`);
68
70
  }
69
- if (item.ReservePrice !== undefined) {
71
+ if (tradingItem.ReservePrice !== undefined) {
70
72
  return inputError(`${parameter}.ReservePrice`, 'ReservePrice only applies to AUCTION listings');
71
73
  }
72
- if (item.BuyItNowPrice !== undefined) {
74
+ if (tradingItem.BuyItNowPrice !== undefined) {
73
75
  return inputError(`${parameter}.BuyItNowPrice`, 'BuyItNowPrice only applies to AUCTION listings; FIXED_PRICE listings use StartPrice');
74
76
  }
75
77
  };
76
- const auctionPriceViolation = (item, parameter) => {
77
- const start = parseTradingAmount(item.StartPrice);
78
+ const auctionPriceViolation = (tradingItem, parameter) => {
79
+ const start = parseTradingAmount(tradingItem.StartPrice);
78
80
  if (start === undefined) {
79
81
  return;
80
82
  }
81
- const reserve = parseTradingAmount(item.ReservePrice);
83
+ const reserve = parseTradingAmount(tradingItem.ReservePrice);
82
84
  if (reserve !== undefined && reserve <= start) {
83
85
  return inputError(`${parameter}.ReservePrice`, 'ReservePrice must be higher than StartPrice (the opening bid)');
84
86
  }
85
- const buyItNow = parseTradingAmount(item.BuyItNowPrice);
87
+ const buyItNow = parseTradingAmount(tradingItem.BuyItNowPrice);
86
88
  if (buyItNow !== undefined && !meetsBuyItNowMargin(start, buyItNow)) {
87
89
  return inputError(`${parameter}.BuyItNowPrice`, `BuyItNowPrice must be at least ${BUY_IT_NOW_MARGIN_LABEL} higher than StartPrice (the opening bid)`);
88
90
  }
@@ -110,13 +112,13 @@ const auctionPriceViolation = (item, parameter) => {
110
112
  * });
111
113
  * ```
112
114
  */
113
- export const findTradingListingFormatViolation = ({ item, format, parameter, isCreate, }) => {
115
+ export const findTradingListingFormatViolation = ({ item: tradingItem, format, parameter, isCreate, }) => {
114
116
  if (format === FormatType.FIXED_PRICE) {
115
- return fixedPriceViolation(item, parameter);
117
+ return fixedPriceViolation(tradingItem, parameter);
116
118
  }
117
- return (auctionViolation(item, parameter) ??
118
- (isCreate ? auctionCreateViolation(item, parameter) : undefined) ??
119
- auctionPriceViolation(item, parameter));
119
+ return (auctionViolation(tradingItem, parameter) ??
120
+ (isCreate ? auctionCreateViolation(tradingItem, parameter) : undefined) ??
121
+ auctionPriceViolation(tradingItem, parameter));
120
122
  };
121
123
  /**
122
124
  * Rejects a Trading `Item` payload that mixes AUCTION and FIXED_PRICE fields before it reaches eBay.
@@ -126,7 +128,12 @@ export const findTradingListingFormatViolation = ({ item, format, parameter, isC
126
128
  *
127
129
  * @example
128
130
  * ```ts
129
- * yield* validateTradingListingFormatEffect({ item, format, parameter: 'item', isCreate: true });
131
+ * yield* validateTradingListingFormatEffect({
132
+ * item: tradingItem,
133
+ * format,
134
+ * parameter: 'item',
135
+ * isCreate: true,
136
+ * });
130
137
  * ```
131
138
  */
132
139
  export const validateTradingListingFormatEffect = (check) => {
@@ -3,11 +3,11 @@ import { FormatType } from '../../types/ebayEnums.js';
3
3
  import { isRecord } from '../../utils/typeGuards.js';
4
4
  import { Effect } from 'effect';
5
5
  import { TRADING_AUCTION_LISTING_TYPE, resolveTradingFormat, tradingCallName, validateTradingEndingReasonEffect, validateTradingListingFormatEffect, } from './listingFormat.js';
6
- const asRecordArray = (value) => {
7
- if (!Array.isArray(value)) {
6
+ const asRecordArray = (rawField) => {
7
+ if (!Array.isArray(rawField)) {
8
8
  return [];
9
9
  }
10
- return value.filter(isRecord);
10
+ return rawField.filter(isRecord);
11
11
  };
12
12
  /**
13
13
  * High-level wrapper for seller listing operations backed by eBay Trading API calls.
@@ -25,7 +25,7 @@ export class TradingApi {
25
25
  *
26
26
  * @example
27
27
  * ```ts
28
- * const response = await Effect.runPromise(
28
+ * const tradingRecord = await Effect.runPromise(
29
29
  * tradingApi.getActiveListings({ page: 2, entriesPerPage: 25 }),
30
30
  * );
31
31
  * ```
@@ -35,9 +35,9 @@ export class TradingApi {
35
35
  getActiveListings = (input = {}) => {
36
36
  const tradingClient = this.client;
37
37
  return Effect.gen(function* () {
38
- const request = yield* requireObjectEffect(input, 'input');
39
- const inputPage = yield* optionalPositiveNumberEffect(request.page, 'page');
40
- const inputEntriesPerPage = yield* optionalPositiveNumberEffect(request.entriesPerPage, 'entriesPerPage');
38
+ const validatedInput = yield* requireObjectEffect(input, 'input');
39
+ const inputPage = yield* optionalPositiveNumberEffect(validatedInput.page, 'page');
40
+ const inputEntriesPerPage = yield* optionalPositiveNumberEffect(validatedInput.entriesPerPage, 'entriesPerPage');
41
41
  const page = inputPage === undefined ? 1 : inputPage;
42
42
  const entriesPerPage = inputEntriesPerPage === undefined ? 50 : inputEntriesPerPage;
43
43
  return yield* tradingClient.execute('GetMyeBaySelling', {
@@ -67,14 +67,14 @@ export class TradingApi {
67
67
  getListing = (input) => {
68
68
  const tradingClient = this.client;
69
69
  return Effect.gen(function* () {
70
- const request = yield* requireObjectEffect(input, 'input');
71
- const itemId = yield* requireStringEffect(request.itemId, 'itemId');
72
- const result = yield* tradingClient.execute('GetItem', {
70
+ const validatedInput = yield* requireObjectEffect(input, 'input');
71
+ const itemId = yield* requireStringEffect(validatedInput.itemId, 'itemId');
72
+ const getItemResponse = yield* tradingClient.execute('GetItem', {
73
73
  ItemID: itemId,
74
74
  DetailLevel: 'ReturnAll',
75
75
  });
76
- const items = asRecordArray(result.Item);
77
- return items.length > 0 ? items[0] : result;
76
+ const items = asRecordArray(getItemResponse.Item);
77
+ return items.length > 0 ? items[0] : getItemResponse;
78
78
  });
79
79
  };
80
80
  /**
@@ -105,19 +105,19 @@ export class TradingApi {
105
105
  createListing = (input) => {
106
106
  const tradingClient = this.client;
107
107
  return Effect.gen(function* () {
108
- const request = yield* requireObjectEffect(input, 'input');
109
- const format = resolveTradingFormat(request.format);
110
- const item = yield* requireObjectEffect(request.item, 'item');
108
+ const validatedInput = yield* requireObjectEffect(input, 'input');
109
+ const format = resolveTradingFormat(validatedInput.format);
110
+ const tradingItem = yield* requireObjectEffect(validatedInput.item, 'item');
111
111
  yield* validateTradingListingFormatEffect({
112
- item,
112
+ item: tradingItem,
113
113
  format,
114
114
  parameter: 'item',
115
115
  isCreate: true,
116
116
  });
117
- const payload = format === FormatType.AUCTION
118
- ? { ...item, ListingType: TRADING_AUCTION_LISTING_TYPE }
119
- : item;
120
- return yield* tradingClient.execute(tradingCallName('create', format), { Item: payload });
117
+ const listingItem = format === FormatType.AUCTION
118
+ ? { ...tradingItem, ListingType: TRADING_AUCTION_LISTING_TYPE }
119
+ : tradingItem;
120
+ return yield* tradingClient.execute(tradingCallName('create', format), { Item: listingItem });
121
121
  });
122
122
  };
123
123
  /**
@@ -142,10 +142,10 @@ export class TradingApi {
142
142
  reviseListing = (input) => {
143
143
  const tradingClient = this.client;
144
144
  return Effect.gen(function* () {
145
- const request = yield* requireObjectEffect(input, 'input');
146
- const format = resolveTradingFormat(request.format);
147
- const itemId = yield* requireStringEffect(request.itemId, 'itemId');
148
- const fields = yield* requireObjectEffect(request.fields, 'fields');
145
+ const validatedInput = yield* requireObjectEffect(input, 'input');
146
+ const format = resolveTradingFormat(validatedInput.format);
147
+ const itemId = yield* requireStringEffect(validatedInput.itemId, 'itemId');
148
+ const fields = yield* requireObjectEffect(validatedInput.fields, 'fields');
149
149
  yield* validateTradingListingFormatEffect({
150
150
  item: fields,
151
151
  format,
@@ -179,10 +179,10 @@ export class TradingApi {
179
179
  endListing = (input) => {
180
180
  const tradingClient = this.client;
181
181
  return Effect.gen(function* () {
182
- const request = yield* requireObjectEffect(input, 'input');
183
- const format = resolveTradingFormat(request.format);
184
- const itemId = yield* requireStringEffect(request.itemId, 'itemId');
185
- const inputReason = yield* optionalStringEffect(request.reason, 'reason');
182
+ const validatedInput = yield* requireObjectEffect(input, 'input');
183
+ const format = resolveTradingFormat(validatedInput.format);
184
+ const itemId = yield* requireStringEffect(validatedInput.itemId, 'itemId');
185
+ const inputReason = yield* optionalStringEffect(validatedInput.reason, 'reason');
186
186
  yield* validateTradingEndingReasonEffect(inputReason, format, 'reason');
187
187
  const reason = inputReason === undefined ? 'NotAvailable' : inputReason;
188
188
  return yield* tradingClient.execute(tradingCallName('end', format), {
@@ -213,12 +213,12 @@ export class TradingApi {
213
213
  relistItem = (input) => {
214
214
  const tradingClient = this.client;
215
215
  return Effect.gen(function* () {
216
- const request = yield* requireObjectEffect(input, 'input');
217
- const format = resolveTradingFormat(request.format);
218
- const itemId = yield* requireStringEffect(request.itemId, 'itemId');
216
+ const validatedInput = yield* requireObjectEffect(input, 'input');
217
+ const format = resolveTradingFormat(validatedInput.format);
218
+ const itemId = yield* requireStringEffect(validatedInput.itemId, 'itemId');
219
219
  let modifications = {};
220
- if (request.modifications !== undefined) {
221
- modifications = yield* requireObjectEffect(request.modifications, 'modifications');
220
+ if (validatedInput.modifications !== undefined) {
221
+ modifications = yield* requireObjectEffect(validatedInput.modifications, 'modifications');
222
222
  }
223
223
  yield* validateTradingListingFormatEffect({
224
224
  item: modifications,
@@ -1,7 +1,7 @@
1
1
  import dotenv from 'dotenv';
2
- import stringify from 'dotenv-stringify';
3
2
  import { closeSync, existsSync, fsyncSync, openSync, readFileSync, writeFileSync } from 'fs';
4
3
  import { CREDENTIAL_ENV_PATH } from '../config/credentialFile.js';
4
+ import { stringifyEnv } from '../utils/envFile.js';
5
5
  import { Data, Effect } from 'effect';
6
6
  /**
7
7
  * Default lifetime for stored eBay user access tokens when eBay omits an expiry.
@@ -66,15 +66,15 @@ export class DotEnvCredentialStore {
66
66
  try: () => {
67
67
  // Drop non-string values so a missing refresh_token cannot persist as empty.
68
68
  const sanitizedUpdates = {};
69
- for (const [key, value] of Object.entries(updates)) {
70
- if (typeof value === 'string') {
71
- sanitizedUpdates[key] = value;
69
+ for (const [name, envValue] of Object.entries(updates)) {
70
+ if (typeof envValue === 'string') {
71
+ sanitizedUpdates[name] = envValue;
72
72
  }
73
73
  }
74
74
  const existingEnv = existsSync(envPath)
75
75
  ? dotenv.parse(readFileSync(envPath, 'utf-8'))
76
76
  : {};
77
- const safeEnvContent = stringify({ ...existingEnv, ...sanitizedUpdates });
77
+ const safeEnvContent = stringifyEnv({ ...existingEnv, ...sanitizedUpdates });
78
78
  writeFileSync(envPath, safeEnvContent, 'utf-8');
79
79
  // Durability: best-effort fsync. Windows and mocked FS may reject fsync
80
80
  // on a read-only open; never fail the write after a successful writeFileSync.
@@ -415,7 +415,7 @@ export class EbayOAuthClient {
415
415
  * ```
416
416
  */
417
417
  getTokenInfo() {
418
- const info = {
418
+ const tokenInfo = {
419
419
  hasUserToken: this.userTokens !== null && !this.isUserAccessTokenExpired(this.userTokens),
420
420
  hasAppAccessToken: this.appAccessToken !== null && Date.now() < this.appAccessTokenExpiry,
421
421
  };
@@ -426,7 +426,7 @@ export class EbayOAuthClient {
426
426
  const tokenScopeSet = new Set(tokenScopes);
427
427
  const missingScopes = environmentScopes.filter((scope) => !tokenScopeSet.has(scope));
428
428
  return {
429
- ...info,
429
+ ...tokenInfo,
430
430
  scopeInfo: {
431
431
  tokenScopes,
432
432
  environmentScopes,
@@ -434,7 +434,7 @@ export class EbayOAuthClient {
434
434
  },
435
435
  };
436
436
  }
437
- return info;
437
+ return tokenInfo;
438
438
  }
439
439
  /**
440
440
  * Get internal user tokens for debugging and status tools.
@@ -32,7 +32,7 @@ export const generateAuthUrl = (clientId, redirectUri, environment, scopes) => g
32
32
  * HTML-significant characters neutralizes the markup while keeping the message
33
33
  * legible.
34
34
  */
35
- const escapeHtml = (value) => value
35
+ const escapeHtml = (text) => text
36
36
  .replace(/&/g, '&amp;')
37
37
  .replace(/</g, '&lt;')
38
38
  .replace(/>/g, '&gt;')
@@ -107,24 +107,24 @@ const renderCallbackPage = (success, detail = '') => {
107
107
  export const startCallbackServer = async (port = 3000, timeoutMs = 300_000, options = {}) => {
108
108
  const callbackPath = options.path ?? '/oauth/callback';
109
109
  const { expectedState } = options;
110
- return await new Promise((resolve, reject) => {
110
+ return await new Promise((onServerReady, onServerError) => {
111
111
  let timer;
112
112
  let settled = false;
113
113
  let settle;
114
114
  const codePromise = new Promise((resolveCode) => {
115
- settle = (result) => {
115
+ settle = (outcome) => {
116
116
  if (settled)
117
117
  return;
118
118
  settled = true;
119
119
  clearTimeout(timer);
120
- resolveCode(result);
120
+ resolveCode(outcome);
121
121
  };
122
122
  });
123
- const server = createServer((req, res) => {
124
- const url = new URL(req.url ?? '/', `http://localhost:${port}`);
123
+ const server = createServer((request, response) => {
124
+ const url = new URL(request.url ?? '/', `http://localhost:${port}`);
125
125
  if (url.pathname !== callbackPath) {
126
- res.writeHead(404);
127
- res.end('Not Found');
126
+ response.writeHead(404);
127
+ response.end('Not Found');
128
128
  return;
129
129
  }
130
130
  const code = url.searchParams.get('code') ?? undefined;
@@ -132,8 +132,8 @@ export const startCallbackServer = async (port = 3000, timeoutMs = 300_000, opti
132
132
  const error = url.searchParams.get('error') ?? undefined;
133
133
  const errorDescription = url.searchParams.get('error_description') ?? undefined;
134
134
  if (expectedState && state !== expectedState) {
135
- res.writeHead(400, { 'Content-Type': 'text/html' });
136
- res.end(renderCallbackPage(false, 'State mismatch — the request may have been tampered with.'));
135
+ response.writeHead(400, { 'Content-Type': 'text/html' });
136
+ response.end(renderCallbackPage(false, 'State mismatch — the request may have been tampered with.'));
137
137
  settle({
138
138
  error: 'invalid_state',
139
139
  errorDescription: 'State parameter mismatch — possible CSRF.',
@@ -141,23 +141,23 @@ export const startCallbackServer = async (port = 3000, timeoutMs = 300_000, opti
141
141
  return;
142
142
  }
143
143
  if (code) {
144
- res.writeHead(200, { 'Content-Type': 'text/html' });
145
- res.end(renderCallbackPage(true));
144
+ response.writeHead(200, { 'Content-Type': 'text/html' });
145
+ response.end(renderCallbackPage(true));
146
146
  settle({ code, state });
147
147
  return;
148
148
  }
149
149
  if (error) {
150
- res.writeHead(400, { 'Content-Type': 'text/html' });
151
- res.end(renderCallbackPage(false, errorDescription ?? error));
150
+ response.writeHead(400, { 'Content-Type': 'text/html' });
151
+ response.end(renderCallbackPage(false, errorDescription ?? error));
152
152
  settle({ error, errorDescription });
153
153
  return;
154
154
  }
155
- res.writeHead(400);
156
- res.end('Missing authorization code');
155
+ response.writeHead(400);
156
+ response.end('Missing authorization code');
157
157
  });
158
- server.on('error', (err) => {
158
+ server.on('error', (serverError) => {
159
159
  clearTimeout(timer);
160
- reject(err);
160
+ onServerError(serverError);
161
161
  });
162
162
  server.listen(port, () => {
163
163
  timer = setTimeout(() => settle({
@@ -166,7 +166,7 @@ export const startCallbackServer = async (port = 3000, timeoutMs = 300_000, opti
166
166
  }), timeoutMs);
167
167
  // Don't let the timeout alone keep the process alive; the listening server does.
168
168
  timer.unref();
169
- resolve({ server, codePromise });
169
+ onServerReady({ server, codePromise });
170
170
  });
171
171
  });
172
172
  };
@@ -205,16 +205,16 @@ export const interactiveOAuthFlow = async (config, scopes) => {
205
205
  writeCliLine(chalk.gray('Waiting for authorization...'));
206
206
  writeCliLine(chalk.gray('(This window will update automatically after you authorize)\n'));
207
207
  // Wait for callback
208
- const result = await codePromise;
208
+ const callback = await codePromise;
209
209
  // Close server
210
210
  server.close();
211
- if (result.error) {
212
- writeCliLine(chalk.red(`\n✗ Authorization failed: ${result.errorDescription || result.error}\n`));
211
+ if (callback.error) {
212
+ writeCliLine(chalk.red(`\n✗ Authorization failed: ${callback.errorDescription || callback.error}\n`));
213
213
  return null;
214
214
  }
215
- if (result.code) {
215
+ if (callback.code) {
216
216
  writeCliLine(chalk.green('\n✓ Authorization successful!\n'));
217
- return result.code;
217
+ return callback.code;
218
218
  }
219
219
  writeCliLine(chalk.yellow('\n⚠️ No authorization code received.\n'));
220
220
  return null;
@@ -18,7 +18,7 @@ export const createMetadataRouter = (config) => {
18
18
  const router = createRouter();
19
19
  // RFC 9728: Protected Resource Metadata endpoint
20
20
  // Path: /.well-known/oauth-protected-resource
21
- router.get('/.well-known/oauth-protected-resource', (_req, res) => {
21
+ router.get('/.well-known/oauth-protected-resource', (_request, response) => {
22
22
  const authServers = typeof config.authServerMetadata === 'string'
23
23
  ? [config.authServerMetadata]
24
24
  : [config.authServerMetadata.issuer];
@@ -30,10 +30,10 @@ export const createMetadataRouter = (config) => {
30
30
  if (config.resourceDocumentation) {
31
31
  metadata.resource_documentation = config.resourceDocumentation;
32
32
  }
33
- res.json(metadata);
33
+ response.json(metadata);
34
34
  });
35
35
  // Optional: Server info endpoint for debugging
36
- router.get('/.well-known/mcp-server-info', (_req, res) => {
36
+ router.get('/.well-known/mcp-server-info', (_request, response) => {
37
37
  const serverInfo = {
38
38
  name: config.resourceName || 'MCP Resource Server',
39
39
  version: '1.0.0',
@@ -53,7 +53,7 @@ export const createMetadataRouter = (config) => {
53
53
  note: 'MCP OAuth scopes (scopes_supported) are separate from eBay API OAuth scopes (ebay.scopes)',
54
54
  };
55
55
  }
56
- res.json(serverInfo);
56
+ response.json(serverInfo);
57
57
  });
58
58
  return router;
59
59
  };