ebay-mcp 2.0.0 → 2.0.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 +11 -11
- package/build/api/ebaySellerApi.js +0 -4
- package/build/api/other/browse.js +1 -2
- package/build/skills/registrySnapshot.js +1 -1
- package/build/tools/categories/browse.js +2 -10
- package/package.json +4 -4
- package/build/api/other/finding.js +0 -280
- package/build/schemas/other/finding.js +0 -27
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>The eBay MCP server — give Claude, Cursor, and any AI assistant full access to eBay's Sell APIs.
|
|
8
|
+
<strong>The eBay MCP server — give Claude, Cursor, and any AI assistant full access to eBay's Sell APIs. 379 tools for inventory, orders, marketing, and analytics, running locally with your own keys.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center"><sub>Unofficial, open-source project — not affiliated with, authorized, or endorsed by eBay Inc.</sub></p>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
23
|
-
<img src="https://img.shields.io/badge/tools-
|
|
23
|
+
<img src="https://img.shields.io/badge/tools-379-8957e5?logo=ebay&logoColor=white" alt="379 eBay API tools" />
|
|
24
24
|
<img src="https://img.shields.io/badge/Sell%20API%20coverage-100%25-success" alt="100% eBay Sell API coverage" />
|
|
25
25
|
<img src="https://img.shields.io/badge/Model%20Context%20Protocol-compatible-000000" alt="Model Context Protocol compatible" />
|
|
26
26
|
<img src="https://img.shields.io/badge/tests-1%2C000%2B%20passing-3fb950?logo=vitest&logoColor=white" alt="Over 1,000 passing tests" />
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
-
**eBay MCP** is a local [Model Context Protocol](https://modelcontextprotocol.io) server that connects AI assistants — [Claude Desktop](https://claude.ai/download), [Claude Code](https://code.claude.com/docs/en/overview), [Cursor](https://cursor.com/), [Cline](https://cline.bot/), [Windsurf](https://windsurf.com/), [Zed](https://zed.dev/), [Continue.dev](https://docs.continue.dev/), [Roo Code](https://roocode.com/), and [Amazon Q Developer](https://aws.amazon.com/q/developer/) — directly to **[eBay's Sell APIs](https://developer.ebay.com/api-docs/sell/static/overview.html)**. It exposes **
|
|
36
|
+
**eBay MCP** is a local [Model Context Protocol](https://modelcontextprotocol.io) server that connects AI assistants — [Claude Desktop](https://claude.ai/download), [Claude Code](https://code.claude.com/docs/en/overview), [Cursor](https://cursor.com/), [Cline](https://cline.bot/), [Windsurf](https://windsurf.com/), [Zed](https://zed.dev/), [Continue.dev](https://docs.continue.dev/), [Roo Code](https://roocode.com/), and [Amazon Q Developer](https://aws.amazon.com/q/developer/) — directly to **[eBay's Sell APIs](https://developer.ebay.com/api-docs/sell/static/overview.html)**. It exposes **379 tools** spanning **100% of the eBay Sell, Commerce and Developer APIs it tracks** (356 unique endpoints) for inventory management, order fulfillment, promoted-listings marketing, analytics, and developer tooling. Everything runs on your machine over STDIO or local HTTP — **no cloud relay**, and your eBay credentials never leave your computer.
|
|
37
37
|
|
|
38
38
|
> **Disclaimer:** Unofficial, third-party project — **not affiliated with or endorsed by eBay Inc.** Provided "as is" without warranty. You are responsible for complying with [eBay's API License Agreement](https://developer.ebay.com/join/api-license-agreement) and [data-handling requirements](https://developer.ebay.com/api-docs/static/data-handling-update.html), keeping your credentials secure, and staying within rate limits. Test in sandbox before production. See [LICENSE](LICENSE).
|
|
39
39
|
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
## Features
|
|
61
61
|
|
|
62
|
-
- **
|
|
62
|
+
- **379 eBay API tools** — 100% coverage of the eBay Sell APIs across inventory, orders, marketing, analytics, metadata, taxonomy, and developer tooling.
|
|
63
63
|
- **9 AI clients, auto-configured** — Claude Desktop, Cursor, Zed, Cline, Continue.dev, Windsurf, Roo Code, Claude Code CLI, and Amazon Q Developer.
|
|
64
64
|
- **OAuth 2.0 built in** — full user-token management with automatic refresh, and smart fallback from user tokens (10k–50k req/day) to client credentials (1k req/day).
|
|
65
65
|
- **Resilient by default** — automatic retry with exponential backoff on `429` rate limits, and consistent, loud error surfacing.
|
|
@@ -82,7 +82,7 @@ Use this map when deciding which tool family to expose, or when asking an assist
|
|
|
82
82
|
| `stores` | eBay Store details, store categories (add, rename, move, delete), and category task status | "Show my eBay Store category tree." |
|
|
83
83
|
| `fulfillment` | Orders, shipping fulfillments, refunds, payment disputes, and dispute evidence | "Show unfulfilled orders from the last 7 days." |
|
|
84
84
|
| `logistics` | Shipping quotes, label purchase, shipments, and label download (limited-release Logistics API) | "Get shipping rate quotes for this order." |
|
|
85
|
-
| `browse` |
|
|
85
|
+
| `browse` | Active listing search and item details (Browse API) for market research | "What similar items are listed right now?" |
|
|
86
86
|
| `marketing` | Promoted Listings campaigns, ads, promotions, bidding, and marketing reports | "List my active promoted listing campaigns." |
|
|
87
87
|
| `analytics` | Traffic reports, seller standards, and customer-service metrics | "Show my seller standards profile." |
|
|
88
88
|
| `communication` | Buyer-seller messaging, negotiations, notifications, and feedback | "Show recent buyer messages that need a response." |
|
|
@@ -281,7 +281,7 @@ Both talk to the same eBay endpoints — the difference is everything you'd othe
|
|
|
281
281
|
| Input validation | Zod schemas + TypeScript types on every tool | None — you validate your own payloads |
|
|
282
282
|
| Setup | One wizard (`npm run setup`) | Per-call auth, headers, and marketplace wiring |
|
|
283
283
|
| AI client support | 9 clients auto-configured | Not applicable |
|
|
284
|
-
| API coverage |
|
|
284
|
+
| API coverage | 379 tools across 100% of the Sell APIs, ready to call | Build each request from the docs |
|
|
285
285
|
| Hosting | Runs locally, no cloud relay | Your own infrastructure |
|
|
286
286
|
|
|
287
287
|
## One-click AI setup
|
|
@@ -463,9 +463,9 @@ Auto-configured by `npm run setup`. Requires [Node.js](https://nodejs.org/en)
|
|
|
463
463
|
## Available tools
|
|
464
464
|
|
|
465
465
|
<details open>
|
|
466
|
-
<summary><strong>
|
|
466
|
+
<summary><strong>379 tools by category (100% Sell API coverage)</strong></summary>
|
|
467
467
|
|
|
468
|
-
**
|
|
468
|
+
**379 tools**, 100% Sell API coverage, organized by category. Each link points to the tool definitions and handlers in [`src/tools/categories/`](src/tools/categories/):
|
|
469
469
|
|
|
470
470
|
| Category | What you can do |
|
|
471
471
|
| --- | --- |
|
|
@@ -553,7 +553,7 @@ Common tasks, phrased as you'd ask your AI assistant:
|
|
|
553
553
|
<details>
|
|
554
554
|
<summary><strong>What is the eBay MCP server?</strong></summary>
|
|
555
555
|
|
|
556
|
-
A local [Model Context Protocol](https://modelcontextprotocol.io) server that exposes **
|
|
556
|
+
A local [Model Context Protocol](https://modelcontextprotocol.io) server that exposes **379 tools** covering **100% of the eBay Sell, Commerce and Developer APIs it tracks** (356 endpoints) to AI assistants — inventory, order fulfillment, marketing, analytics, and developer tools.
|
|
557
557
|
|
|
558
558
|
</details>
|
|
559
559
|
|
|
@@ -588,7 +588,7 @@ Interactive [MCP Apps](#interactive-ui-mcp-apps) views only appear on hosts that
|
|
|
588
588
|
<details>
|
|
589
589
|
<summary><strong>How many eBay APIs and tools does it cover?</strong></summary>
|
|
590
590
|
|
|
591
|
-
|
|
591
|
+
379 tools across 356 unique endpoints — 100% of the eBay Sell, Commerce and Developer APIs it tracks.
|
|
592
592
|
|
|
593
593
|
</details>
|
|
594
594
|
|
|
@@ -701,7 +701,7 @@ pnpm test && pnpm test:integration # Vitest unit + integration
|
|
|
701
701
|
pnpm build # Build to build/
|
|
702
702
|
```
|
|
703
703
|
|
|
704
|
-
To run the CI gate locally in Docker before pushing, use [act](https://nektosact.com): `act workflow_dispatch -W .github/workflows/ci.yml`. The
|
|
704
|
+
To run the CI gate locally in Docker before pushing, use [act](https://nektosact.com): `act workflow_dispatch -W .github/workflows/ci.yml`. The machine `~/.actrc` selects the runner, architecture and shared caches.
|
|
705
705
|
|
|
706
706
|
## Resources
|
|
707
707
|
|
|
@@ -22,7 +22,6 @@ import { FulfillmentApi } from '../api/order-management/fulfillment.js';
|
|
|
22
22
|
import { LogisticsApi } from '../api/order-management/logistics.js';
|
|
23
23
|
import { EDeliveryApi } from '../api/other/edelivery.js';
|
|
24
24
|
import { BrowseApi } from '../api/other/browse.js';
|
|
25
|
-
import { FindingApi } from '../api/other/finding.js';
|
|
26
25
|
import { IdentityApi } from '../api/other/identity.js';
|
|
27
26
|
import { TranslationApi } from '../api/other/translation.js';
|
|
28
27
|
import { VeroApi } from '../api/other/vero.js';
|
|
@@ -60,7 +59,6 @@ export class EbaySellerApi {
|
|
|
60
59
|
translation;
|
|
61
60
|
edelivery;
|
|
62
61
|
browse;
|
|
63
|
-
finding;
|
|
64
62
|
developer;
|
|
65
63
|
trading;
|
|
66
64
|
constructor(config) {
|
|
@@ -92,7 +90,6 @@ export class EbaySellerApi {
|
|
|
92
90
|
this.translation = new TranslationApi(this.client);
|
|
93
91
|
this.edelivery = new EDeliveryApi(this.client);
|
|
94
92
|
this.browse = new BrowseApi(this.client);
|
|
95
|
-
this.finding = new FindingApi(this.client);
|
|
96
93
|
this.developer = new DeveloperApi(this.client);
|
|
97
94
|
const tradingClient = new TradingApiClient(this.client);
|
|
98
95
|
this.trading = new TradingApi(tradingClient);
|
|
@@ -170,7 +167,6 @@ export { BrowseApi } from '../api/other/browse.js';
|
|
|
170
167
|
export { buildBrowseFilter, requireNoPriceFilterConflict } from '../api/other/browseFilter.js';
|
|
171
168
|
export { mapItemDetailsResponse, mapItemSummary, mapSearchActiveItemsResponse, } from '../api/other/browseMappers.js';
|
|
172
169
|
export { BROWSE_SORT_VALUES, } from '../api/other/browseTypes.js';
|
|
173
|
-
export * from '../api/other/finding.js';
|
|
174
170
|
export * from '../api/other/identity.js';
|
|
175
171
|
export * from '../api/other/translation.js';
|
|
176
172
|
export * from '../api/other/vero.js';
|
|
@@ -27,8 +27,7 @@ export class BrowseApi {
|
|
|
27
27
|
*
|
|
28
28
|
* Uses the Buy Browse API (`item_summary/search`) with the application
|
|
29
29
|
* access token eBay requires for Browse, minted from client credentials
|
|
30
|
-
* under the basic `api_scope`.
|
|
31
|
-
* live listings.
|
|
30
|
+
* under the basic `api_scope`. Returns live listings only.
|
|
32
31
|
*
|
|
33
32
|
* @param input - Query plus optional pagination, sort, and filters.
|
|
34
33
|
* @returns An Effect that succeeds with cleaned active-listing summaries.
|
|
@@ -19,7 +19,7 @@ const FAMILY_BLURBS = {
|
|
|
19
19
|
metadata: 'Marketplace policies, item conditions, listing constraints, automotive compatibility, shipping carriers and services',
|
|
20
20
|
taxonomy: 'Category trees, category suggestions, required item aspects, expired categories, and charities',
|
|
21
21
|
communication: 'Buyer messages, member messages, and notification settings',
|
|
22
|
-
browse: '
|
|
22
|
+
browse: 'Active listing search and item details (Browse API) for market research',
|
|
23
23
|
other: 'Feedback, recommendations, and assorted Sell-API helpers',
|
|
24
24
|
developer: 'API status, rate limits, client registration, and signing keys',
|
|
25
25
|
trading: 'Legacy Trading API (XML) — create / revise / relist / end fixed-price listings',
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { findActiveItemsInputSchema, getItemDetailsInputSchema } from '../../schemas/other/browse.js';
|
|
2
|
-
import { findCompletedItemsInputSchema } from '../../schemas/other/finding.js';
|
|
3
2
|
import { defineTool } from '../../tools/defineTool.js';
|
|
4
3
|
import { Effect } from 'effect';
|
|
5
4
|
/**
|
|
6
|
-
* Browse
|
|
5
|
+
* Browse tools for public marketplace search data.
|
|
7
6
|
*
|
|
8
7
|
* Gated as family `browse` via `EBAY_MCP_TOOLS=browse`.
|
|
9
8
|
*/
|
|
10
9
|
export const browseEntries = [
|
|
11
10
|
defineTool({
|
|
12
11
|
name: 'ebay_find_active_items',
|
|
13
|
-
description: 'Search active eBay listings marketplace-wide (not the seller\'s own inventory). Uses the Buy Browse API item_summary/search with the application access token eBay requires for Browse, minted from client credentials under the basic api_scope. Supports pagination (limit/offset), sort (price, -price, newlyListed, endingSoonest), category restriction, and condition/buying-option/price filters plus a raw Browse filter passthrough. eBay documents that a search without a buying-option filter returns only listings that still offer FIXED_PRICE, so pass buyingOptions: ["AUCTION"] (or ["AUCTION", "FIXED_PRICE"] for both) to be sure of reaching auctions that have taken a bid. Returns cleaned summaries: itemId (feed into ebay_get_item_details), title, price (an auction\'s current bid when there is no fixed price, with bidCount), condition, buyingOptions, seller and feedback, first advertised shipping cost, auction end date, and listing URL. `hasNext` is the authoritative pagination signal, derived from the `next` link in eBay\'s paged-collection response: page forward by advancing `offset` in whole `limit`-sized steps while `hasNext` is true, and stop as soon as it is false. Neither `total` nor a short page is a pagination signal — `total` is eBay\'s match count for the query and in live testing came back as 0 once `offset` ran past the available result window, and a page can hold fewer items than `limit` without being the last one.
|
|
12
|
+
description: 'Search active eBay listings marketplace-wide (not the seller\'s own inventory). Uses the Buy Browse API item_summary/search with the application access token eBay requires for Browse, minted from client credentials under the basic api_scope. Supports pagination (limit/offset), sort (price, -price, newlyListed, endingSoonest), category restriction, and condition/buying-option/price filters plus a raw Browse filter passthrough. eBay documents that a search without a buying-option filter returns only listings that still offer FIXED_PRICE, so pass buyingOptions: ["AUCTION"] (or ["AUCTION", "FIXED_PRICE"] for both) to be sure of reaching auctions that have taken a bid. Returns cleaned summaries: itemId (feed into ebay_get_item_details), title, price (an auction\'s current bid when there is no fixed price, with bidCount), condition, buyingOptions, seller and feedback, first advertised shipping cost, auction end date, and listing URL. `hasNext` is the authoritative pagination signal, derived from the `next` link in eBay\'s paged-collection response: page forward by advancing `offset` in whole `limit`-sized steps while `hasNext` is true, and stop as soon as it is false. Neither `total` nor a short page is a pagination signal — `total` is eBay\'s match count for the query and in live testing came back as 0 once `offset` ran past the available result window, and a page can hold fewer items than `limit` without being the last one.',
|
|
14
13
|
inputSchema: findActiveItemsInputSchema.shape,
|
|
15
14
|
annotations: { readOnlyHint: true },
|
|
16
15
|
handler: (api, args) => Effect.runPromise(api.browse.searchActiveItems(args)),
|
|
@@ -22,11 +21,4 @@ export const browseEntries = [
|
|
|
22
21
|
annotations: { readOnlyHint: true },
|
|
23
22
|
handler: (api, args) => Effect.runPromise(api.browse.getItemDetails(args)),
|
|
24
23
|
}),
|
|
25
|
-
defineTool({
|
|
26
|
-
name: 'ebay_find_completed_items',
|
|
27
|
-
description: 'Search eBay sold/completed listings for pricing research (sold comps). Uses the Finding API findCompletedItems operation with app credentials (SECURITY-APPNAME / EBAY_CLIENT_ID). Returns cleaned sold items: itemId, title, price, shippingCost, soldDate, condition, and listingUrl. Useful for market price research before listing or repricing. App credentials are sufficient for this public search data when OAuth is available.',
|
|
28
|
-
inputSchema: findCompletedItemsInputSchema.shape,
|
|
29
|
-
annotations: { readOnlyHint: true },
|
|
30
|
-
handler: (api, args) => Effect.runPromise(api.finding.findCompletedItems(args)),
|
|
31
|
-
}),
|
|
32
24
|
];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebay-mcp",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Model Context Protocol (MCP) server that exposes 100% of the eBay Sell, Commerce and Developer APIs it tracks (
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Model Context Protocol (MCP) server that exposes 100% of the eBay Sell, Commerce and Developer APIs it tracks (379 tools across 356 endpoints) to AI assistants like Claude, Cursor, and Cline, covering inventory, order fulfillment, marketing, analytics, and developer tools with OAuth authentication and refresh-token support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
7
7
|
"types": "build/index.d.ts",
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
"skills": "node build/scripts/skills.js",
|
|
111
111
|
"sync": "tsx src/scripts/devSync.ts",
|
|
112
112
|
"diagnose": "node build/scripts/diagnostics.js",
|
|
113
|
-
"typecheck": "tsc --noEmit",
|
|
113
|
+
"typecheck": "tsc --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsc/src.tsbuildinfo",
|
|
114
114
|
"typecheck:mcp-apps": "tsc -p mcp-apps/tsconfig.json --noEmit",
|
|
115
|
-
"check": "
|
|
115
|
+
"check": "pnpm run typecheck && pnpm run typecheck:mcp-apps && biome check \"{src,mcp-apps}/**/*.{ts,tsx,js,json,md,css,html}\"",
|
|
116
116
|
"check:ci": "biome ci .",
|
|
117
117
|
"verify": "pnpm run check:ci && pnpm run typecheck && pnpm run typecheck:mcp-apps && pnpm run test && pnpm run build",
|
|
118
118
|
"fix": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true \"{src,mcp-apps}/**/*.{ts,tsx,js,json,md,css,html}\"",
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { EbayApiError, EndpointInputError, optionalPositiveNumberEffect, requireObjectEffect, requireStringEffect, } from '../../api/shared/request.js';
|
|
2
|
-
import { isRecord } from '../../utils/typeGuards.js';
|
|
3
|
-
import { Effect } from 'effect';
|
|
4
|
-
/** Finding Service API version used for findCompletedItems. */
|
|
5
|
-
const FINDING_SERVICE_VERSION = '1.13.0';
|
|
6
|
-
/** Default page size when maxResults is omitted. */
|
|
7
|
-
const DEFAULT_MAX_RESULTS = 20;
|
|
8
|
-
/** Hard upper bound for paginationInput.entriesPerPage. */
|
|
9
|
-
const MAX_ENTRIES_PER_PAGE = 100;
|
|
10
|
-
/** Hard upper bound for the EndTimeFrom days-back window. */
|
|
11
|
-
const MAX_DAYS_BACK = 90;
|
|
12
|
-
/**
|
|
13
|
-
* Resolve the Finding Service base URL for the configured environment.
|
|
14
|
-
*
|
|
15
|
-
* Finding lives on the `svcs` host, not the Sell REST `api` host. When a proxy
|
|
16
|
-
* base URL is configured, the Finding path is appended so the proxy can route it.
|
|
17
|
-
*
|
|
18
|
-
* @param environment - eBay environment used when no override is configured.
|
|
19
|
-
* @param overrideBaseUrl - Optional base URL override from `EBAY_MCP_API_BASE_URL`.
|
|
20
|
-
* @returns Absolute Finding Service URL (no trailing slash).
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* const url = getFindingServiceBaseUrl('production');
|
|
25
|
-
* // https://svcs.ebay.com/services/search/FindingService/v1
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export const getFindingServiceBaseUrl = (environment, overrideBaseUrl) => {
|
|
29
|
-
if (overrideBaseUrl) {
|
|
30
|
-
return `${overrideBaseUrl.replace(/\/$/, '')}/services/search/FindingService/v1`;
|
|
31
|
-
}
|
|
32
|
-
return environment === 'production'
|
|
33
|
-
? 'https://svcs.ebay.com/services/search/FindingService/v1'
|
|
34
|
-
: 'https://svcs.sandbox.ebay.com/services/search/FindingService/v1';
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Build query parameters for Finding API findCompletedItems.
|
|
38
|
-
*
|
|
39
|
-
* Finding authenticates primarily via `SECURITY-APPNAME` (the app client id).
|
|
40
|
-
* Results are limited to sold listings through the `SoldItemsOnly` item filter.
|
|
41
|
-
*
|
|
42
|
-
* @param input - Validated search input plus the app client id.
|
|
43
|
-
* @returns Wire query params for the Finding Service GET request.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* const params = buildFindCompletedItemsParams({
|
|
48
|
-
* keywords: 'iphone 14',
|
|
49
|
-
* maxResults: 20,
|
|
50
|
-
* appId: 'client-id',
|
|
51
|
-
* });
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export const buildFindCompletedItemsParams = (input) => {
|
|
55
|
-
const params = {
|
|
56
|
-
'OPERATION-NAME': 'findCompletedItems',
|
|
57
|
-
'SERVICE-VERSION': FINDING_SERVICE_VERSION,
|
|
58
|
-
'SECURITY-APPNAME': input.appId,
|
|
59
|
-
'RESPONSE-DATA-FORMAT': 'JSON',
|
|
60
|
-
'REST-PAYLOAD': '',
|
|
61
|
-
keywords: input.keywords,
|
|
62
|
-
'paginationInput.entriesPerPage': input.maxResults,
|
|
63
|
-
'itemFilter(0).name': 'SoldItemsOnly',
|
|
64
|
-
'itemFilter(0).value': 'true',
|
|
65
|
-
};
|
|
66
|
-
if (input.daysBack !== undefined) {
|
|
67
|
-
const endTimeFrom = new Date();
|
|
68
|
-
endTimeFrom.setUTCDate(endTimeFrom.getUTCDate() - input.daysBack);
|
|
69
|
-
params['itemFilter(1).name'] = 'EndTimeFrom';
|
|
70
|
-
params['itemFilter(1).value'] = endTimeFrom.toISOString();
|
|
71
|
-
}
|
|
72
|
-
return params;
|
|
73
|
-
};
|
|
74
|
-
/** Read the first string from a Finding JSON array-or-string field. */
|
|
75
|
-
const firstString = (rawField) => {
|
|
76
|
-
if (typeof rawField === 'string' && rawField.length > 0) {
|
|
77
|
-
return rawField;
|
|
78
|
-
}
|
|
79
|
-
if (Array.isArray(rawField) && typeof rawField[0] === 'string' && rawField[0].length > 0) {
|
|
80
|
-
return rawField[0];
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
/** Read the first object from a Finding JSON array-or-object field. */
|
|
84
|
-
const firstRecord = (rawField) => {
|
|
85
|
-
if (isRecord(rawField)) {
|
|
86
|
-
return rawField;
|
|
87
|
-
}
|
|
88
|
-
if (Array.isArray(rawField) && isRecord(rawField[0])) {
|
|
89
|
-
return rawField[0];
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
/** Parse a Finding API money field (`@currencyId` + `__value__`). */
|
|
93
|
-
const parseMoney = (rawMoney) => {
|
|
94
|
-
const money = firstRecord(rawMoney);
|
|
95
|
-
if (!money) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const currency = typeof money['@currencyId'] === 'string'
|
|
99
|
-
? money['@currencyId']
|
|
100
|
-
: typeof money.currencyId === 'string'
|
|
101
|
-
? money.currencyId
|
|
102
|
-
: undefined;
|
|
103
|
-
const amount = typeof money.__value__ === 'string'
|
|
104
|
-
? money.__value__
|
|
105
|
-
: typeof money.value === 'string'
|
|
106
|
-
? money.value
|
|
107
|
-
: undefined;
|
|
108
|
-
if (!(currency && amount)) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
return { currency, value: amount };
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* Map one raw Finding item into a cleaned {@link CompletedItem}.
|
|
115
|
-
*
|
|
116
|
-
* @param raw - One item object from `searchResult.item`.
|
|
117
|
-
* @returns Cleaned item, or undefined when itemId/title are missing.
|
|
118
|
-
*/
|
|
119
|
-
export const mapFindingItem = (raw) => {
|
|
120
|
-
if (!isRecord(raw)) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const itemId = firstString(raw.itemId);
|
|
124
|
-
const title = firstString(raw.title);
|
|
125
|
-
if (!(itemId && title)) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const sellingStatus = firstRecord(raw.sellingStatus);
|
|
129
|
-
const shippingInfo = firstRecord(raw.shippingInfo);
|
|
130
|
-
const listingInfo = firstRecord(raw.listingInfo);
|
|
131
|
-
const condition = firstRecord(raw.condition);
|
|
132
|
-
const price = parseMoney(sellingStatus?.currentPrice);
|
|
133
|
-
const shippingCost = parseMoney(shippingInfo?.shippingServiceCost);
|
|
134
|
-
const soldDate = firstString(listingInfo?.endTime);
|
|
135
|
-
const conditionName = firstString(condition?.conditionDisplayName);
|
|
136
|
-
const listingUrl = firstString(raw.viewItemURL);
|
|
137
|
-
return {
|
|
138
|
-
itemId,
|
|
139
|
-
title,
|
|
140
|
-
...(price === undefined ? {} : { price }),
|
|
141
|
-
...(shippingCost === undefined ? {} : { shippingCost }),
|
|
142
|
-
...(soldDate === undefined ? {} : { soldDate }),
|
|
143
|
-
...(conditionName === undefined ? {} : { condition: conditionName }),
|
|
144
|
-
...(listingUrl === undefined ? {} : { listingUrl }),
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
/**
|
|
148
|
-
* Map a raw Finding findCompletedItems JSON response into cleaned sold comps.
|
|
149
|
-
*
|
|
150
|
-
* Finding wraps most nodes in single-element arrays (XML→JSON conversion).
|
|
151
|
-
*
|
|
152
|
-
* @param raw - Full JSON body returned by the Finding Service.
|
|
153
|
-
* @param keywords - Keywords used for the request (echoed in the result).
|
|
154
|
-
* @returns Cleaned items plus optional total entry count.
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* ```ts
|
|
158
|
-
* const soldListings = mapFindCompletedItemsResponse(rawJson, 'iphone 14');
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
export const mapFindCompletedItemsResponse = (raw, keywords) => {
|
|
162
|
-
if (!isRecord(raw)) {
|
|
163
|
-
return { items: [], keywords };
|
|
164
|
-
}
|
|
165
|
-
const responseRoot = firstRecord(raw.findCompletedItemsResponse);
|
|
166
|
-
if (!responseRoot) {
|
|
167
|
-
return { items: [], keywords };
|
|
168
|
-
}
|
|
169
|
-
const searchResult = firstRecord(responseRoot.searchResult);
|
|
170
|
-
const rawItems = searchResult?.item;
|
|
171
|
-
const itemList = Array.isArray(rawItems) ? rawItems : rawItems === undefined ? [] : [rawItems];
|
|
172
|
-
const items = [];
|
|
173
|
-
for (const entry of itemList) {
|
|
174
|
-
const mapped = mapFindingItem(entry);
|
|
175
|
-
if (mapped) {
|
|
176
|
-
items.push(mapped);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const pagination = firstRecord(responseRoot.paginationOutput);
|
|
180
|
-
const totalRaw = firstString(pagination?.totalEntries);
|
|
181
|
-
const totalEntries = totalRaw !== undefined && Number.isFinite(Number(totalRaw)) ? Number(totalRaw) : undefined;
|
|
182
|
-
return {
|
|
183
|
-
items,
|
|
184
|
-
keywords,
|
|
185
|
-
...(totalEntries === undefined ? {} : { totalEntries }),
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
/**
|
|
189
|
-
* Validate maxResults falls within Finding's supported range.
|
|
190
|
-
*
|
|
191
|
-
* @param maxResults - Positive page size already validated as > 0.
|
|
192
|
-
* @returns The same value when in range, or a tagged input error.
|
|
193
|
-
*/
|
|
194
|
-
const requireMaxResultsInRange = (maxResults) => {
|
|
195
|
-
if (maxResults > MAX_ENTRIES_PER_PAGE) {
|
|
196
|
-
return Effect.fail(new EndpointInputError({
|
|
197
|
-
parameter: 'maxResults',
|
|
198
|
-
message: `maxResults must be between 1 and ${MAX_ENTRIES_PER_PAGE}`,
|
|
199
|
-
}));
|
|
200
|
-
}
|
|
201
|
-
return Effect.succeed(maxResults);
|
|
202
|
-
};
|
|
203
|
-
/**
|
|
204
|
-
* Validate daysBack falls within the supported window when provided.
|
|
205
|
-
*
|
|
206
|
-
* @param daysBack - Optional positive day count.
|
|
207
|
-
* @returns The same value (or undefined) when valid, or a tagged input error.
|
|
208
|
-
*/
|
|
209
|
-
const requireDaysBackInRange = (daysBack) => {
|
|
210
|
-
if (daysBack === undefined) {
|
|
211
|
-
return Effect.succeed(undefined);
|
|
212
|
-
}
|
|
213
|
-
if (daysBack > MAX_DAYS_BACK) {
|
|
214
|
-
return Effect.fail(new EndpointInputError({
|
|
215
|
-
parameter: 'daysBack',
|
|
216
|
-
message: `daysBack must be between 1 and ${MAX_DAYS_BACK}`,
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
return Effect.succeed(daysBack);
|
|
220
|
-
};
|
|
221
|
-
/** Finding API - sold/completed listing search for pricing research. */
|
|
222
|
-
export class FindingApi {
|
|
223
|
-
client;
|
|
224
|
-
constructor(client) {
|
|
225
|
-
this.client = client;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Search sold and completed eBay listings for pricing comps.
|
|
229
|
-
*
|
|
230
|
-
* Uses the legacy Finding API (`findCompletedItems`) with app credentials
|
|
231
|
-
* (`SECURITY-APPNAME`). Useful for market price research; sold listings only.
|
|
232
|
-
*
|
|
233
|
-
* @param input - Keywords plus optional result cap and days-back window.
|
|
234
|
-
* @returns An Effect that succeeds with cleaned sold-item comps.
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```ts
|
|
238
|
-
* const comps = await Effect.runPromise(
|
|
239
|
-
* findingApi.findCompletedItems({ keywords: 'nike dunk low', maxResults: 25, daysBack: 30 }),
|
|
240
|
-
* );
|
|
241
|
-
* ```
|
|
242
|
-
*
|
|
243
|
-
* @see https://developer.ebay.com/devzone/finding/callref/findCompletedItems.html
|
|
244
|
-
*/
|
|
245
|
-
findCompletedItems = (input) => {
|
|
246
|
-
const client = this.client;
|
|
247
|
-
return Effect.gen(function* () {
|
|
248
|
-
const validatedInput = yield* requireObjectEffect(input, 'input');
|
|
249
|
-
const keywords = yield* requireStringEffect(validatedInput.keywords, 'keywords');
|
|
250
|
-
const maxResultsRaw = yield* optionalPositiveNumberEffect(validatedInput.maxResults, 'maxResults');
|
|
251
|
-
const daysBackRaw = yield* optionalPositiveNumberEffect(validatedInput.daysBack, 'daysBack');
|
|
252
|
-
const maxResults = yield* requireMaxResultsInRange(maxResultsRaw ?? DEFAULT_MAX_RESULTS);
|
|
253
|
-
const daysBack = yield* requireDaysBackInRange(daysBackRaw);
|
|
254
|
-
const config = client.getConfig();
|
|
255
|
-
const appId = config.clientId;
|
|
256
|
-
if (!appId) {
|
|
257
|
-
return yield* Effect.fail(new EndpointInputError({
|
|
258
|
-
parameter: 'clientId',
|
|
259
|
-
message: 'EBAY_CLIENT_ID is required for Finding API requests (SECURITY-APPNAME). Set it in your environment.',
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
|
-
const baseUrl = getFindingServiceBaseUrl(config.environment, config.apiBaseUrl);
|
|
263
|
-
const params = buildFindCompletedItemsParams({
|
|
264
|
-
keywords,
|
|
265
|
-
maxResults,
|
|
266
|
-
...(daysBack === undefined ? {} : { daysBack }),
|
|
267
|
-
appId,
|
|
268
|
-
});
|
|
269
|
-
const raw = yield* Effect.tryPromise({
|
|
270
|
-
try: () => client.getWithFullUrl(baseUrl, params),
|
|
271
|
-
catch: (cause) => new EbayApiError({
|
|
272
|
-
method: 'GET',
|
|
273
|
-
path: baseUrl,
|
|
274
|
-
cause,
|
|
275
|
-
}),
|
|
276
|
-
});
|
|
277
|
-
return mapFindCompletedItemsResponse(raw, keywords);
|
|
278
|
-
});
|
|
279
|
-
};
|
|
280
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Finding API Schemas
|
|
4
|
-
*
|
|
5
|
-
* Zod input schemas for sold/completed listing search tools.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Validates input for ebay_find_completed_items (Finding API findCompletedItems).
|
|
9
|
-
*/
|
|
10
|
-
export const findCompletedItemsInputSchema = z.object({
|
|
11
|
-
keywords: z
|
|
12
|
-
.string()
|
|
13
|
-
.min(1)
|
|
14
|
-
.describe('Search keywords for sold/completed listings (e.g. "iphone 14 pro 256")'),
|
|
15
|
-
maxResults: z
|
|
16
|
-
.number()
|
|
17
|
-
.int()
|
|
18
|
-
.positive()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('Maximum sold items to return (1–100). Defaults to 20.'),
|
|
21
|
-
daysBack: z
|
|
22
|
-
.number()
|
|
23
|
-
.int()
|
|
24
|
-
.positive()
|
|
25
|
-
.optional()
|
|
26
|
-
.describe('Only include items that ended within this many days (1–90).'),
|
|
27
|
-
});
|