ebay-mcp-remote-edition 2.0.14 → 2.0.16
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/build/auth/kv-store.js +3 -1
- package/build/auth/multi-user-store.js +31 -0
- package/build/server-http.js +159 -8
- package/package.json +1 -1
package/build/auth/kv-store.js
CHANGED
|
@@ -88,7 +88,9 @@ export class CloudflareKVStore {
|
|
|
88
88
|
await this.client.put(`/values/${encodeURIComponent(key)}`, JSON.stringify(value), { params });
|
|
89
89
|
// Keep the in-memory cache consistent with what we wrote.
|
|
90
90
|
// If the KV entry has its own TTL, honour it for the cache as well.
|
|
91
|
-
const cacheTtl = expirationTtl
|
|
91
|
+
const cacheTtl = expirationTtl
|
|
92
|
+
? Math.min(expirationTtl * 1_000, this.cacheTtlMs)
|
|
93
|
+
: this.cacheTtlMs;
|
|
92
94
|
this.cache.set(key, { value, expiresAt: Date.now() + cacheTtl });
|
|
93
95
|
}
|
|
94
96
|
async delete(key) {
|
|
@@ -118,6 +118,37 @@ export class MultiUserAuthStore {
|
|
|
118
118
|
await this.kv.put(`client:${clientId}`, record);
|
|
119
119
|
return record;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Upserts a client record using a **caller-supplied** `clientId`.
|
|
123
|
+
*
|
|
124
|
+
* Used by the `/authorize` endpoint to auto-register trusted desktop MCP
|
|
125
|
+
* clients (VS Code, Cursor, Windsurf, localhost loopback) that arrive at
|
|
126
|
+
* `/authorize` without a prior `/register` call (e.g. because the in-memory
|
|
127
|
+
* registration was lost between requests, or the client drives `/authorize`
|
|
128
|
+
* directly).
|
|
129
|
+
*
|
|
130
|
+
* An existing record for `clientId` is overwritten only if the supplied
|
|
131
|
+
* `redirectUri` is not already listed (additive merge otherwise).
|
|
132
|
+
*/
|
|
133
|
+
async registerClientWithId(clientId, redirectUris, clientName) {
|
|
134
|
+
const existing = await this.kv.get(`client:${clientId}`);
|
|
135
|
+
const now = new Date().toISOString();
|
|
136
|
+
if (existing) {
|
|
137
|
+
// Merge any new redirect URIs into the existing record
|
|
138
|
+
const merged = Array.from(new Set([...existing.redirectUris, ...redirectUris]));
|
|
139
|
+
const updated = { ...existing, redirectUris: merged };
|
|
140
|
+
await this.kv.put(`client:${clientId}`, updated);
|
|
141
|
+
return updated;
|
|
142
|
+
}
|
|
143
|
+
const record = {
|
|
144
|
+
clientId,
|
|
145
|
+
redirectUris,
|
|
146
|
+
clientName,
|
|
147
|
+
createdAt: now,
|
|
148
|
+
};
|
|
149
|
+
await this.kv.put(`client:${clientId}`, record);
|
|
150
|
+
return record;
|
|
151
|
+
}
|
|
121
152
|
async getClient(clientId) {
|
|
122
153
|
return await this.kv.get(`client:${clientId}`);
|
|
123
154
|
}
|
package/build/server-http.js
CHANGED
|
@@ -30,10 +30,10 @@ function getServerBaseUrl() {
|
|
|
30
30
|
}
|
|
31
31
|
function htmlEscape(value) {
|
|
32
32
|
return value
|
|
33
|
-
.replace(/&/g, '&
|
|
34
|
-
.replace(/</g, '
|
|
35
|
-
.replace(/>/g, '
|
|
36
|
-
.replace(/"/g, '
|
|
33
|
+
.replace(/&/g, '&')
|
|
34
|
+
.replace(/</g, '<')
|
|
35
|
+
.replace(/>/g, '>')
|
|
36
|
+
.replace(/"/g, '"')
|
|
37
37
|
.replace(/'/g, ''');
|
|
38
38
|
}
|
|
39
39
|
function requireAdmin(req, res, next) {
|
|
@@ -61,6 +61,36 @@ function requireOauthStartKey(req, res, next) {
|
|
|
61
61
|
}
|
|
62
62
|
next();
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns true when a redirect URI belongs to a well-known desktop / IDE
|
|
66
|
+
* MCP client (VS Code, Cursor, Windsurf) or a localhost loopback.
|
|
67
|
+
*
|
|
68
|
+
* These clients drive the authorize flow directly and cannot always guarantee
|
|
69
|
+
* that their /register request was persisted before /authorize is called.
|
|
70
|
+
* We allow them to self-register on the fly so the flow doesn't hard-fail on
|
|
71
|
+
* a missing client_id when the user's eBay app credentials are already in env.
|
|
72
|
+
*/
|
|
73
|
+
function isTrustedDesktopRedirectUri(redirectUri) {
|
|
74
|
+
try {
|
|
75
|
+
const u = new URL(redirectUri);
|
|
76
|
+
// Desktop IDE callback schemes
|
|
77
|
+
if (u.protocol === 'vscode:' ||
|
|
78
|
+
u.protocol === 'cursor:' ||
|
|
79
|
+
u.protocol === 'windsurf:' ||
|
|
80
|
+
u.protocol === 'claude:') {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// Localhost loopback (any port)
|
|
84
|
+
if ((u.protocol === 'http:' || u.protocol === 'https:') &&
|
|
85
|
+
(u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '::1')) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Malformed URI – treat as untrusted
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
64
94
|
async function createUserScopedApi(userId, environment) {
|
|
65
95
|
const api = new EbaySellerApi(getEbayConfig(environment), { userId, environment });
|
|
66
96
|
await api.initialize();
|
|
@@ -132,6 +162,10 @@ function createApp() {
|
|
|
132
162
|
}
|
|
133
163
|
const uris = redirectUris;
|
|
134
164
|
const client = await authStore.registerClient(uris, clientName);
|
|
165
|
+
serverLogger.info('[register] MCP client registered', {
|
|
166
|
+
clientId: client.clientId,
|
|
167
|
+
redirectUris: client.redirectUris,
|
|
168
|
+
});
|
|
135
169
|
res.status(201).json({
|
|
136
170
|
client_id: client.clientId,
|
|
137
171
|
redirect_uris: client.redirectUris,
|
|
@@ -151,22 +185,51 @@ function createApp() {
|
|
|
151
185
|
const q = req.query;
|
|
152
186
|
const { client_id: clientId, redirect_uri: redirectUri, response_type: responseType, state: mcpState, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod, } = q;
|
|
153
187
|
const environment = q.env === 'sandbox' || q.env === 'production' ? q.env : getConfiguredEnvironment();
|
|
188
|
+
serverLogger.info('[authorize] Request received', {
|
|
189
|
+
clientId,
|
|
190
|
+
redirectUri,
|
|
191
|
+
responseType,
|
|
192
|
+
hasPkce: !!codeChallenge,
|
|
193
|
+
pkceMethod: codeChallengeMethod,
|
|
194
|
+
environment,
|
|
195
|
+
hasMcpState: !!mcpState,
|
|
196
|
+
});
|
|
154
197
|
if (responseType !== 'code') {
|
|
198
|
+
serverLogger.warn('[authorize] Rejected: unsupported_response_type', { responseType });
|
|
155
199
|
res.status(400).json({ error: 'unsupported_response_type' });
|
|
156
200
|
return;
|
|
157
201
|
}
|
|
158
202
|
if (!clientId) {
|
|
203
|
+
serverLogger.warn('[authorize] Rejected: missing client_id');
|
|
159
204
|
res
|
|
160
205
|
.status(400)
|
|
161
206
|
.json({ error: 'invalid_request', error_description: 'client_id is required' });
|
|
162
207
|
return;
|
|
163
208
|
}
|
|
164
|
-
|
|
209
|
+
// Look up the MCP client. If unknown, auto-register it for trusted desktop
|
|
210
|
+
// redirect URIs (VS Code, Cursor, Windsurf, localhost) so that the flow
|
|
211
|
+
// continues even when /register state was not persisted (e.g. memory backend)
|
|
212
|
+
// or when the IDE drives /authorize directly without a prior /register call.
|
|
213
|
+
let client = await authStore.getClient(clientId);
|
|
165
214
|
if (!client) {
|
|
166
|
-
|
|
167
|
-
|
|
215
|
+
if (redirectUri && isTrustedDesktopRedirectUri(redirectUri)) {
|
|
216
|
+
serverLogger.info('[authorize] Auto-registering trusted desktop MCP client (client_id not in store)', { clientId, redirectUri });
|
|
217
|
+
client = await authStore.registerClientWithId(clientId, [redirectUri]);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
serverLogger.warn('[authorize] Rejected: unknown client_id (not a trusted desktop URI)', {
|
|
221
|
+
clientId,
|
|
222
|
+
redirectUri,
|
|
223
|
+
});
|
|
224
|
+
res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
168
227
|
}
|
|
169
228
|
if (!redirectUri || !client.redirectUris.includes(redirectUri)) {
|
|
229
|
+
serverLogger.warn('[authorize] Rejected: redirect_uri mismatch', {
|
|
230
|
+
provided: redirectUri,
|
|
231
|
+
registered: client.redirectUris,
|
|
232
|
+
});
|
|
170
233
|
res.status(400).json({
|
|
171
234
|
error: 'invalid_request',
|
|
172
235
|
error_description: 'redirect_uri not registered for this client',
|
|
@@ -174,6 +237,10 @@ function createApp() {
|
|
|
174
237
|
return;
|
|
175
238
|
}
|
|
176
239
|
if (!codeChallenge || codeChallengeMethod !== 'S256') {
|
|
240
|
+
serverLogger.warn('[authorize] Rejected: missing or invalid PKCE', {
|
|
241
|
+
hasPkce: !!codeChallenge,
|
|
242
|
+
method: codeChallengeMethod,
|
|
243
|
+
});
|
|
177
244
|
res.status(400).json({
|
|
178
245
|
error: 'invalid_request',
|
|
179
246
|
error_description: 'PKCE with S256 code_challenge is required',
|
|
@@ -182,6 +249,12 @@ function createApp() {
|
|
|
182
249
|
}
|
|
183
250
|
const ebayConfig = getEbayConfig(environment);
|
|
184
251
|
if (!ebayConfig.clientId || !ebayConfig.clientSecret || !ebayConfig.redirectUri) {
|
|
252
|
+
serverLogger.error('[authorize] eBay app credentials missing in env', {
|
|
253
|
+
hasClientId: !!ebayConfig.clientId,
|
|
254
|
+
hasClientSecret: !!ebayConfig.clientSecret,
|
|
255
|
+
hasRedirectUri: !!ebayConfig.redirectUri,
|
|
256
|
+
environment,
|
|
257
|
+
});
|
|
185
258
|
res.status(500).json({
|
|
186
259
|
error: 'server_error',
|
|
187
260
|
error_description: `Missing eBay configuration for ${environment}`,
|
|
@@ -196,9 +269,17 @@ function createApp() {
|
|
|
196
269
|
mcpCodeChallengeMethod: codeChallengeMethod,
|
|
197
270
|
});
|
|
198
271
|
const oauthUrl = getOAuthAuthorizationUrl(ebayConfig.clientId, ebayConfig.redirectUri, environment, getHostedOauthScopes(environment), undefined, stateRecord.state);
|
|
272
|
+
serverLogger.info('[authorize] Redirecting to eBay OAuth', {
|
|
273
|
+
state: stateRecord.state,
|
|
274
|
+
environment,
|
|
275
|
+
});
|
|
199
276
|
res.redirect(oauthUrl);
|
|
200
277
|
}
|
|
201
278
|
catch (error) {
|
|
279
|
+
serverLogger.error('[authorize] Unhandled error', {
|
|
280
|
+
error: error instanceof Error ? error.message : String(error),
|
|
281
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
282
|
+
});
|
|
202
283
|
res.status(500).json({
|
|
203
284
|
error: 'server_error',
|
|
204
285
|
error_description: error instanceof Error ? error.message : String(error),
|
|
@@ -210,9 +291,14 @@ function createApp() {
|
|
|
210
291
|
* Exchanges a short-lived MCP authorization code (+ PKCE verifier) for a session token.
|
|
211
292
|
*/
|
|
212
293
|
app.post('/token', async (req, res) => {
|
|
294
|
+
serverLogger.info('[token] Request received', {
|
|
295
|
+
contentType: req.headers['content-type'],
|
|
296
|
+
hasBody: !!req.body,
|
|
297
|
+
});
|
|
213
298
|
// Body may be form-encoded (RFC 6749 §4.1.3) or JSON — both are parsed by middleware.
|
|
214
299
|
// Guard against unparsed bodies (missing Content-Type header etc.)
|
|
215
300
|
if (!req.body || typeof req.body !== 'object') {
|
|
301
|
+
serverLogger.warn('[token] Rejected: missing or unparseable body');
|
|
216
302
|
res.status(400).json({
|
|
217
303
|
error: 'invalid_request',
|
|
218
304
|
error_description: 'Request body is missing or unparseable. Use application/x-www-form-urlencoded or application/json.',
|
|
@@ -221,31 +307,58 @@ function createApp() {
|
|
|
221
307
|
}
|
|
222
308
|
const body = req.body;
|
|
223
309
|
const { grant_type: grantType, code, redirect_uri: redirectUri, client_id: clientId, code_verifier: codeVerifier, } = body;
|
|
310
|
+
serverLogger.info('[token] Parsed fields', {
|
|
311
|
+
grantType,
|
|
312
|
+
hasCode: !!code,
|
|
313
|
+
redirectUri,
|
|
314
|
+
clientId,
|
|
315
|
+
hasCodeVerifier: !!codeVerifier,
|
|
316
|
+
});
|
|
224
317
|
if (grantType !== 'authorization_code') {
|
|
318
|
+
serverLogger.warn('[token] Rejected: unsupported_grant_type', { grantType });
|
|
225
319
|
res.status(400).json({ error: 'unsupported_grant_type' });
|
|
226
320
|
return;
|
|
227
321
|
}
|
|
228
322
|
if (!code) {
|
|
323
|
+
serverLogger.warn('[token] Rejected: missing code');
|
|
229
324
|
res.status(400).json({ error: 'invalid_request', error_description: 'code is required' });
|
|
230
325
|
return;
|
|
231
326
|
}
|
|
232
327
|
const authCode = await authStore.consumeAuthCode(code);
|
|
233
328
|
if (!authCode) {
|
|
329
|
+
serverLogger.warn('[token] Rejected: invalid or expired authorization code', {
|
|
330
|
+
codePrefix: code.substring(0, 8),
|
|
331
|
+
});
|
|
234
332
|
res.status(400).json({
|
|
235
333
|
error: 'invalid_grant',
|
|
236
334
|
error_description: 'Invalid or expired authorization code',
|
|
237
335
|
});
|
|
238
336
|
return;
|
|
239
337
|
}
|
|
338
|
+
serverLogger.info('[token] Auth code found', {
|
|
339
|
+
storedClientId: authCode.clientId,
|
|
340
|
+
providedClientId: clientId,
|
|
341
|
+
storedRedirectUri: authCode.redirectUri,
|
|
342
|
+
providedRedirectUri: redirectUri,
|
|
343
|
+
});
|
|
240
344
|
if (authCode.clientId !== clientId) {
|
|
345
|
+
serverLogger.warn('[token] Rejected: client_id mismatch', {
|
|
346
|
+
stored: authCode.clientId,
|
|
347
|
+
provided: clientId,
|
|
348
|
+
});
|
|
241
349
|
res.status(400).json({ error: 'invalid_client', error_description: 'client_id mismatch' });
|
|
242
350
|
return;
|
|
243
351
|
}
|
|
244
352
|
if (authCode.redirectUri !== redirectUri) {
|
|
353
|
+
serverLogger.warn('[token] Rejected: redirect_uri mismatch', {
|
|
354
|
+
stored: authCode.redirectUri,
|
|
355
|
+
provided: redirectUri,
|
|
356
|
+
});
|
|
245
357
|
res.status(400).json({ error: 'invalid_grant', error_description: 'redirect_uri mismatch' });
|
|
246
358
|
return;
|
|
247
359
|
}
|
|
248
360
|
if (!codeVerifier) {
|
|
361
|
+
serverLogger.warn('[token] Rejected: missing code_verifier');
|
|
249
362
|
res
|
|
250
363
|
.status(400)
|
|
251
364
|
.json({ error: 'invalid_request', error_description: 'code_verifier is required' });
|
|
@@ -254,12 +367,21 @@ function createApp() {
|
|
|
254
367
|
// Verify PKCE S256: BASE64URL(SHA256(code_verifier)) must equal code_challenge
|
|
255
368
|
const expectedChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
|
|
256
369
|
if (expectedChallenge !== authCode.codeChallenge) {
|
|
370
|
+
serverLogger.warn('[token] Rejected: PKCE verification failed', {
|
|
371
|
+
expected: authCode.codeChallenge,
|
|
372
|
+
computed: expectedChallenge,
|
|
373
|
+
});
|
|
257
374
|
res
|
|
258
375
|
.status(400)
|
|
259
376
|
.json({ error: 'invalid_grant', error_description: 'PKCE verification failed' });
|
|
260
377
|
return;
|
|
261
378
|
}
|
|
262
379
|
const session = await authStore.createSession(authCode.userId, authCode.environment);
|
|
380
|
+
serverLogger.info('[token] Session created, issuing access token', {
|
|
381
|
+
userId: authCode.userId,
|
|
382
|
+
environment: authCode.environment,
|
|
383
|
+
sessionTokenPrefix: session.sessionToken.substring(0, 8),
|
|
384
|
+
});
|
|
263
385
|
res.json({
|
|
264
386
|
access_token: session.sessionToken,
|
|
265
387
|
token_type: 'bearer',
|
|
@@ -292,13 +414,23 @@ function createApp() {
|
|
|
292
414
|
const envFromQuery = typeof req.query.env === 'string' ? req.query.env : undefined;
|
|
293
415
|
const oauthError = typeof req.query.error === 'string' ? req.query.error : undefined;
|
|
294
416
|
const errorDescription = typeof req.query.error_description === 'string' ? req.query.error_description : undefined;
|
|
417
|
+
serverLogger.info('[oauth/callback] Received', {
|
|
418
|
+
hasCode: !!code,
|
|
419
|
+
hasState: !!state,
|
|
420
|
+
oauthError,
|
|
421
|
+
});
|
|
295
422
|
if (oauthError) {
|
|
423
|
+
serverLogger.warn('[oauth/callback] eBay returned OAuth error', {
|
|
424
|
+
oauthError,
|
|
425
|
+
errorDescription,
|
|
426
|
+
});
|
|
296
427
|
res
|
|
297
428
|
.status(400)
|
|
298
429
|
.send(`<h1>OAuth failed</h1><p>${htmlEscape(errorDescription ?? oauthError)}</p>`);
|
|
299
430
|
return;
|
|
300
431
|
}
|
|
301
432
|
if (!code) {
|
|
433
|
+
serverLogger.warn('[oauth/callback] Missing authorization code');
|
|
302
434
|
res.status(400).send('<h1>Missing authorization code</h1>');
|
|
303
435
|
return;
|
|
304
436
|
}
|
|
@@ -307,10 +439,17 @@ function createApp() {
|
|
|
307
439
|
if (state) {
|
|
308
440
|
stateRecord = await authStore.consumeOAuthState(state);
|
|
309
441
|
if (!stateRecord) {
|
|
442
|
+
serverLogger.warn('[oauth/callback] OAuth state not found or expired', { state });
|
|
310
443
|
res.status(400).send('<h1>Invalid or expired OAuth state</h1>');
|
|
311
444
|
return;
|
|
312
445
|
}
|
|
313
446
|
environment = stateRecord.environment;
|
|
447
|
+
serverLogger.info('[oauth/callback] State resolved', {
|
|
448
|
+
environment,
|
|
449
|
+
isMcpFlow: !!(stateRecord.mcpClientId && stateRecord.mcpRedirectUri),
|
|
450
|
+
mcpClientId: stateRecord.mcpClientId,
|
|
451
|
+
mcpRedirectUri: stateRecord.mcpRedirectUri,
|
|
452
|
+
});
|
|
314
453
|
}
|
|
315
454
|
else {
|
|
316
455
|
environment =
|
|
@@ -322,7 +461,12 @@ function createApp() {
|
|
|
322
461
|
const userId = randomUUID();
|
|
323
462
|
const api = await createUserScopedApi(userId, environment);
|
|
324
463
|
const oauthClient = api.getAuthClient().getOAuthClient();
|
|
464
|
+
serverLogger.info('[oauth/callback] Exchanging code for eBay tokens', { userId });
|
|
325
465
|
const tokenData = await oauthClient.exchangeCodeForToken(code);
|
|
466
|
+
serverLogger.info('[oauth/callback] eBay token exchange successful', {
|
|
467
|
+
userId,
|
|
468
|
+
hasScope: !!tokenData.scope,
|
|
469
|
+
});
|
|
326
470
|
// ── MCP OAuth flow: redirect back to the registered MCP client ─────────
|
|
327
471
|
if (stateRecord?.mcpClientId && stateRecord.mcpRedirectUri && stateRecord.mcpCodeChallenge) {
|
|
328
472
|
const authCodeRecord = await authStore.createAuthCode(stateRecord.mcpClientId, stateRecord.mcpRedirectUri, stateRecord.mcpCodeChallenge, stateRecord.mcpCodeChallengeMethod ?? 'S256', userId, environment);
|
|
@@ -331,15 +475,18 @@ function createApp() {
|
|
|
331
475
|
if (stateRecord.mcpState) {
|
|
332
476
|
redirectUrl.searchParams.set('state', stateRecord.mcpState);
|
|
333
477
|
}
|
|
334
|
-
serverLogger.info('MCP OAuth flow complete, redirecting to client', {
|
|
478
|
+
serverLogger.info('[oauth/callback] MCP OAuth flow complete, redirecting to client', {
|
|
335
479
|
clientId: stateRecord.mcpClientId,
|
|
336
480
|
redirectUri: stateRecord.mcpRedirectUri,
|
|
337
481
|
userId,
|
|
482
|
+
authCodePrefix: authCodeRecord.code.substring(0, 8),
|
|
483
|
+
finalRedirectUrl: redirectUrl.toString().substring(0, 120),
|
|
338
484
|
});
|
|
339
485
|
res.redirect(redirectUrl.toString());
|
|
340
486
|
return;
|
|
341
487
|
}
|
|
342
488
|
// ── End MCP OAuth flow ─────────────────────────────────────────────────
|
|
489
|
+
serverLogger.info('[oauth/callback] Non-MCP flow: creating hosted session', { userId });
|
|
343
490
|
const session = await authStore.createSession(userId, environment);
|
|
344
491
|
res.status(200).send(`<!doctype html>
|
|
345
492
|
<html>
|
|
@@ -403,6 +550,10 @@ function createApp() {
|
|
|
403
550
|
</html>`);
|
|
404
551
|
}
|
|
405
552
|
catch (error) {
|
|
553
|
+
serverLogger.error('[oauth/callback] Unhandled error', {
|
|
554
|
+
error: error instanceof Error ? error.message : String(error),
|
|
555
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
556
|
+
});
|
|
406
557
|
res
|
|
407
558
|
.status(500)
|
|
408
559
|
.send(`<h1>OAuth callback failed</h1><pre>${htmlEscape(error instanceof Error ? error.message : String(error))}</pre>`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebay-mcp-remote-edition",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"description": "Remote + 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",
|