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.
- package/README.md +24 -35
- package/build/api/account-management/accountV2.js +22 -22
- package/build/api/account-management/finances.js +10 -10
- package/build/api/analytics-and-report/analytics.js +1 -1
- package/build/api/{client.js → client/ebayApiClient.js} +37 -41
- package/build/api/{clientTrading.js → client/tradingApiClient.js} +22 -22
- package/build/api/communication/feedback.js +21 -21
- package/build/api/communication/message.js +23 -23
- package/build/api/communication/negotiation.js +7 -7
- package/build/api/communication/notification.js +52 -52
- package/build/api/developer/developer.js +3 -3
- package/build/api/{index.js → ebaySellerApi.js} +4 -4
- package/build/api/listing-management/feedReportTasks.js +6 -6
- package/build/api/listing-management/feedRequest.js +6 -6
- package/build/api/listing-management/feedSchedules.js +4 -4
- package/build/api/listing-management/feedTasks.js +2 -2
- package/build/api/listing-management/items.js +22 -22
- package/build/api/listing-management/locations.js +4 -4
- package/build/api/listing-management/media.js +14 -14
- package/build/api/listing-management/mediaAttach.js +14 -14
- package/build/api/listing-management/mediaResources.js +15 -13
- package/build/api/listing-management/offerFormat.js +1 -1
- package/build/api/listing-management/offers.js +23 -23
- package/build/api/listing-management/stores.js +9 -9
- package/build/api/listing-metadata/metadata.js +10 -10
- package/build/api/marketing-and-promotions/ads.js +34 -34
- package/build/api/marketing-and-promotions/campaigns.js +19 -19
- package/build/api/marketing-and-promotions/promotions.js +22 -22
- package/build/api/marketing-and-promotions/reports.js +7 -7
- package/build/api/order-management/dispute.js +11 -9
- package/build/api/order-management/fulfillment.js +4 -4
- package/build/api/order-management/logistics.js +7 -7
- package/build/api/other/browseMappers.js +15 -13
- package/build/api/other/browseValidators.js +4 -4
- package/build/api/other/edelivery.js +19 -19
- package/build/api/other/finding.js +13 -13
- package/build/api/other/translation.js +2 -2
- package/build/api/other/vero.js +2 -2
- package/build/api/shared/download.js +7 -7
- package/build/api/shared/request.js +28 -19
- package/build/api/trading/listingFormat.js +37 -30
- package/build/api/trading/trading.js +33 -33
- package/build/auth/credentialSession.js +5 -5
- package/build/auth/oauth.js +3 -3
- package/build/auth/oauthHelper.js +24 -24
- package/build/auth/oauthMetadata.js +4 -4
- package/build/auth/oauthMiddleware.js +16 -16
- package/build/auth/scopeUtils.js +2 -2
- package/build/auth/tokenVerifier.js +23 -23
- package/build/config/environment.js +4 -45
- package/build/config/oauthScopes.js +132 -0
- package/build/index.js +5 -5
- package/build/mcp/httpTransport.js +32 -20
- package/build/mcp/runtime.js +12 -12
- package/build/mcp/toolGating.js +6 -12
- package/build/mcp/uiBridge.js +3 -3
- package/build/schemas/account-management/account.js +2 -52
- package/build/schemas/account-management/accountV2.js +2 -2
- package/build/schemas/account-management/finances.js +1 -1
- package/build/schemas/analytics/analytics.js +2 -48
- package/build/schemas/communication/messages.js +2 -46
- package/build/schemas/developer/developer.js +1 -27
- package/build/schemas/fulfillment/logistics.js +1 -1
- package/build/schemas/fulfillment/orders.js +2 -47
- package/build/schemas/inventory-management/feed.js +1 -1
- package/build/schemas/inventory-management/inventory.js +2 -50
- package/build/schemas/inventory-management/media.js +2 -2
- package/build/schemas/inventory-management/mediaDocuments.js +1 -1
- package/build/schemas/inventory-management/stores.js +1 -1
- package/build/schemas/marketing/ads.js +2 -2
- package/build/schemas/marketing/campaigns.js +2 -2
- package/build/schemas/marketing/common.js +2 -2
- package/build/schemas/marketing/marketing.js +0 -1
- package/build/schemas/marketing/promotions.js +2 -2
- package/build/schemas/marketing/reports.js +2 -2
- package/build/schemas/metadata/metadata.js +2 -90
- package/build/schemas/metadata/shipping.js +1 -1
- package/build/schemas/other/browse.js +2 -2
- package/build/schemas/other/finding.js +2 -2
- package/build/schemas/other/otherApis.js +2 -114
- package/build/schemas/taxonomy/categoryTree.js +1 -1
- package/build/schemas/taxonomy/charity.js +1 -1
- package/build/schemas/taxonomy/taxonomy.js +2 -69
- package/build/scripts/autoSetup.js +3 -3
- package/build/scripts/{buildUi.js → buildMcpApps.js} +13 -13
- package/build/scripts/devSync.js +19 -68
- package/build/scripts/diagnostics.js +15 -16
- package/build/scripts/interactiveSetup.js +25 -25
- package/build/scripts/scopeHelper.js +2 -2
- package/build/scripts/securityChecker.js +20 -20
- package/build/scripts/setup.js +76 -76
- package/build/scripts/setupShared.js +5 -30
- package/build/scripts/setupValidator.js +9 -9
- package/build/scripts/setupWizard.js +23 -23
- package/build/scripts/skills.js +14 -17
- package/build/scripts/specRefs.js +1 -1
- package/build/scripts/specSources.js +144 -0
- package/build/serverHttp.js +4 -7
- package/build/skills/content.js +13 -13
- package/build/skills/install.js +9 -9
- package/build/skills/render.js +2 -2
- package/build/tools/categories/communication.js +1 -1
- package/build/tools/categories/connector.js +19 -16
- package/build/tools/categories/fulfillment.js +4 -4
- package/build/tools/categories/inventory.js +1 -1
- package/build/tools/categories/metadata.js +1 -1
- package/build/tools/categories/taxonomy.js +1 -1
- package/build/tools/categories/tokenManagement.js +8 -8
- package/build/tools/contracts.js +2 -2
- package/build/tools/defineTool.js +27 -12
- package/build/tools/registry.js +2 -2
- package/build/tools/schemas.js +2 -2
- package/build/tools/ui/mapHelpers.js +12 -12
- package/build/tools/ui/maps.js +101 -97
- package/build/tools/ui/viewModels.js +2 -2
- package/build/utils/apiStatusFeed.js +12 -12
- package/build/utils/communication/feedback.js +3 -3
- package/build/utils/communication/message.js +3 -3
- package/build/utils/communication/negotiation.js +3 -3
- package/build/utils/communication/notification.js +3 -3
- package/build/utils/entryModule.js +33 -0
- package/build/utils/envFile.js +47 -0
- package/build/utils/envParser.js +5 -5
- package/build/utils/errors.js +4 -4
- package/build/utils/http.js +38 -38
- package/build/utils/localFeedFiles.js +3 -3
- package/build/utils/logRedaction.js +21 -0
- package/build/utils/logger.js +49 -35
- package/build/utils/other/compliance.js +3 -3
- package/build/utils/other/identity.js +3 -3
- package/build/utils/other/translation.js +3 -3
- package/build/utils/other/vero.js +3 -3
- package/build/utils/schemaHelpers.js +2 -2
- package/build/utils/tokenUtils.js +2 -2
- package/build/utils/trading/trading.js +1 -1
- package/build/utils/version.js +1 -1
- package/build/utils/zodIssues.js +14 -0
- package/package.json +8 -11
- package/README.de.md +0 -426
- package/README.es.md +0 -426
- package/README.fr.md +0 -426
- package/README.ja.md +0 -426
- package/README.ko.md +0 -426
- package/README.pt-BR.md +0 -426
- package/README.ru.md +0 -426
- package/README.zh-CN.md +0 -426
- package/build/schemas/index.js +0 -76
- package/build/schemas/marketing/jsonSchemas.js +0 -118
- package/build/scripts/downloadSpecs.js +0 -117
- package/build/scripts/updateApiStatusDoc.js +0 -71
- package/build/utils/effectSchema.js +0 -203
- package/build/utils/effectSchemaAdapter.js +0 -121
- package/build/utils/effectSchemaChainMethods.js +0 -146
- package/build/utils/effectSchemaRefinements.js +0 -104
- package/build/utils/effectSchemaStringMethods.js +0 -63
- package/build/utils/effectSchemaTypes.js +0 -47
- package/docs/auth/production_scopes.json +0 -111
- package/docs/auth/sandbox_scopes.json +0 -142
- /package/build/api/{clientRequestError.js → client/ebayClientRequestError.js} +0 -0
- /package/build/api/{rateLimitTracker.js → client/rateLimitTracker.js} +0 -0
- /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 = (
|
|
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((
|
|
71
|
+
}).pipe(Effect.filterOrFail((fileResponse) => downloadSize(fileResponse) <= MAX_INLINE_DOWNLOAD_BYTES, (fileResponse) => new DownloadTooLargeError({
|
|
72
72
|
path,
|
|
73
|
-
bytes: downloadSize(
|
|
73
|
+
bytes: downloadSize(fileResponse),
|
|
74
74
|
limit: MAX_INLINE_DOWNLOAD_BYTES,
|
|
75
|
-
})), Effect.map((
|
|
76
|
-
bytes:
|
|
77
|
-
contentType:
|
|
78
|
-
fileName: fileNameFromDisposition(
|
|
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 {
|
|
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
|
-
*
|
|
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 -
|
|
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') =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
218
|
+
* const createdPolicy = await Effect.runPromise(requestPostEffect(client, path, requestBody));
|
|
214
219
|
* ```
|
|
215
220
|
*/
|
|
216
|
-
export const requestPostEffect = (client, path,
|
|
221
|
+
export const requestPostEffect = (client, path, requestBody, config) => Effect.tryPromise({
|
|
217
222
|
try: () => {
|
|
218
223
|
if (config !== undefined) {
|
|
219
|
-
return client.post(path,
|
|
224
|
+
return client.post(path, requestBody, config);
|
|
220
225
|
}
|
|
221
|
-
return
|
|
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
|
|
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
|
|
240
|
+
* const updatedPolicy = await Effect.runPromise(requestPutEffect(client, path, requestBody));
|
|
236
241
|
* ```
|
|
237
242
|
*/
|
|
238
|
-
export const requestPutEffect = (client, path,
|
|
239
|
-
try: () => config === undefined
|
|
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
|
|
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,
|
|
260
|
+
* await Effect.runPromise(requestPatchEffect(client, path, requestBody, { headers }));
|
|
254
261
|
* ```
|
|
255
262
|
*/
|
|
256
|
-
export const requestPatchEffect = (client, path,
|
|
257
|
-
try: () => config === undefined
|
|
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 = (
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
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(
|
|
29
|
+
const parsed = Number(amount);
|
|
30
30
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
31
31
|
};
|
|
32
|
-
const isBestOfferEnabled = (
|
|
33
|
-
const details =
|
|
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 = (
|
|
40
|
-
if (
|
|
41
|
-
|
|
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 (
|
|
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 (
|
|
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(
|
|
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 = (
|
|
55
|
-
if (
|
|
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(
|
|
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 = (
|
|
63
|
-
if (
|
|
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 (
|
|
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 (
|
|
71
|
+
if (tradingItem.ReservePrice !== undefined) {
|
|
70
72
|
return inputError(`${parameter}.ReservePrice`, 'ReservePrice only applies to AUCTION listings');
|
|
71
73
|
}
|
|
72
|
-
if (
|
|
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 = (
|
|
77
|
-
const start = parseTradingAmount(
|
|
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(
|
|
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(
|
|
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(
|
|
117
|
+
return fixedPriceViolation(tradingItem, parameter);
|
|
116
118
|
}
|
|
117
|
-
return (auctionViolation(
|
|
118
|
-
(isCreate ? auctionCreateViolation(
|
|
119
|
-
auctionPriceViolation(
|
|
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({
|
|
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 = (
|
|
7
|
-
if (!Array.isArray(
|
|
6
|
+
const asRecordArray = (rawField) => {
|
|
7
|
+
if (!Array.isArray(rawField)) {
|
|
8
8
|
return [];
|
|
9
9
|
}
|
|
10
|
-
return
|
|
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
|
|
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
|
|
39
|
-
const inputPage = yield* optionalPositiveNumberEffect(
|
|
40
|
-
const inputEntriesPerPage = yield* optionalPositiveNumberEffect(
|
|
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
|
|
71
|
-
const itemId = yield* requireStringEffect(
|
|
72
|
-
const
|
|
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(
|
|
77
|
-
return items.length > 0 ? items[0] :
|
|
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
|
|
109
|
-
const format = resolveTradingFormat(
|
|
110
|
-
const
|
|
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
|
|
118
|
-
? { ...
|
|
119
|
-
:
|
|
120
|
-
return yield* tradingClient.execute(tradingCallName('create', format), { Item:
|
|
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
|
|
146
|
-
const format = resolveTradingFormat(
|
|
147
|
-
const itemId = yield* requireStringEffect(
|
|
148
|
-
const fields = yield* requireObjectEffect(
|
|
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
|
|
183
|
-
const format = resolveTradingFormat(
|
|
184
|
-
const itemId = yield* requireStringEffect(
|
|
185
|
-
const inputReason = yield* optionalStringEffect(
|
|
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
|
|
217
|
-
const format = resolveTradingFormat(
|
|
218
|
-
const itemId = yield* requireStringEffect(
|
|
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 (
|
|
221
|
-
modifications = yield* requireObjectEffect(
|
|
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 [
|
|
70
|
-
if (typeof
|
|
71
|
-
sanitizedUpdates[
|
|
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 =
|
|
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.
|
package/build/auth/oauth.js
CHANGED
|
@@ -415,7 +415,7 @@ export class EbayOAuthClient {
|
|
|
415
415
|
* ```
|
|
416
416
|
*/
|
|
417
417
|
getTokenInfo() {
|
|
418
|
-
const
|
|
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
|
-
...
|
|
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
|
|
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 = (
|
|
35
|
+
const escapeHtml = (text) => text
|
|
36
36
|
.replace(/&/g, '&')
|
|
37
37
|
.replace(/</g, '<')
|
|
38
38
|
.replace(/>/g, '>')
|
|
@@ -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((
|
|
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 = (
|
|
115
|
+
settle = (outcome) => {
|
|
116
116
|
if (settled)
|
|
117
117
|
return;
|
|
118
118
|
settled = true;
|
|
119
119
|
clearTimeout(timer);
|
|
120
|
-
resolveCode(
|
|
120
|
+
resolveCode(outcome);
|
|
121
121
|
};
|
|
122
122
|
});
|
|
123
|
-
const server = createServer((
|
|
124
|
-
const url = new URL(
|
|
123
|
+
const server = createServer((request, response) => {
|
|
124
|
+
const url = new URL(request.url ?? '/', `http://localhost:${port}`);
|
|
125
125
|
if (url.pathname !== callbackPath) {
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
156
|
-
|
|
155
|
+
response.writeHead(400);
|
|
156
|
+
response.end('Missing authorization code');
|
|
157
157
|
});
|
|
158
|
-
server.on('error', (
|
|
158
|
+
server.on('error', (serverError) => {
|
|
159
159
|
clearTimeout(timer);
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
208
|
+
const callback = await codePromise;
|
|
209
209
|
// Close server
|
|
210
210
|
server.close();
|
|
211
|
-
if (
|
|
212
|
-
writeCliLine(chalk.red(`\n✗ Authorization failed: ${
|
|
211
|
+
if (callback.error) {
|
|
212
|
+
writeCliLine(chalk.red(`\n✗ Authorization failed: ${callback.errorDescription || callback.error}\n`));
|
|
213
213
|
return null;
|
|
214
214
|
}
|
|
215
|
-
if (
|
|
215
|
+
if (callback.code) {
|
|
216
216
|
writeCliLine(chalk.green('\n✓ Authorization successful!\n'));
|
|
217
|
-
return
|
|
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', (
|
|
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
|
-
|
|
33
|
+
response.json(metadata);
|
|
34
34
|
});
|
|
35
35
|
// Optional: Server info endpoint for debugging
|
|
36
|
-
router.get('/.well-known/mcp-server-info', (
|
|
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
|
-
|
|
56
|
+
response.json(serverInfo);
|
|
57
57
|
});
|
|
58
58
|
return router;
|
|
59
59
|
};
|