ebay-mcp 1.6.1 → 1.7.1
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 +45 -104
- package/build/api/client.js +7 -5
- package/build/api/index.js +2 -2
- package/build/api/other/identity.js +1 -1
- package/build/auth/oauth.js +4 -4
- package/build/config/environment.js +45 -13
- package/build/index.js +14 -0
- package/build/scripts/setup.js +31 -10
- package/build/scripts/update-api-status-doc.js +44 -0
- package/build/server-http.js +32 -13
- package/build/tools/definitions/developer.js +43 -0
- package/build/tools/index.js +46 -5
- package/build/utils/api-status-feed.js +85 -0
- package/build/utils/version.js +56 -0
- package/package.json +5 -1
- package/public/icons/1024x1024.png +0 -0
- package/public/icons/128x128.png +0 -0
- package/public/icons/16x16.png +0 -0
- package/public/icons/256x256.png +0 -0
- package/public/icons/32x32.png +0 -0
- package/public/icons/48x48.png +0 -0
- package/public/icons/512x512.png +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://mseep.ai/app/yosefhayim-ebay-api-mcp-server)
|
|
12
12
|
<a href="https://www.buymeacoffee.com/yosefhayim" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
13
13
|
|
|
14
|
-
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server providing AI assistants with comprehensive access to eBay's Sell APIs. Includes **
|
|
14
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server providing AI assistants with comprehensive access to eBay's Sell APIs. Includes **325 tools** for inventory management, order fulfillment, marketing campaigns, analytics, developer tools, and more.
|
|
15
15
|
|
|
16
16
|
**API Coverage:** 100% (270 unique eBay API endpoints)
|
|
17
17
|
|
|
@@ -99,6 +99,7 @@ For official eBay API support, please refer to the [eBay Developer Program](http
|
|
|
99
99
|
- [⚠️ Disclaimer](#️-disclaimer)
|
|
100
100
|
- [Features](#features)
|
|
101
101
|
- [Quick Start](#quick-start)
|
|
102
|
+
- [Demo](#demo)
|
|
102
103
|
- [Visual Setup Guide](#visual-setup-guide)
|
|
103
104
|
- [Configuration](#configuration)
|
|
104
105
|
- [Available Tools](#available-tools)
|
|
@@ -112,7 +113,7 @@ For official eBay API support, please refer to the [eBay Developer Program](http
|
|
|
112
113
|
|
|
113
114
|
## Features
|
|
114
115
|
|
|
115
|
-
- **
|
|
116
|
+
- **325 eBay API Tools** - 100% coverage of eBay Sell APIs across inventory, orders, marketing, analytics, developer tools, and more
|
|
116
117
|
- **9 AI Clients Supported** - Auto-configuration for Claude Desktop, Cursor, Zed, Cline, Continue.dev, Windsurf, Roo Code, Claude Code CLI, and Amazon Q
|
|
117
118
|
- **OAuth 2.0 Support** - Full user token management with automatic refresh
|
|
118
119
|
- **Type Safety** - Built with TypeScript, Zod validation, and OpenAPI-generated types
|
|
@@ -147,122 +148,65 @@ npm install
|
|
|
147
148
|
npm run build
|
|
148
149
|
```
|
|
149
150
|
|
|
150
|
-
### 3.
|
|
151
|
+
### 3. Run Setup Wizard
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
The interactive setup wizard handles everything for you:
|
|
153
154
|
|
|
154
155
|
```bash
|
|
155
156
|
npm run setup
|
|
156
157
|
```
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
The wizard will:
|
|
160
|
+
- Configure your eBay credentials
|
|
161
|
+
- Set up OAuth authentication (for higher rate limits)
|
|
162
|
+
- Auto-detect and configure your MCP client (Claude Desktop, etc.)
|
|
163
|
+
- Save all configuration automatically
|
|
159
164
|
|
|
160
165
|
---
|
|
161
166
|
|
|
162
|
-
##
|
|
167
|
+
## Demo
|
|
163
168
|
|
|
164
|
-
|
|
169
|
+
See the eBay MCP Server in action with Claude Desktop:
|
|
165
170
|
|
|
166
|
-
|
|
171
|
+
https://github.com/user-attachments/assets/0173c8df-221c-4943-a4ce-cd20bce79f4b
|
|
167
172
|
|
|
168
|
-
|
|
173
|
+
---
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
## Visual Setup Guide
|
|
171
176
|
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
EBAY_CLIENT_ID=your_app_id_here
|
|
175
|
-
EBAY_CLIENT_SECRET=your_cert_id_here
|
|
176
|
-
EBAY_ENVIRONMENT=sandbox # or "production"
|
|
177
|
-
```
|
|
177
|
+
The setup wizard (`npm run setup`) handles OAuth authentication automatically. Here's where to find your credentials in the eBay Developer Portal:
|
|
178
178
|
|
|
179
|
-
###
|
|
179
|
+
### Finding Your Credentials
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
**Step 1:** Navigate to [eBay Developer Portal](https://developer.ebay.com/my/keys) and copy your **App ID (Client ID)** and **Cert ID (Client Secret)**:
|
|
182
182
|
|
|
183
|
-

|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
EBAY_REDIRECT_URI=your_runame_here
|
|
188
|
-
```
|
|
185
|
+
**Step 2:** In your app's **User Tokens** settings, copy the **RuName** (eBay Redirect URL):
|
|
189
186
|
|
|
190
|
-
|
|
187
|
+

|
|
191
188
|
|
|
192
|
-
|
|
189
|
+
### Running the Setup Wizard
|
|
193
190
|
|
|
194
|
-
|
|
195
|
-
npm run setup
|
|
196
|
-
```
|
|
191
|
+
Run `npm run setup` and enter your credentials when prompted. The wizard will:
|
|
197
192
|
|
|
198
|
-
|
|
193
|
+
1. Open your browser for OAuth login automatically
|
|
194
|
+
2. Guide you through the eBay sign-in process
|
|
199
195
|
|
|
200
196
|

|
|
201
197
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
After granting permissions, you'll be redirected to a URL containing an authorization code. Copy the **code** parameter from the URL and paste it into the setup wizard terminal.
|
|
198
|
+
3. Ask you to paste the authorization code from the callback URL
|
|
205
199
|
|
|
206
200
|

|
|
207
201
|
|
|
208
|
-
|
|
202
|
+
4. Exchange the code for tokens and save them automatically
|
|
203
|
+
5. Configure your MCP client (Claude Desktop, etc.)
|
|
209
204
|
|
|
210
205
|
**Success!** You now have user token authentication with 10k-50k requests/day instead of the default 1k/day.
|
|
211
206
|
|
|
212
207
|
---
|
|
213
208
|
|
|
214
|
-
### 4.
|
|
215
|
-
|
|
216
|
-
Add this server to your MCP client configuration:
|
|
217
|
-
|
|
218
|
-
**Claude Desktop:**
|
|
219
|
-
|
|
220
|
-
Edit your Claude Desktop config file:
|
|
221
|
-
|
|
222
|
-
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
223
|
-
- Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
224
|
-
- Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
225
|
-
|
|
226
|
-
Add the server configuration:
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{
|
|
230
|
-
"mcpServers": {
|
|
231
|
-
"ebay": {
|
|
232
|
-
"command": "npx",
|
|
233
|
-
"args": ["-y", "ebay-mcp"],
|
|
234
|
-
"env": {
|
|
235
|
-
"EBAY_CLIENT_ID": "your_client_id",
|
|
236
|
-
"EBAY_CLIENT_SECRET": "your_client_secret",
|
|
237
|
-
"EBAY_ENVIRONMENT": "sandbox",
|
|
238
|
-
"EBAY_REDIRECT_URI": "your_runame"
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Alternative: Use locally installed version**
|
|
246
|
-
|
|
247
|
-
If you installed from source:
|
|
248
|
-
|
|
249
|
-
```json
|
|
250
|
-
{
|
|
251
|
-
"mcpServers": {
|
|
252
|
-
"ebay": {
|
|
253
|
-
"command": "node",
|
|
254
|
-
"args": ["/absolute/path/to/ebay-mcp/build/index.js"],
|
|
255
|
-
"env": {
|
|
256
|
-
"EBAY_CLIENT_ID": "your_client_id",
|
|
257
|
-
"EBAY_CLIENT_SECRET": "your_client_secret",
|
|
258
|
-
"EBAY_ENVIRONMENT": "sandbox"
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### 5. Use
|
|
209
|
+
### 4. Use
|
|
266
210
|
|
|
267
211
|
Restart your MCP client (Claude Desktop, etc.) and start using eBay tools through your AI assistant.
|
|
268
212
|
|
|
@@ -272,32 +216,21 @@ Restart your MCP client (Claude Desktop, etc.) and start using eBay tools throug
|
|
|
272
216
|
|
|
273
217
|
### Environment Variables
|
|
274
218
|
|
|
275
|
-
|
|
219
|
+
The setup wizard (`npm run setup`) automatically creates and configures your `.env` file. For reference, these are the environment variables used:
|
|
276
220
|
|
|
277
221
|
```bash
|
|
278
222
|
EBAY_CLIENT_ID=your_client_id
|
|
279
223
|
EBAY_CLIENT_SECRET=your_client_secret
|
|
280
224
|
EBAY_ENVIRONMENT=sandbox # or "production"
|
|
281
225
|
EBAY_REDIRECT_URI=your_runame
|
|
282
|
-
|
|
283
|
-
# Optional: For higher rate limits (10k-50k req/day)
|
|
284
|
-
EBAY_USER_REFRESH_TOKEN=your_refresh_token
|
|
226
|
+
EBAY_USER_REFRESH_TOKEN=your_refresh_token # For higher rate limits
|
|
285
227
|
```
|
|
286
228
|
|
|
287
229
|
### OAuth Authentication
|
|
288
230
|
|
|
289
|
-
**Client Credentials (
|
|
290
|
-
|
|
291
|
-
- Default authentication method
|
|
292
|
-
- 1,000 requests/day
|
|
293
|
-
- No setup required beyond client ID and secret
|
|
231
|
+
**Client Credentials (Default):** 1,000 requests/day - works automatically with just Client ID and Secret.
|
|
294
232
|
|
|
295
|
-
**User Tokens (Recommended
|
|
296
|
-
|
|
297
|
-
- 10,000-50,000 requests/day
|
|
298
|
-
- Use `ebay_get_oauth_url` tool to generate authorization URL
|
|
299
|
-
- Add `EBAY_USER_REFRESH_TOKEN` to `.env` after OAuth flow
|
|
300
|
-
- Tokens refresh automatically
|
|
233
|
+
**User Tokens (Recommended):** 10,000-50,000 requests/day - the setup wizard handles the OAuth flow automatically. Tokens refresh automatically.
|
|
301
234
|
|
|
302
235
|
For detailed OAuth setup and comprehensive configuration guide, see the [Configuration Documentation](docs/auth/CONFIGURATION.md).
|
|
303
236
|
|
|
@@ -327,7 +260,7 @@ This server supports **9 AI clients** with auto-configuration via `npm run setup
|
|
|
327
260
|
|
|
328
261
|
```bash
|
|
329
262
|
npm install -g ebay-mcp
|
|
330
|
-
npx ebay-mcp # Interactive setup wizard - auto-detects installed clients
|
|
263
|
+
npx ebay-mcp setup # Interactive setup wizard - auto-detects installed clients
|
|
331
264
|
```
|
|
332
265
|
|
|
333
266
|
The setup wizard will automatically detect which AI clients you have installed and configure them for you.
|
|
@@ -377,7 +310,7 @@ Monitor your API usage in the [eBay Developer Portal](https://developer.ebay.com
|
|
|
377
310
|
|
|
378
311
|
## Available Tools
|
|
379
312
|
|
|
380
|
-
The server provides **
|
|
313
|
+
The server provides **325 tools** with **100% API coverage** organized into the following categories:
|
|
381
314
|
|
|
382
315
|
- **Account Management** - Policies, programs, subscriptions, sales tax
|
|
383
316
|
- **Inventory Management** - Items, offers, locations, bulk operations, SKU location mapping
|
|
@@ -631,7 +564,7 @@ Log files are stored in `~/.ebay-mcp/logs/`:
|
|
|
631
564
|
|
|
632
565
|
1. Verify you're using the correct environment (sandbox vs production)
|
|
633
566
|
2. Ensure you have proper permissions/scopes for the operation
|
|
634
|
-
3. Check
|
|
567
|
+
3. Check current API status with the `ebay_get_api_status` tool or the [eBay API Status](https://developer.ebay.com/support/api-status) page
|
|
635
568
|
4. Run `npm run diagnose` to check your configuration
|
|
636
569
|
5. Review the [eBay API documentation](https://developer.ebay.com/docs) for endpoint requirements
|
|
637
570
|
|
|
@@ -669,12 +602,20 @@ If you're still experiencing issues:
|
|
|
669
602
|
|
|
670
603
|
## Resources
|
|
671
604
|
|
|
605
|
+
### API Status
|
|
606
|
+
|
|
607
|
+
Check current eBay API health, incidents, and fixes:
|
|
608
|
+
|
|
609
|
+
- [eBay API Status](https://developer.ebay.com/support/api-status) - Official status page
|
|
610
|
+
- [API Status RSS feed](https://developer.ebay.com/rss/api-status) - Latest issues and resolutions (XML)
|
|
611
|
+
- **`ebay_get_api_status`** - MCP tool that returns the latest items from this feed (filter by status or API name, optional limit)
|
|
612
|
+
- [Latest snapshot (auto-updated)](docs/API_STATUS.md) - In-repo digest of recent status items
|
|
613
|
+
|
|
672
614
|
### Documentation
|
|
673
615
|
|
|
674
616
|
- [eBay Developer Portal](https://developer.ebay.com/) - API documentation and credentials
|
|
675
617
|
- [eBay API License Agreement](https://developer.ebay.com/join/api-license-agreement) - Terms of use and compliance requirements
|
|
676
618
|
- [eBay Data Handling Requirements](https://developer.ebay.com/api-docs/static/data-handling-update.html) - Important data protection and privacy guidelines
|
|
677
|
-
- [eBay API Status](https://developer.ebay.com/support/api-status) - Real-time API health and status
|
|
678
619
|
- [MCP Documentation](https://modelcontextprotocol.io/) - Model Context Protocol specification
|
|
679
620
|
- [OAuth Quick Reference](docs/auth/OAUTH_QUICK_REFERENCE.md) - **Complete OAuth authentication guide with scopes, troubleshooting, and examples**
|
|
680
621
|
- [OAuth Setup Guide](docs/auth/) - Detailed authentication configuration
|
package/build/api/client.js
CHANGED
|
@@ -149,7 +149,9 @@ export class EbayApiClient {
|
|
|
149
149
|
attempt: `${retryCount + 1}/3`,
|
|
150
150
|
delayMs: Math.min(delay, 5000),
|
|
151
151
|
});
|
|
152
|
-
await new Promise((resolve) =>
|
|
152
|
+
await new Promise((resolve) => {
|
|
153
|
+
setTimeout(resolve, Math.min(delay, 5000));
|
|
154
|
+
});
|
|
153
155
|
return await this.httpClient.request(config);
|
|
154
156
|
}
|
|
155
157
|
}
|
|
@@ -168,8 +170,8 @@ export class EbayApiClient {
|
|
|
168
170
|
* Validate that access token is available before making API request
|
|
169
171
|
*/
|
|
170
172
|
validateAccessToken() {
|
|
171
|
-
if (!this.
|
|
172
|
-
throw new Error('
|
|
173
|
+
if (!this.config.clientId || !this.config.clientSecret) {
|
|
174
|
+
throw new Error('Missing required eBay credentials. Please set EBAY_CLIENT_ID and EBAY_CLIENT_SECRET in your .env file.');
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
/**
|
|
@@ -225,8 +227,8 @@ export class EbayApiClient {
|
|
|
225
227
|
/**
|
|
226
228
|
* Set user access and refresh tokens
|
|
227
229
|
*/
|
|
228
|
-
async setUserTokens(accessToken, refreshToken) {
|
|
229
|
-
|
|
230
|
+
async setUserTokens(accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry) {
|
|
231
|
+
this.authClient.setUserTokens(accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry);
|
|
230
232
|
}
|
|
231
233
|
/**
|
|
232
234
|
* Get token information for debugging
|
package/build/api/index.js
CHANGED
|
@@ -87,8 +87,8 @@ export class EbaySellerApi {
|
|
|
87
87
|
/**
|
|
88
88
|
* Set user access and refresh tokens
|
|
89
89
|
*/
|
|
90
|
-
async setUserTokens(accessToken, refreshToken) {
|
|
91
|
-
await this.client.setUserTokens(accessToken, refreshToken);
|
|
90
|
+
async setUserTokens(accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry) {
|
|
91
|
+
await this.client.setUserTokens(accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry);
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Get OAuth client for advanced operations
|
|
@@ -18,7 +18,7 @@ export class IdentityApi {
|
|
|
18
18
|
async getUser() {
|
|
19
19
|
const config = this.client.getConfig();
|
|
20
20
|
const identityBaseUrl = getIdentityBaseUrl(config.environment);
|
|
21
|
-
const fullUrl = `${identityBaseUrl}${this.basePath}/user
|
|
21
|
+
const fullUrl = `${identityBaseUrl}${this.basePath}/user`;
|
|
22
22
|
return await this.client.getWithFullUrl(fullUrl);
|
|
23
23
|
}
|
|
24
24
|
}
|
package/build/auth/oauth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { getBaseUrl, getDefaultScopes } from '../config/environment.js';
|
|
3
3
|
import { LocaleEnum } from '../types/ebay-enums.js';
|
|
4
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { authLogger } from '../utils/logger.js';
|
|
7
7
|
/**
|
|
@@ -10,7 +10,7 @@ import { authLogger } from '../utils/logger.js';
|
|
|
10
10
|
function updateEnvFile(updates) {
|
|
11
11
|
try {
|
|
12
12
|
const envPath = join(process.cwd(), '.env');
|
|
13
|
-
let envContent = readFileSync(envPath, 'utf-8');
|
|
13
|
+
let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
|
|
14
14
|
// Update each key-value pair
|
|
15
15
|
for (const [key, value] of Object.entries(updates)) {
|
|
16
16
|
// Match the key with or without value, handling comments
|
|
@@ -28,7 +28,7 @@ function updateEnvFile(updates) {
|
|
|
28
28
|
writeFileSync(envPath, envContent, 'utf-8');
|
|
29
29
|
// Tokens updated silently - console output interferes with MCP JSON protocol
|
|
30
30
|
}
|
|
31
|
-
catch (
|
|
31
|
+
catch (_error) {
|
|
32
32
|
// Silent failure - error logging interferes with MCP JSON protocol
|
|
33
33
|
// If needed, check .env file manually
|
|
34
34
|
}
|
|
@@ -180,7 +180,7 @@ export class EbayOAuthClient {
|
|
|
180
180
|
*/
|
|
181
181
|
async getOrRefreshAppAccessToken() {
|
|
182
182
|
// Return cached token if still valid
|
|
183
|
-
if (this.appAccessToken) {
|
|
183
|
+
if (this.appAccessToken && Date.now() < this.appAccessTokenExpiry) {
|
|
184
184
|
return this.appAccessToken;
|
|
185
185
|
}
|
|
186
186
|
const authUrl = `${getBaseUrl(this.config.environment)}/identity/v1/oauth2/token`;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { config } from 'dotenv';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
5
|
import { LocaleEnum } from '../types/ebay-enums.js';
|
|
6
|
+
import { getVersion } from '../utils/version.js';
|
|
6
7
|
// Load .env silently - suppress dotenv output to keep stdout clean for MCP JSON-RPC
|
|
7
8
|
config({ quiet: true });
|
|
8
9
|
// Get the current directory for loading scope files
|
|
@@ -58,7 +59,16 @@ function getSandboxScopes() {
|
|
|
58
59
|
* Get default scopes for the specified environment
|
|
59
60
|
*/
|
|
60
61
|
export function getDefaultScopes(environment) {
|
|
61
|
-
|
|
62
|
+
if (environment === 'production') {
|
|
63
|
+
return getProductionScopes();
|
|
64
|
+
}
|
|
65
|
+
const sandboxScopes = getSandboxScopes();
|
|
66
|
+
const productionScopes = getProductionScopes();
|
|
67
|
+
const productionWithoutEdelivery = productionScopes.filter((scope) => scope !== 'https://api.ebay.com/oauth/scope/sell.edelivery');
|
|
68
|
+
const mergedScopes = new Set([...sandboxScopes, ...productionWithoutEdelivery]);
|
|
69
|
+
mergedScopes.add('https://api.ebay.com/oauth/api_scope/sell.edelivery');
|
|
70
|
+
mergedScopes.add('https://api.ebay.com/oauth/scope/sell.edelivery');
|
|
71
|
+
return Array.from(mergedScopes);
|
|
62
72
|
}
|
|
63
73
|
/**
|
|
64
74
|
* Validate scopes against environment and return warnings for invalid scopes
|
|
@@ -172,14 +182,15 @@ export function getAuthUrl(clientIdOrEnvironment, redirectUri, environment, loca
|
|
|
172
182
|
}
|
|
173
183
|
// Otherwise, generate the full OAuth authorization URL
|
|
174
184
|
const clientId = clientIdOrEnvironment;
|
|
175
|
-
const env = environment
|
|
185
|
+
const env = environment ?? 'sandbox';
|
|
176
186
|
const scope = getDefaultScopes(env);
|
|
177
187
|
if (!(clientId && redirectUri)) {
|
|
178
188
|
console.error('clientId, redirectUri (RuName), and scope are required,please initialize the class properly.');
|
|
179
189
|
return '';
|
|
180
190
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
191
|
+
// Build consent URL with auth2 endpoint (eBay's OAuth consent endpoint)
|
|
192
|
+
const consentDomain = env === 'production' ? 'https://auth2.ebay.com' : 'https://auth2.sandbox.ebay.com';
|
|
193
|
+
const consentParams = new URLSearchParams({
|
|
183
194
|
client_id: clientId,
|
|
184
195
|
redirect_uri: redirectUri,
|
|
185
196
|
response_type: responseType,
|
|
@@ -188,7 +199,10 @@ export function getAuthUrl(clientIdOrEnvironment, redirectUri, environment, loca
|
|
|
188
199
|
locale,
|
|
189
200
|
...(state ? { state } : {}),
|
|
190
201
|
});
|
|
191
|
-
|
|
202
|
+
const consentUrl = `${consentDomain}/oauth2/consents?${consentParams.toString()}`;
|
|
203
|
+
// Build signin URL that redirects to consent
|
|
204
|
+
const signinDomain = env === 'production' ? 'https://signin.ebay.com' : 'https://signin.sandbox.ebay.com';
|
|
205
|
+
return `${signinDomain}/signin?ru=${encodeURIComponent(consentUrl)}&sgfl=oauth2&AppName=${encodeURIComponent(clientId)}`;
|
|
192
206
|
}
|
|
193
207
|
/**
|
|
194
208
|
* Generate the OAuth authorization URL for user consent
|
|
@@ -227,34 +241,52 @@ environment, scopes, locale, state) {
|
|
|
227
241
|
const ruParam = encodeURIComponent(`${authorizeEndpoint}?${params.toString()}`);
|
|
228
242
|
return `${signinDomain}/signin?ru=${ruParam}&sgfl=oauth2_login&AppName=${clientId}`;
|
|
229
243
|
}
|
|
244
|
+
const iconUrl = (size) => {
|
|
245
|
+
const url = new URL(`../../public/icons/${size}.png`, import.meta.url);
|
|
246
|
+
const path = fileURLToPath(url);
|
|
247
|
+
if (!existsSync(path)) {
|
|
248
|
+
console.warn(`[eBay MCP] Icon not found at ${path}. Ensure public/icons is included in the package.`);
|
|
249
|
+
}
|
|
250
|
+
return url.toString();
|
|
251
|
+
};
|
|
230
252
|
export const mcpConfig = {
|
|
231
253
|
name: 'eBay API Model Context Protocol Server',
|
|
232
|
-
version:
|
|
254
|
+
version: getVersion(),
|
|
233
255
|
title: 'eBay API Model Context Protocol Server',
|
|
234
|
-
websiteUrl: 'https://github.com/
|
|
256
|
+
websiteUrl: 'https://github.com/YosefHayim/ebay-mcp',
|
|
235
257
|
icons: [
|
|
236
258
|
{
|
|
237
|
-
src: '
|
|
259
|
+
src: iconUrl('16x16'),
|
|
260
|
+
mimeType: 'image/png',
|
|
261
|
+
sizes: ['16x16'],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
src: iconUrl('32x32'),
|
|
265
|
+
mimeType: 'image/png',
|
|
266
|
+
sizes: ['32x32'],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
src: iconUrl('48x48'),
|
|
238
270
|
mimeType: 'image/png',
|
|
239
271
|
sizes: ['48x48'],
|
|
240
272
|
},
|
|
241
273
|
{
|
|
242
|
-
src: '
|
|
274
|
+
src: iconUrl('128x128'),
|
|
243
275
|
mimeType: 'image/png',
|
|
244
276
|
sizes: ['128x128'],
|
|
245
277
|
},
|
|
246
278
|
{
|
|
247
|
-
src: '
|
|
279
|
+
src: iconUrl('256x256'),
|
|
248
280
|
mimeType: 'image/png',
|
|
249
281
|
sizes: ['256x256'],
|
|
250
282
|
},
|
|
251
283
|
{
|
|
252
|
-
src: '
|
|
284
|
+
src: iconUrl('512x512'),
|
|
253
285
|
mimeType: 'image/png',
|
|
254
286
|
sizes: ['512x512'],
|
|
255
287
|
},
|
|
256
288
|
{
|
|
257
|
-
src: '
|
|
289
|
+
src: iconUrl('1024x1024'),
|
|
258
290
|
mimeType: 'image/png',
|
|
259
291
|
sizes: ['1024x1024'],
|
|
260
292
|
},
|
package/build/index.js
CHANGED
|
@@ -5,6 +5,20 @@ import { EbaySellerApi } from './api/index.js';
|
|
|
5
5
|
import { getEbayConfig, mcpConfig, validateEnvironmentConfig } from './config/environment.js';
|
|
6
6
|
import { getToolDefinitions, executeTool } from './tools/index.js';
|
|
7
7
|
import { serverLogger, toolLogger, getLogPaths } from './utils/logger.js';
|
|
8
|
+
import { checkForUpdates } from './utils/version.js';
|
|
9
|
+
checkForUpdates({ defer: true });
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
if (args.includes('setup')) {
|
|
12
|
+
try {
|
|
13
|
+
const { runSetup } = await import('./scripts/setup.js');
|
|
14
|
+
await runSetup();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('Setup failed:', error instanceof Error ? error.message : error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* eBay API MCP Server
|
|
10
24
|
* Provides access to eBay APIs through Model Context Protocol
|
package/build/scripts/setup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { dirname, join } from 'path';
|
|
2
|
+
import { dirname, join, resolve } from 'path';
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { homedir, platform } from 'os';
|
|
5
5
|
import axios from 'axios';
|
|
@@ -9,7 +9,9 @@ import { exec } from 'child_process';
|
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import prompts from 'prompts';
|
|
11
11
|
import { getDefaultScopes } from '../config/environment.js';
|
|
12
|
+
import { checkForUpdates } from '../utils/version.js';
|
|
12
13
|
config({ quiet: true });
|
|
14
|
+
checkForUpdates();
|
|
13
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
16
|
const __dirname = dirname(__filename);
|
|
15
17
|
const PROJECT_ROOT = join(__dirname, '../..');
|
|
@@ -853,13 +855,25 @@ async function stepOAuth(state) {
|
|
|
853
855
|
}
|
|
854
856
|
}
|
|
855
857
|
else if (tokenChoice.method === 'manual') {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
858
|
+
// Build the OAuth consent URL (auth2 endpoint)
|
|
859
|
+
const consentDomain = state.environment === 'production'
|
|
860
|
+
? 'https://auth2.ebay.com'
|
|
861
|
+
: 'https://auth2.sandbox.ebay.com';
|
|
859
862
|
// Get scopes from environment config
|
|
860
863
|
const scopes = getDefaultScopes(state.environment);
|
|
861
|
-
|
|
862
|
-
const
|
|
864
|
+
// Build the consent URL parameters
|
|
865
|
+
const consentParams = new URLSearchParams({
|
|
866
|
+
client_id: state.config.EBAY_CLIENT_ID,
|
|
867
|
+
redirect_uri: state.config.EBAY_REDIRECT_URI,
|
|
868
|
+
response_type: 'code',
|
|
869
|
+
scope: scopes.join(' '),
|
|
870
|
+
});
|
|
871
|
+
const consentUrl = `${consentDomain}/oauth2/consents?${consentParams.toString()}`;
|
|
872
|
+
// Build the signin URL that redirects to consent
|
|
873
|
+
const signinDomain = state.environment === 'production'
|
|
874
|
+
? 'https://signin.ebay.com'
|
|
875
|
+
: 'https://signin.sandbox.ebay.com';
|
|
876
|
+
const authUrl = `${signinDomain}/signin?ru=${encodeURIComponent(consentUrl)}&sgfl=oauth2&AppName=${encodeURIComponent(state.config.EBAY_CLIENT_ID)}`;
|
|
863
877
|
console.log('\n ' + ui.bold('OAuth Authorization URL:'));
|
|
864
878
|
console.log(ui.dim(' ' + '─'.repeat(56)));
|
|
865
879
|
console.log(` ${ui.info(authUrl)}`);
|
|
@@ -1275,7 +1289,14 @@ process.on('SIGINT', () => {
|
|
|
1275
1289
|
console.log(ui.warning('\n\n Setup interrupted.\n'));
|
|
1276
1290
|
process.exit(0);
|
|
1277
1291
|
});
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1292
|
+
export async function runSetup() {
|
|
1293
|
+
await main();
|
|
1294
|
+
}
|
|
1295
|
+
const entryPath = process.argv[1] ? resolve(process.argv[1]) : undefined;
|
|
1296
|
+
const modulePath = resolve(fileURLToPath(import.meta.url));
|
|
1297
|
+
if (entryPath && modulePath === entryPath) {
|
|
1298
|
+
runSetup().catch((error) => {
|
|
1299
|
+
console.error(ui.error('\n Setup failed:'), error);
|
|
1300
|
+
process.exit(1);
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches the eBay API Status RSS feed and writes the latest items to docs/API_STATUS.md.
|
|
3
|
+
* Used by the GitHub Action to keep an in-repo snapshot. Run with: npx tsx src/scripts/update-api-status-doc.ts
|
|
4
|
+
*/
|
|
5
|
+
import { writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { getApiStatusFeed } from '../utils/api-status-feed.js';
|
|
8
|
+
const DEFAULT_LIMIT = 15;
|
|
9
|
+
const OUT_PATH = join(process.cwd(), 'docs', 'API_STATUS.md');
|
|
10
|
+
function escapeCell(s) {
|
|
11
|
+
return s.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
12
|
+
}
|
|
13
|
+
function buildMarkdown(items) {
|
|
14
|
+
const lines = [
|
|
15
|
+
'# eBay API Status (latest)',
|
|
16
|
+
'',
|
|
17
|
+
'Auto-updated snapshot from the [eBay API Status RSS feed](https://developer.ebay.com/rss/api-status).',
|
|
18
|
+
'Full list: [developer.ebay.com/support/api-status](https://developer.ebay.com/support/api-status).',
|
|
19
|
+
'',
|
|
20
|
+
`*Last updated: ${new Date().toISOString()}*`,
|
|
21
|
+
'',
|
|
22
|
+
'| Title | API | Site | Status | Last updated | Link |',
|
|
23
|
+
'|-------|-----|------|--------|--------------|------|',
|
|
24
|
+
];
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
const link = item.link ? `[Details](${item.link})` : '';
|
|
27
|
+
lines.push(`| ${escapeCell(item.title)} | ${escapeCell(item.api)} | ${escapeCell(item.site)} | ${escapeCell(item.status)} | ${escapeCell(item.lastUpdated)} | ${link} |`);
|
|
28
|
+
}
|
|
29
|
+
lines.push('', '---', '', '*Generated by [ebay-mcp](https://github.com/YosefHayim/ebay-mcp) API status sync.*');
|
|
30
|
+
return lines.join('\n');
|
|
31
|
+
}
|
|
32
|
+
async function main() {
|
|
33
|
+
const { items, error } = await getApiStatusFeed({ limit: DEFAULT_LIMIT });
|
|
34
|
+
if (error && items.length === 0) {
|
|
35
|
+
throw new Error(`Failed to fetch API status feed: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
const markdown = buildMarkdown(items);
|
|
38
|
+
writeFileSync(OUT_PATH, markdown, 'utf8');
|
|
39
|
+
console.log(`Wrote ${items.length} items to ${OUT_PATH}`);
|
|
40
|
+
}
|
|
41
|
+
main().catch((err) => {
|
|
42
|
+
console.error(err);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
});
|
package/build/server-http.js
CHANGED
|
@@ -12,6 +12,8 @@ import express from 'express';
|
|
|
12
12
|
import helmet from 'helmet';
|
|
13
13
|
import cors from 'cors';
|
|
14
14
|
import { randomUUID } from 'crypto';
|
|
15
|
+
import { dirname, join, resolve } from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
15
17
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
18
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
17
19
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -21,6 +23,7 @@ import { getToolDefinitions, executeTool } from './tools/index.js';
|
|
|
21
23
|
import { TokenVerifier } from './auth/token-verifier.js';
|
|
22
24
|
import { createBearerAuthMiddleware } from './auth/oauth-middleware.js';
|
|
23
25
|
import { createMetadataRouter, getProtectedResourceMetadataUrl } from './auth/oauth-metadata.js';
|
|
26
|
+
import { getVersion } from './utils/version.js';
|
|
24
27
|
// Configuration from environment
|
|
25
28
|
const CONFIG = {
|
|
26
29
|
// Server settings
|
|
@@ -62,6 +65,9 @@ function getAuthServerMetadataUrl() {
|
|
|
62
65
|
*/
|
|
63
66
|
async function createApp() {
|
|
64
67
|
const app = express();
|
|
68
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
69
|
+
const __dirname = dirname(__filename);
|
|
70
|
+
const projectRoot = join(__dirname, '..');
|
|
65
71
|
// Enable CORS
|
|
66
72
|
app.use(cors({
|
|
67
73
|
// TODO: Restrict origin to known clients in production
|
|
@@ -84,6 +90,9 @@ async function createApp() {
|
|
|
84
90
|
});
|
|
85
91
|
// Server URL
|
|
86
92
|
const serverUrl = `http://${CONFIG.host}:${CONFIG.port}`;
|
|
93
|
+
const iconBaseUrl = `${serverUrl}/icons`;
|
|
94
|
+
// Static assets (icons)
|
|
95
|
+
app.use('/icons', express.static(join(projectRoot, 'public', 'icons')));
|
|
87
96
|
// Get eBay configuration for metadata
|
|
88
97
|
const ebayConfig = getEbayConfig();
|
|
89
98
|
// Add OAuth metadata endpoints
|
|
@@ -140,47 +149,55 @@ async function createApp() {
|
|
|
140
149
|
/**
|
|
141
150
|
* Create a new MCP server instance
|
|
142
151
|
*/
|
|
143
|
-
function createMcpServer() {
|
|
152
|
+
async function createMcpServer() {
|
|
144
153
|
const ebayConfig = getEbayConfig();
|
|
145
154
|
const api = new EbaySellerApi(ebayConfig);
|
|
155
|
+
try {
|
|
156
|
+
await api.initialize();
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
160
|
+
console.error(`Failed to initialize eBay API client: ${message}`);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
146
163
|
const server = new McpServer({
|
|
147
164
|
name: 'ebay-mcp',
|
|
148
|
-
version:
|
|
165
|
+
version: getVersion(),
|
|
149
166
|
title: 'eBay API MCP Server',
|
|
150
|
-
websiteUrl: 'https://
|
|
167
|
+
websiteUrl: 'https://github.com/YosefHayim/ebay-mcp',
|
|
151
168
|
icons: [
|
|
152
169
|
{
|
|
153
|
-
src:
|
|
170
|
+
src: `${iconBaseUrl}/16x16.png`,
|
|
154
171
|
mimeType: 'image/png',
|
|
155
172
|
sizes: ['16x16'],
|
|
156
173
|
},
|
|
157
174
|
{
|
|
158
|
-
src:
|
|
175
|
+
src: `${iconBaseUrl}/32x32.png`,
|
|
159
176
|
mimeType: 'image/png',
|
|
160
177
|
sizes: ['32x32'],
|
|
161
178
|
},
|
|
162
179
|
{
|
|
163
|
-
src:
|
|
180
|
+
src: `${iconBaseUrl}/48x48.png`,
|
|
164
181
|
mimeType: 'image/png',
|
|
165
182
|
sizes: ['48x48'],
|
|
166
183
|
},
|
|
167
184
|
{
|
|
168
|
-
src:
|
|
185
|
+
src: `${iconBaseUrl}/128x128.png`,
|
|
169
186
|
mimeType: 'image/png',
|
|
170
187
|
sizes: ['128x128'],
|
|
171
188
|
},
|
|
172
189
|
{
|
|
173
|
-
src:
|
|
190
|
+
src: `${iconBaseUrl}/256x256.png`,
|
|
174
191
|
mimeType: 'image/png',
|
|
175
192
|
sizes: ['256x256'],
|
|
176
193
|
},
|
|
177
194
|
{
|
|
178
|
-
src:
|
|
195
|
+
src: `${iconBaseUrl}/512x512.png`,
|
|
179
196
|
mimeType: 'image/png',
|
|
180
197
|
sizes: ['512x512'],
|
|
181
198
|
},
|
|
182
199
|
{
|
|
183
|
-
src:
|
|
200
|
+
src: `${iconBaseUrl}/1024x1024.png`,
|
|
184
201
|
mimeType: 'image/png',
|
|
185
202
|
sizes: ['1024x1024'],
|
|
186
203
|
},
|
|
@@ -246,7 +263,7 @@ async function createApp() {
|
|
|
246
263
|
console.log(`MCP session closed: ${transport.sessionId}`);
|
|
247
264
|
}
|
|
248
265
|
};
|
|
249
|
-
const server = createMcpServer();
|
|
266
|
+
const server = await createMcpServer();
|
|
250
267
|
await server.connect(transport);
|
|
251
268
|
}
|
|
252
269
|
else {
|
|
@@ -338,7 +355,7 @@ async function main() {
|
|
|
338
355
|
}
|
|
339
356
|
else {
|
|
340
357
|
console.log('Authorization is DISABLED');
|
|
341
|
-
console.log('Set OAUTH_ENABLED=true to enable OAuth protection');
|
|
358
|
+
console.log('Set OAUTH_ENABLED=true (or remove OAUTH_ENABLED=false) to enable OAuth protection');
|
|
342
359
|
}
|
|
343
360
|
});
|
|
344
361
|
// Graceful shutdown
|
|
@@ -356,6 +373,8 @@ async function main() {
|
|
|
356
373
|
}
|
|
357
374
|
}
|
|
358
375
|
// Start server if run directly
|
|
359
|
-
|
|
376
|
+
const entryPath = process.argv[1] ? resolve(process.argv[1]) : undefined;
|
|
377
|
+
const modulePath = resolve(fileURLToPath(import.meta.url));
|
|
378
|
+
if (entryPath && modulePath === entryPath) {
|
|
360
379
|
await main();
|
|
361
380
|
}
|
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const developerTools = [
|
|
3
|
+
{
|
|
4
|
+
name: 'ebay_get_api_status',
|
|
5
|
+
description: 'Get the latest eBay API status and incidents from the official RSS feed. Returns recent issues, fixes, and outages for eBay APIs (e.g. Trading API, Inventory API, Sandbox). Use when the user asks about API status, outages, or fixes.',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
limit: z
|
|
8
|
+
.number()
|
|
9
|
+
.int()
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(50)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Maximum number of items to return (default 20)'),
|
|
14
|
+
status: z
|
|
15
|
+
.enum(['Resolved', 'Unresolved'])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Filter by status: Resolved or Unresolved'),
|
|
18
|
+
api: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Filter by API name (e.g. "Trading API", "Inventory API", "Sandbox")'),
|
|
22
|
+
},
|
|
23
|
+
outputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
items: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
title: { type: 'string' },
|
|
32
|
+
summary: { type: 'string' },
|
|
33
|
+
link: { type: 'string' },
|
|
34
|
+
api: { type: 'string' },
|
|
35
|
+
site: { type: 'string' },
|
|
36
|
+
status: { type: 'string' },
|
|
37
|
+
lastUpdated: { type: 'string' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
error: { type: 'string' },
|
|
42
|
+
},
|
|
43
|
+
description: 'Latest API status items from eBay developer feed',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
3
46
|
{
|
|
4
47
|
name: 'ebay_get_rate_limits',
|
|
5
48
|
description: 'Get application rate limits for eBay APIs. Returns call quota, remaining calls, and time until reset for each API resource.',
|
package/build/tools/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getOAuthAuthorizationUrl, validateScopes } from '../config/environment.js';
|
|
2
2
|
import { accountTools, analyticsTools, communicationTools, developerTools, fulfillmentTools, inventoryTools, marketingTools, metadataTools, otherApiTools, taxonomyTools, tokenManagementTools, } from '../tools/definitions/index.js';
|
|
3
|
+
import { chatGptTools } from '../tools/tool-definitions.js';
|
|
4
|
+
import { getApiStatusFeed } from '../utils/api-status-feed.js';
|
|
3
5
|
import { convertToTimestamp, validateTokenExpiry } from '../utils/date-converter.js';
|
|
4
6
|
// Import Zod schemas for input validation
|
|
5
7
|
import { getAwaitingFeedbackSchema, getFeedbackRatingSummarySchema, getFeedbackSchema, leaveFeedbackForBuyerSchema, respondToFeedbackSchema, } from '../utils/communication/feedback.js';
|
|
@@ -10,7 +12,9 @@ import { createDestinationSchema, createSubscriptionFilterSchema, createSubscrip
|
|
|
10
12
|
* Get all tool definitions for the MCP server
|
|
11
13
|
*/
|
|
12
14
|
export function getToolDefinitions() {
|
|
15
|
+
const chatConnectorTools = chatGptTools.filter((tool) => tool.name === 'search' || tool.name === 'fetch');
|
|
13
16
|
return [
|
|
17
|
+
...chatConnectorTools,
|
|
14
18
|
...tokenManagementTools,
|
|
15
19
|
...accountTools,
|
|
16
20
|
...inventoryTools,
|
|
@@ -33,14 +37,42 @@ export async function executeTool(api, toolName, args) {
|
|
|
33
37
|
case 'search': {
|
|
34
38
|
// For this example, we'll treat the query as a search for inventory items.
|
|
35
39
|
// A more robust implementation might search across different types of content.
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
40
|
+
const requestedLimitRaw = args.limit;
|
|
41
|
+
const limit = typeof requestedLimitRaw === 'number' && Number.isFinite(requestedLimitRaw)
|
|
42
|
+
? Math.max(Math.floor(requestedLimitRaw), 1)
|
|
43
|
+
: 10;
|
|
44
|
+
const query = args.query?.toLowerCase().trim();
|
|
45
|
+
const pageSize = query ? Math.min(Math.max(limit, 50), 200) : limit;
|
|
46
|
+
const matches = [];
|
|
47
|
+
let offset = 0;
|
|
48
|
+
while (matches.length < limit) {
|
|
49
|
+
const response = await api.inventory.getInventoryItems(pageSize, offset);
|
|
50
|
+
const pageItems = response.inventoryItems ?? [];
|
|
51
|
+
if (pageItems.length === 0) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
// Filter to only include items with valid SKUs (required for getInventoryItem calls)
|
|
55
|
+
const itemsWithSku = pageItems.filter((item) => typeof item.sku === 'string' && item.sku.trim() !== '');
|
|
56
|
+
const filtered = query
|
|
57
|
+
? itemsWithSku.filter((item) => (item.product?.title ?? '').toLowerCase().includes(query))
|
|
58
|
+
: itemsWithSku;
|
|
59
|
+
matches.push(...filtered);
|
|
60
|
+
offset += pageSize;
|
|
61
|
+
const total = response.total;
|
|
62
|
+
if (typeof total === 'number' && offset >= total) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (!query || pageItems.length < pageSize) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const results = matches.slice(0, limit).map((item) => ({
|
|
70
|
+
id: item.sku,
|
|
39
71
|
title: item.product?.title ?? 'No Title',
|
|
40
72
|
// The URL should be a canonical link to the item, which we don't have here.
|
|
41
73
|
// We'll use a placeholder.
|
|
42
74
|
url: `https://www.ebay.com/`, // Placeholder URL
|
|
43
|
-
}))
|
|
75
|
+
}));
|
|
44
76
|
// Format the response as required by the ChatGPT connector spec.
|
|
45
77
|
return {
|
|
46
78
|
content: [
|
|
@@ -207,7 +239,7 @@ export async function executeTool(api, toolName, args) {
|
|
|
207
239
|
refreshExpiry = convertToTimestamp(refreshTokenExpiry);
|
|
208
240
|
}
|
|
209
241
|
// Set tokens (will use defaults if expiry times not provided)
|
|
210
|
-
await api.setUserTokens(accessToken, refreshToken);
|
|
242
|
+
await api.setUserTokens(accessToken, refreshToken, accessExpiry, refreshExpiry);
|
|
211
243
|
// If autoRefresh is enabled, attempt to get a fresh access token
|
|
212
244
|
// (The OAuth client will handle refresh internally if needed)
|
|
213
245
|
if (autoRefresh) {
|
|
@@ -1124,6 +1156,15 @@ export async function executeTool(api, toolName, args) {
|
|
|
1124
1156
|
},
|
|
1125
1157
|
],
|
|
1126
1158
|
};
|
|
1159
|
+
// Developer API - API Status (public RSS feed)
|
|
1160
|
+
case 'ebay_get_api_status': {
|
|
1161
|
+
const result = await getApiStatusFeed({
|
|
1162
|
+
limit: args.limit,
|
|
1163
|
+
status: args.status,
|
|
1164
|
+
api: args.api,
|
|
1165
|
+
});
|
|
1166
|
+
return { items: result.items, ...(result.error && { error: result.error }) };
|
|
1167
|
+
}
|
|
1127
1168
|
// Developer API - Rate Limits
|
|
1128
1169
|
case 'ebay_get_rate_limits':
|
|
1129
1170
|
return await api.developer.getRateLimits(args.apiContext, args.apiName);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches and parses the eBay API Status RSS feed (public, no auth).
|
|
3
|
+
* Used by the ebay_get_api_status MCP tool and optionally by the docs sync script.
|
|
4
|
+
*/
|
|
5
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
const RSS_URL = 'https://developer.ebay.com/rss/api-status';
|
|
8
|
+
function stripHtml(html) {
|
|
9
|
+
return html
|
|
10
|
+
.replace(/<[^>]+>/g, ' ')
|
|
11
|
+
.replace(/\s+/g, ' ')
|
|
12
|
+
.trim();
|
|
13
|
+
}
|
|
14
|
+
function normalizeString(value) {
|
|
15
|
+
if (value == null)
|
|
16
|
+
return '';
|
|
17
|
+
if (typeof value === 'string')
|
|
18
|
+
return value.trim();
|
|
19
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
20
|
+
return String(value).trim();
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
function parseItem(raw) {
|
|
24
|
+
const summary = normalizeString(raw.summary) ||
|
|
25
|
+
stripHtml(normalizeString(raw.description)).slice(0, 300) ||
|
|
26
|
+
'';
|
|
27
|
+
return {
|
|
28
|
+
title: normalizeString(raw.title) || 'Untitled',
|
|
29
|
+
summary: summary || normalizeString(raw.title),
|
|
30
|
+
link: normalizeString(raw.link) || '',
|
|
31
|
+
api: normalizeString(raw.api) || '',
|
|
32
|
+
site: normalizeString(raw.site) || '',
|
|
33
|
+
status: normalizeString(raw.status) || '',
|
|
34
|
+
lastUpdated: normalizeString(raw.lastUpdated) || '',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function ensureArray(value) {
|
|
38
|
+
if (value == null)
|
|
39
|
+
return [];
|
|
40
|
+
return Array.isArray(value) ? value : [value];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fetches the eBay API Status RSS feed, parses it, and returns items
|
|
44
|
+
* optionally filtered by status and API name, limited to `limit` items.
|
|
45
|
+
*/
|
|
46
|
+
export async function getApiStatusFeed(options = {}) {
|
|
47
|
+
const { limit = 20, status: statusFilter, api: apiFilter } = options;
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios.get(RSS_URL, {
|
|
50
|
+
timeout: 15_000,
|
|
51
|
+
responseType: 'text',
|
|
52
|
+
headers: { Accept: 'application/rss+xml, application/xml, text/xml' },
|
|
53
|
+
});
|
|
54
|
+
const xml = response.data;
|
|
55
|
+
const parser = new XMLParser({
|
|
56
|
+
ignoreAttributes: true,
|
|
57
|
+
trimValues: true,
|
|
58
|
+
parseTagValue: false,
|
|
59
|
+
});
|
|
60
|
+
const parsed = parser.parse(xml);
|
|
61
|
+
const channel = parsed?.rss?.channel;
|
|
62
|
+
if (!channel) {
|
|
63
|
+
return { items: [], error: 'RSS feed missing channel' };
|
|
64
|
+
}
|
|
65
|
+
const rawItems = ensureArray(channel.item);
|
|
66
|
+
let items = rawItems.map(parseItem);
|
|
67
|
+
if (statusFilter) {
|
|
68
|
+
items = items.filter((i) => i.status.toLowerCase() === statusFilter.toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
if (apiFilter?.trim()) {
|
|
71
|
+
const needle = apiFilter.trim().toLowerCase();
|
|
72
|
+
items = items.filter((i) => i.api.toLowerCase().includes(needle));
|
|
73
|
+
}
|
|
74
|
+
items = items.slice(0, Math.min(limit, 50));
|
|
75
|
+
return { items };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const message = axios.isAxiosError(err) && err.response?.status
|
|
79
|
+
? `Feed unavailable (HTTP ${err.response.status})`
|
|
80
|
+
: err instanceof Error
|
|
81
|
+
? err.message
|
|
82
|
+
: 'Failed to fetch API status feed';
|
|
83
|
+
return { items: [], error: message };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import updateNotifier from 'update-notifier';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const PACKAGE_JSON_PATH = join(__dirname, '../../package.json');
|
|
8
|
+
let cachedPackageJson = null;
|
|
9
|
+
export function getPackageJson() {
|
|
10
|
+
if (cachedPackageJson) {
|
|
11
|
+
return cachedPackageJson;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(PACKAGE_JSON_PATH, 'utf-8');
|
|
15
|
+
cachedPackageJson = JSON.parse(content);
|
|
16
|
+
return cachedPackageJson;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { name: 'ebay-mcp', version: '0.0.0' };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function getVersion() {
|
|
23
|
+
return getPackageJson().version;
|
|
24
|
+
}
|
|
25
|
+
export function getPackageName() {
|
|
26
|
+
return getPackageJson().name;
|
|
27
|
+
}
|
|
28
|
+
const ONE_DAY_MS = 1000 * 60 * 60 * 24;
|
|
29
|
+
export function checkForUpdates(options = {}) {
|
|
30
|
+
const pkg = getPackageJson();
|
|
31
|
+
const notifier = updateNotifier({
|
|
32
|
+
pkg,
|
|
33
|
+
updateCheckInterval: ONE_DAY_MS,
|
|
34
|
+
});
|
|
35
|
+
notifier.notify({
|
|
36
|
+
isGlobal: true,
|
|
37
|
+
defer: options.defer ?? false,
|
|
38
|
+
message: 'Update available {currentVersion} → {latestVersion}\n' + 'Run {updateCommand} to update',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function getUpdateInfo() {
|
|
42
|
+
const pkg = getPackageJson();
|
|
43
|
+
const notifier = updateNotifier({
|
|
44
|
+
pkg,
|
|
45
|
+
updateCheckInterval: 0,
|
|
46
|
+
});
|
|
47
|
+
await notifier.fetchInfo();
|
|
48
|
+
if (notifier.update && notifier.update.latest !== pkg.version) {
|
|
49
|
+
return {
|
|
50
|
+
current: pkg.version,
|
|
51
|
+
latest: notifier.update.latest,
|
|
52
|
+
name: pkg.name,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebay-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Local MCP server for eBay APIs - provides access to eBay developer functionality through MCP (Model Context Protocol)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"build/**/*.js",
|
|
43
43
|
"build/index.d.ts",
|
|
44
44
|
"build/server-http.d.ts",
|
|
45
|
+
"public/icons/*.png",
|
|
45
46
|
"README.md"
|
|
46
47
|
],
|
|
47
48
|
"repository": {
|
|
@@ -59,10 +60,12 @@
|
|
|
59
60
|
"cors": "^2.8.5",
|
|
60
61
|
"dotenv": "^17.2.3",
|
|
61
62
|
"express": "^5.1.0",
|
|
63
|
+
"fast-xml-parser": "^5.3.4",
|
|
62
64
|
"helmet": "^8.1.0",
|
|
63
65
|
"jose": "^6.1.2",
|
|
64
66
|
"jsonwebtoken": "^9.0.2",
|
|
65
67
|
"prompts": "^2.4.2",
|
|
68
|
+
"update-notifier": "^7.3.1",
|
|
66
69
|
"winston": "^3.19.0",
|
|
67
70
|
"zod": "^3.23.8",
|
|
68
71
|
"zod-to-json-schema": "^3.24.1"
|
|
@@ -75,6 +78,7 @@
|
|
|
75
78
|
"@types/node": "^24.10.1",
|
|
76
79
|
"@types/prompts": "^2.4.9",
|
|
77
80
|
"@types/supertest": "^6.0.3",
|
|
81
|
+
"@types/update-notifier": "^6.0.8",
|
|
78
82
|
"@vitest/coverage-v8": "^4.0.13",
|
|
79
83
|
"@vitest/ui": "^4.0.8",
|
|
80
84
|
"eslint": "^9.39.1",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|