ebay-mcp-remote-edition 3.1.2 → 3.3.0

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.
@@ -1,6 +1,7 @@
1
1
  import express from 'express';
2
2
  import helmet from 'helmet';
3
3
  import cors from 'cors';
4
+ import axios from 'axios';
4
5
  import { createServer as createHttpsServer } from 'https';
5
6
  import { readFileSync } from 'fs';
6
7
  import { dirname, join, resolve } from 'path';
@@ -10,8 +11,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
11
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
11
12
  import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
12
13
  import { EbaySellerApi } from './api/index.js';
13
- import { getConfiguredEnvironment, getHostedOauthScopes, getEbayConfig, getOAuthAuthorizationUrl, validateCredentialsForEnvironment, ruNameToEnvironment, } from './config/environment.js';
14
- import { getToolDefinitions, executeTool } from './tools/index.js';
14
+ import { getBaseUrl, getConfiguredEnvironment, getHostedOauthScopes, getEbayConfig, getOAuthAuthorizationUrl, getValidationRunnerUserId, validateCredentialsForEnvironment, ruNameToEnvironment, } from './config/environment.js';
15
15
  import { getVersion } from './utils/version.js';
16
16
  import { serverLogger } from './utils/logger.js';
17
17
  import { MultiUserAuthStore } from './auth/multi-user-store.js';
@@ -38,6 +38,48 @@ function htmlEscape(value) {
38
38
  .replace(/"/g, '"')
39
39
  .replace(/'/g, ''');
40
40
  }
41
+ function getValidationIdFromBody(body) {
42
+ if (typeof body === 'object' &&
43
+ body !== null &&
44
+ 'validationId' in body &&
45
+ typeof body.validationId === 'string') {
46
+ return body.validationId;
47
+ }
48
+ return '';
49
+ }
50
+ function getRetryTimestampFromBody(body) {
51
+ if (typeof body === 'object' &&
52
+ body !== null &&
53
+ 'timestamp' in body &&
54
+ typeof body.timestamp === 'string') {
55
+ const parsed = new Date(body.timestamp);
56
+ if (Number.isFinite(parsed.getTime())) {
57
+ return new Date(parsed.getTime() + 30 * 60 * 1000).toISOString();
58
+ }
59
+ }
60
+ return new Date(Date.now() + 30 * 60 * 1000).toISOString();
61
+ }
62
+ function getAxiosFailureDebug(error) {
63
+ if (!axios.isAxiosError(error)) {
64
+ return {
65
+ responseStatus: null,
66
+ responseBodyExcerpt: null,
67
+ };
68
+ }
69
+ const responseStatus = error.response?.status ?? null;
70
+ const rawBody = error.response?.data;
71
+ if (rawBody === undefined) {
72
+ return {
73
+ responseStatus,
74
+ responseBodyExcerpt: null,
75
+ };
76
+ }
77
+ const bodyText = typeof rawBody === 'string' ? rawBody : JSON.stringify(rawBody, null, 2);
78
+ return {
79
+ responseStatus,
80
+ responseBodyExcerpt: bodyText.slice(0, 500),
81
+ };
82
+ }
41
83
  function requireAdmin(req, res, next) {
42
84
  if (!CONFIG.adminApiKey) {
43
85
  res.status(500).json({ error: 'ADMIN_API_KEY is not configured' });
@@ -107,6 +149,7 @@ function createApp() {
107
149
  app.use(express.urlencoded({ extended: false }));
108
150
  app.use(helmet({ xPoweredBy: false }));
109
151
  app.use('/icons', express.static(join(projectRoot, 'public', 'icons')));
152
+ app.use('/callback-copy.js', express.static(join(projectRoot, 'public', 'callback-copy.js')));
110
153
  app.use((req, res, next) => {
111
154
  const start = Date.now();
112
155
  res.on('finish', () => {
@@ -317,9 +360,9 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
317
360
  }
318
361
  }
319
362
  // Step 2: per-env RuName detection.
320
- const sandboxRuName = process.env.EBAY_SANDBOX_RUNAME || process.env.EBAY_SANDBOX_REDIRECT_URI;
321
- const productionRuName = process.env.EBAY_PRODUCTION_RUNAME || process.env.EBAY_PRODUCTION_REDIRECT_URI;
322
- const genericRuName = process.env.EBAY_RUNAME || process.env.EBAY_REDIRECT_URI;
363
+ const sandboxRuName = process.env.EBAY_SANDBOX_RUNAME ?? process.env.EBAY_SANDBOX_REDIRECT_URI;
364
+ const productionRuName = process.env.EBAY_PRODUCTION_RUNAME ?? process.env.EBAY_PRODUCTION_REDIRECT_URI;
365
+ const genericRuName = process.env.EBAY_RUNAME ?? process.env.EBAY_REDIRECT_URI;
323
366
  const sandboxDetected = ruNameToEnvironment(sandboxRuName);
324
367
  const productionDetected = ruNameToEnvironment(productionRuName);
325
368
  if (sandboxDetected && !productionDetected)
@@ -333,6 +376,130 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
333
376
  // Step 4: final fallback.
334
377
  return getConfiguredEnvironment();
335
378
  }
379
+ router.post('/validation/run', requireAdmin, async (req, res) => {
380
+ const environment = resolveEnv(req);
381
+ const validationRunnerUserId = getValidationRunnerUserId(environment);
382
+ if (!validationRunnerUserId) {
383
+ res.status(500).json({
384
+ status: 'error',
385
+ validationId: getValidationIdFromBody(req.body),
386
+ errorCode: 'VALIDATION_USER_NOT_CONFIGURED',
387
+ message: `No validation runner user is configured for ${environment}`,
388
+ retryable: false,
389
+ nextCheckAt: null,
390
+ });
391
+ return;
392
+ }
393
+ const storedTokens = await authStore.getUserTokens(validationRunnerUserId, environment);
394
+ if (!storedTokens?.tokenData) {
395
+ res.status(500).json({
396
+ status: 'error',
397
+ validationId: getValidationIdFromBody(req.body),
398
+ errorCode: 'VALIDATION_USER_TOKENS_MISSING',
399
+ message: `Stored refresh-token-backed credentials were not found for validation user ${validationRunnerUserId} in ${environment}`,
400
+ retryable: false,
401
+ nextCheckAt: null,
402
+ });
403
+ return;
404
+ }
405
+ try {
406
+ const api = await createUserScopedApi(validationRunnerUserId, environment);
407
+ const { runValidation } = await import('./validation/run-validation.js');
408
+ const result = await runValidation(api, req.body);
409
+ if (result.status === 'error') {
410
+ res.status(500).json(result);
411
+ return;
412
+ }
413
+ res.json(result);
414
+ }
415
+ catch (error) {
416
+ res.status(500).json({
417
+ status: 'error',
418
+ validationId: getValidationIdFromBody(req.body),
419
+ errorCode: 'VALIDATION_ROUTE_ERROR',
420
+ message: error instanceof Error ? error.message : String(error),
421
+ retryable: true,
422
+ nextCheckAt: getRetryTimestampFromBody(req.body),
423
+ });
424
+ }
425
+ });
426
+ router.get('/validation/health', requireAdmin, async (req, res) => {
427
+ const environment = resolveEnv(req);
428
+ const validationRunnerUserId = getValidationRunnerUserId(environment);
429
+ const storedTokens = validationRunnerUserId
430
+ ? await authStore.getUserTokens(validationRunnerUserId, environment)
431
+ : null;
432
+ const socialConfig = {
433
+ hasTwitterBearerToken: Boolean(process.env.TWITTER_BEARER_TOKEN?.trim()),
434
+ hasYoutubeApiKey: Boolean(process.env.YOUTUBE_API_KEY?.trim()),
435
+ hasRedditUserAgent: Boolean(process.env.REDDIT_USER_AGENT?.trim()),
436
+ };
437
+ let authenticated = false;
438
+ let authError = null;
439
+ let tokenStatus = null;
440
+ let authDebug = null;
441
+ if (validationRunnerUserId && storedTokens?.tokenData) {
442
+ try {
443
+ const api = await createUserScopedApi(validationRunnerUserId, environment);
444
+ const oauthClient = api.getAuthClient().getOAuthClient();
445
+ const config = api.getAuthClient().getConfig();
446
+ authDebug = {
447
+ ...oauthClient.getAuthDebugInfo(),
448
+ configuredMarketplaceId: config.marketplaceId ?? '',
449
+ configuredContentLanguage: config.contentLanguage ?? '',
450
+ };
451
+ await api.getAuthClient().getOAuthClient().getAccessToken();
452
+ authenticated = true;
453
+ tokenStatus = api.getTokenInfo();
454
+ }
455
+ catch (error) {
456
+ authError = error instanceof Error ? error.message : String(error);
457
+ const failureDebug = getAxiosFailureDebug(error);
458
+ authDebug = authDebug
459
+ ? {
460
+ ...authDebug,
461
+ responseStatus: failureDebug.responseStatus,
462
+ responseBodyExcerpt: failureDebug.responseBodyExcerpt,
463
+ }
464
+ : null;
465
+ serverLogger.error('Validation health auth check failed', {
466
+ environment,
467
+ validationRunnerUserId,
468
+ tokenEndpoint: authDebug?.tokenEndpoint ?? null,
469
+ responseStatus: failureDebug.responseStatus,
470
+ responseBodyExcerpt: failureDebug.responseBodyExcerpt,
471
+ authError,
472
+ });
473
+ }
474
+ }
475
+ const healthResponse = {
476
+ status: authenticated ? 'ok' : 'degraded',
477
+ environment,
478
+ validationRunnerUserId,
479
+ hasStoredTokens: !!storedTokens?.tokenData,
480
+ authenticated,
481
+ tokenStatus,
482
+ authDebug,
483
+ providers: {
484
+ ebay: { available: true, implemented: true, confidence: 'medium' },
485
+ social: { available: false, implemented: false, confidence: 'low' },
486
+ chart: { available: false, implemented: false, confidence: 'low' },
487
+ socialConfig,
488
+ },
489
+ ...(authError ? { authError } : {}),
490
+ };
491
+ serverLogger.info('Validation health response emitted', {
492
+ environment,
493
+ path: req.originalUrl,
494
+ status: healthResponse.status,
495
+ version: getVersion(),
496
+ hasSocialConfigAtRoot: Object.prototype.hasOwnProperty.call(healthResponse, 'socialConfig'),
497
+ hasSocialConfigUnderProviders: Object.prototype.hasOwnProperty.call(healthResponse.providers, 'socialConfig'),
498
+ providerKeys: Object.keys(healthResponse.providers),
499
+ socialConfig,
500
+ });
501
+ res.json(healthResponse);
502
+ });
336
503
  // ── RFC 8414 – Authorization Server Metadata ──────────────────────────
337
504
  // For env-scoped routers: endpoints are relative to the env base URL.
338
505
  // For the ROOT router: endpoints are relative to the DEFAULT environment's
@@ -671,6 +838,19 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
671
838
  next();
672
839
  };
673
840
  async function createMcpServer(userId, environment) {
841
+ let getToolDefinitions;
842
+ let executeTool;
843
+ try {
844
+ ({ getToolDefinitions, executeTool } = await import('./tools/index.js'));
845
+ }
846
+ catch (error) {
847
+ serverLogger.error('Failed to import MCP tool registry', {
848
+ userId,
849
+ environment,
850
+ error: error instanceof Error ? error.message : String(error),
851
+ });
852
+ throw error;
853
+ }
674
854
  const api = await createUserScopedApi(userId, environment);
675
855
  const server = new McpServer({
676
856
  name: 'ebay-mcp-remote-edition',
@@ -685,23 +865,36 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
685
865
  });
686
866
  const tools = getToolDefinitions();
687
867
  for (const toolDef of tools) {
688
- server.registerTool(toolDef.name, { description: toolDef.description, inputSchema: toolDef.inputSchema }, async (args) => {
689
- try {
690
- const result = await executeTool(api, toolDef.name, args);
691
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
692
- }
693
- catch (error) {
694
- return {
695
- content: [
696
- {
697
- type: 'text',
698
- text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2),
699
- },
700
- ],
701
- isError: true,
702
- };
703
- }
704
- });
868
+ try {
869
+ server.registerTool(toolDef.name, { description: toolDef.description, inputSchema: toolDef.inputSchema }, async (args) => {
870
+ try {
871
+ const result = await executeTool(api, toolDef.name, args);
872
+ return {
873
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
874
+ };
875
+ }
876
+ catch (error) {
877
+ return {
878
+ content: [
879
+ {
880
+ type: 'text',
881
+ text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2),
882
+ },
883
+ ],
884
+ isError: true,
885
+ };
886
+ }
887
+ });
888
+ }
889
+ catch (error) {
890
+ serverLogger.error('Failed to register MCP tool', {
891
+ toolName: toolDef.name,
892
+ userId,
893
+ environment,
894
+ error: error instanceof Error ? error.message : String(error),
895
+ });
896
+ throw error;
897
+ }
705
898
  }
706
899
  return server;
707
900
  }
@@ -717,26 +910,51 @@ function mountEnvRouter(hardcodedEnv, serverUrl, iconBaseUrl) {
717
910
  transport = transports.get(sessionId);
718
911
  }
719
912
  else if (!sessionId && isInitializeRequest(req.body)) {
720
- transport = new StreamableHTTPServerTransport({
721
- sessionIdGenerator: () => randomUUID(),
722
- onsessioninitialized: (newSessionId) => {
723
- transports.set(newSessionId, transport);
724
- serverLogger.info('New MCP session initialized', {
725
- sessionId: newSessionId,
726
- userId: userContext.userId,
727
- environment: userContext.environment,
728
- });
729
- },
730
- });
731
- transport.onclose = () => {
732
- if (transport.sessionId) {
733
- transports.delete(transport.sessionId);
734
- }
735
- };
736
- const server = await createMcpServer(userContext.userId, userContext.environment);
737
- await server.connect(transport);
913
+ try {
914
+ transport = new StreamableHTTPServerTransport({
915
+ sessionIdGenerator: () => randomUUID(),
916
+ onsessioninitialized: (newSessionId) => {
917
+ transports.set(newSessionId, transport);
918
+ serverLogger.info('New MCP session initialized', {
919
+ sessionId: newSessionId,
920
+ userId: userContext.userId,
921
+ environment: userContext.environment,
922
+ });
923
+ },
924
+ });
925
+ transport.onclose = () => {
926
+ if (transport.sessionId) {
927
+ transports.delete(transport.sessionId);
928
+ }
929
+ };
930
+ const server = await createMcpServer(userContext.userId, userContext.environment);
931
+ await server.connect(transport);
932
+ }
933
+ catch (error) {
934
+ serverLogger.error('Failed to initialize MCP session', {
935
+ userId: userContext.userId,
936
+ environment: userContext.environment,
937
+ error: error instanceof Error ? error.message : String(error),
938
+ });
939
+ res.status(500).json({
940
+ jsonrpc: '2.0',
941
+ error: {
942
+ code: -32603,
943
+ message: error instanceof Error ? error.message : 'Failed to initialize MCP session',
944
+ },
945
+ id: null,
946
+ });
947
+ return;
948
+ }
738
949
  }
739
950
  else {
951
+ serverLogger.warn('Rejected MCP request without valid transport session', {
952
+ hasSessionId: !!sessionId,
953
+ sessionId,
954
+ isInitialize: isInitializeRequest(req.body),
955
+ userId: userContext.userId,
956
+ environment: userContext.environment,
957
+ });
740
958
  res.status(400).json({
741
959
  jsonrpc: '2.0',
742
960
  error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
@@ -769,7 +987,6 @@ async function handleOAuthCallback(req, res, serverUrl) {
769
987
  try {
770
988
  const code = typeof req.query.code === 'string' ? req.query.code : undefined;
771
989
  const state = typeof req.query.state === 'string' ? req.query.state : undefined;
772
- const envFromQuery = typeof req.query.env === 'string' ? req.query.env : undefined;
773
990
  const oauthError = typeof req.query.error === 'string' ? req.query.error : undefined;
774
991
  const errorDescription = typeof req.query.error_description === 'string' ? req.query.error_description : undefined;
775
992
  serverLogger.info('[oauth/callback] Received', {
@@ -787,33 +1004,37 @@ async function handleOAuthCallback(req, res, serverUrl) {
787
1004
  res.status(400).send('<h1>Missing authorization code</h1>');
788
1005
  return;
789
1006
  }
790
- let environment;
791
- let stateRecord = null;
792
- if (state) {
793
- stateRecord = await authStore.consumeOAuthState(state);
794
- if (!stateRecord) {
795
- serverLogger.warn('[oauth/callback] OAuth state not found or expired', { state });
796
- res.status(400).send('<h1>Invalid or expired OAuth state</h1>');
797
- return;
798
- }
799
- environment = stateRecord.environment;
800
- serverLogger.info('[oauth/callback] State resolved', {
801
- environment,
802
- isMcpFlow: !!(stateRecord.mcpClientId && stateRecord.mcpRedirectUri),
803
- });
1007
+ if (!state) {
1008
+ serverLogger.warn('[oauth/callback] Missing OAuth state in callback');
1009
+ res
1010
+ .status(400)
1011
+ .send("<h1>Missing OAuth state</h1><p>Restart the OAuth flow from this server's /oauth/start or /authorize endpoint.</p>");
1012
+ return;
804
1013
  }
805
- else {
806
- environment =
807
- envFromQuery === 'sandbox' || envFromQuery === 'production'
808
- ? envFromQuery
809
- : getConfiguredEnvironment();
810
- serverLogger.warn('OAuth callback received without state; falling back to configured/query environment', { environment });
1014
+ const stateRecord = await authStore.getOAuthState(state);
1015
+ if (!stateRecord) {
1016
+ serverLogger.warn('[oauth/callback] OAuth state not found or expired', { state });
1017
+ res.status(400).send('<h1>Invalid or expired OAuth state</h1>');
1018
+ return;
811
1019
  }
1020
+ const environment = stateRecord.environment;
1021
+ serverLogger.info('[oauth/callback] State resolved', {
1022
+ environment,
1023
+ isMcpFlow: !!(stateRecord.mcpClientId && stateRecord.mcpRedirectUri),
1024
+ });
1025
+ const ebayConfig = getEbayConfig(environment);
1026
+ serverLogger.info('[oauth/callback] Prepared eBay token exchange', {
1027
+ environment,
1028
+ tokenBaseUrl: getBaseUrl(environment),
1029
+ clientIdPrefix: ebayConfig.clientId ? `${ebayConfig.clientId.slice(0, 12)}...` : '(missing)',
1030
+ ruName: ebayConfig.redirectUri ?? '(missing)',
1031
+ });
812
1032
  const userId = randomUUID();
813
1033
  const api = await createUserScopedApi(userId, environment);
814
1034
  const oauthClient = api.getAuthClient().getOAuthClient();
815
1035
  serverLogger.info('[oauth/callback] Exchanging code for eBay tokens', { userId });
816
1036
  const tokenData = await oauthClient.exchangeCodeForToken(code);
1037
+ await authStore.deleteOAuthState(state);
817
1038
  serverLogger.info('[oauth/callback] eBay token exchange successful', {
818
1039
  userId,
819
1040
  hasScope: !!tokenData.scope,
@@ -901,32 +1122,7 @@ async function handleOAuthCallback(req, res, serverUrl) {
901
1122
  .muted { color: #6b7280; }
902
1123
  a { color: #2563eb; }
903
1124
  </style>
904
- <script>
905
- async function copyText(elementId, statusId) {
906
- const token = document.getElementById(elementId).innerText;
907
- const status = document.getElementById(statusId);
908
- try {
909
- if (navigator.clipboard && window.isSecureContext) {
910
- await navigator.clipboard.writeText(token);
911
- } else {
912
- const temp = document.createElement('textarea');
913
- temp.value = token;
914
- temp.setAttribute('readonly', '');
915
- temp.style.position = 'absolute';
916
- temp.style.left = '-9999px';
917
- document.body.appendChild(temp);
918
- temp.select();
919
- document.execCommand('copy');
920
- document.body.removeChild(temp);
921
- }
922
- status.textContent = 'Copied!';
923
- setTimeout(() => { status.textContent = ''; }, 1800);
924
- } catch (err) {
925
- status.textContent = 'Copy failed — select manually';
926
- setTimeout(() => { status.textContent = ''; }, 2500);
927
- }
928
- }
929
- </script>
1125
+ <script src="/callback-copy.js" defer></script>
930
1126
  </head>
931
1127
  <body>
932
1128
  <h1>eBay account connected ✓</h1>
@@ -936,21 +1132,21 @@ async function handleOAuthCallback(req, res, serverUrl) {
936
1132
  <h2>① Session token — paste as Bearer token in your MCP client</h2>
937
1133
  <div class="card">
938
1134
  <pre id="session-token">${htmlEscape(session.sessionToken)}</pre>
939
- <button class="copy-btn" onclick="copyText('session-token','st-status')">Copy</button><span id="st-status" class="copy-status"></span>
1135
+ <button class="copy-btn" data-copy-source="session-token" data-copy-status="st-status">Copy</button><span id="st-status" class="copy-status"></span>
940
1136
  <p class="muted">Set as <code>EBAY_SESSION_TOKEN</code> in your server env to survive restarts.</p>
941
1137
  </div>
942
1138
 
943
1139
  <h2>② eBay user access token</h2>
944
1140
  <div class="card">
945
1141
  <pre id="access-token">${htmlEscape(tokenData.access_token)}</pre>
946
- <button class="copy-btn" onclick="copyText('access-token','at-status')">Copy</button><span id="at-status" class="copy-status"></span>
1142
+ <button class="copy-btn" data-copy-source="access-token" data-copy-status="at-status">Copy</button><span id="at-status" class="copy-status"></span>
947
1143
  <p class="muted">Set as <code>EBAY_USER_ACCESS_TOKEN</code> in your server env (optional — auto-refreshed from refresh token).</p>
948
1144
  </div>
949
1145
 
950
1146
  <h2>③ eBay user refresh token</h2>
951
1147
  <div class="card">
952
1148
  <pre id="refresh-token">${htmlEscape(tokenData.refresh_token ?? '')}</pre>
953
- <button class="copy-btn" onclick="copyText('refresh-token','rt-status')">Copy</button><span id="rt-status" class="copy-status"></span>
1149
+ <button class="copy-btn" data-copy-source="refresh-token" data-copy-status="rt-status">Copy</button><span id="rt-status" class="copy-status"></span>
954
1150
  <p class="muted">Set as <code>EBAY_USER_REFRESH_TOKEN</code> in your server env. The server uses this to keep the access token fresh automatically.</p>
955
1151
  </div>
956
1152
 
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+ export const chatGptTools = [
3
+ {
4
+ name: 'search',
5
+ description: 'Search for eBay inventory items',
6
+ inputSchema: {
7
+ query: z.string().describe('Search query'),
8
+ limit: z.number().optional().describe('Maximum number of results'),
9
+ },
10
+ title: 'Search',
11
+ outputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ success: { type: 'boolean' },
15
+ data: { type: 'object' },
16
+ },
17
+ },
18
+ annotations: {
19
+ title: 'Search',
20
+ readOnlyHint: true,
21
+ },
22
+ _meta: {
23
+ category: 'chat',
24
+ version: '1.0.0',
25
+ },
26
+ },
27
+ {
28
+ name: 'fetch',
29
+ description: 'Fetch a specific eBay inventory item by SKU',
30
+ inputSchema: {
31
+ id: z.string().describe('Item SKU'),
32
+ },
33
+ title: 'Fetch',
34
+ outputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ success: { type: 'boolean' },
38
+ data: { type: 'object' },
39
+ },
40
+ },
41
+ annotations: {
42
+ title: 'Fetch',
43
+ readOnlyHint: true,
44
+ },
45
+ _meta: {
46
+ category: 'chat',
47
+ version: '1.0.0',
48
+ },
49
+ },
50
+ ];
@@ -1,6 +1,6 @@
1
1
  import { getOAuthAuthorizationUrl, validateScopes } from '../config/environment.js';
2
2
  import { accountTools, analyticsTools, communicationTools, developerTools, fulfillmentTools, inventoryTools, marketingTools, metadataTools, otherApiTools, taxonomyTools, tradingTools, tokenManagementTools, } from '../tools/definitions/index.js';
3
- import { chatGptTools } from '../tools/tool-definitions.js';
3
+ import { chatGptTools } from '../tools/chat-tools.js';
4
4
  import { getApiStatusFeed } from '../utils/api-status-feed.js';
5
5
  import { convertToTimestamp, validateTokenExpiry } from '../utils/date-converter.js';
6
6
  // Import Zod schemas for input validation
@@ -196,7 +196,7 @@ export async function executeTool(api, toolName, args) {
196
196
  };
197
197
  }
198
198
  catch (error) {
199
- throw new Error(`Failed to convert date: ${error instanceof Error ? error.message : String(error)}`);
199
+ throw new Error(`Failed to convert date: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
200
200
  }
201
201
  }
202
202
  case 'ebay_validate_token_expiry': {
@@ -217,7 +217,7 @@ export async function executeTool(api, toolName, args) {
217
217
  };
218
218
  }
219
219
  catch (error) {
220
- throw new Error(`Failed to validate token expiry: ${error instanceof Error ? error.message : String(error)}`);
220
+ throw new Error(`Failed to validate token expiry: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
221
221
  }
222
222
  }
223
223
  case 'ebay_set_user_tokens_with_expiry': {
@@ -272,7 +272,7 @@ export async function executeTool(api, toolName, args) {
272
272
  };
273
273
  }
274
274
  catch (error) {
275
- throw new Error(`Failed to set user tokens: ${error instanceof Error ? error.message : String(error)}`);
275
+ throw new Error(`Failed to set user tokens: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
276
276
  }
277
277
  }
278
278
  case 'ebay_display_credentials': {
@@ -370,7 +370,7 @@ export async function executeTool(api, toolName, args) {
370
370
  };
371
371
  }
372
372
  catch (error) {
373
- throw new Error(`Failed to exchange authorization code: ${error instanceof Error ? error.message : String(error)}`);
373
+ throw new Error(`Failed to exchange authorization code: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
374
374
  }
375
375
  }
376
376
  case 'ebay_refresh_access_token': {
@@ -401,7 +401,7 @@ export async function executeTool(api, toolName, args) {
401
401
  };
402
402
  }
403
403
  catch (error) {
404
- throw new Error(`Failed to refresh access token: ${error instanceof Error ? error.message : String(error)}`);
404
+ throw new Error(`Failed to refresh access token: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
405
405
  }
406
406
  }
407
407
  // Account Management
@@ -966,8 +966,33 @@ export async function executeTool(api, toolName, args) {
966
966
  case 'ebay_get_notification_destinations':
967
967
  return await api.notification.getDestinations(args.limit, args.continuationToken);
968
968
  case 'ebay_create_notification_destination': {
969
- const validated = createDestinationSchema.parse(args);
970
- return await api.notification.createDestination(validated);
969
+ // The MCP tool definition wraps inputs inside a `destination` field:
970
+ // { destination: { name, endpoint, verificationToken } }
971
+ // eBay API expects: { name, status, deliveryConfig: { endpoint, verificationToken } }
972
+ const dest = args.destination;
973
+ if (dest) {
974
+ const { name, endpoint, verificationToken, status } = dest;
975
+ const ebayPayload = {
976
+ name,
977
+ status: status ?? 'ENABLED',
978
+ deliveryConfig: { endpoint, verificationToken },
979
+ };
980
+ return await api.notification.createDestination(ebayPayload);
981
+ }
982
+ else {
983
+ // Fallback: legacy flat delivery_config format
984
+ const validated = createDestinationSchema.parse(args);
985
+ const { delivery_config, name, status } = validated;
986
+ const ebayPayload = {
987
+ name,
988
+ status: status ?? 'ENABLED',
989
+ deliveryConfig: {
990
+ endpoint: delivery_config?.endpoint,
991
+ verificationToken: delivery_config?.verification_token,
992
+ },
993
+ };
994
+ return await api.notification.createDestination(ebayPayload);
995
+ }
971
996
  }
972
997
  case 'ebay_get_notification_destination': {
973
998
  const validated = getDestinationSchema.parse(args);
@@ -987,7 +1012,23 @@ export async function executeTool(api, toolName, args) {
987
1012
  }
988
1013
  case 'ebay_create_notification_subscription': {
989
1014
  const validated = createSubscriptionSchema.parse(args);
990
- return await api.notification.createSubscription(validated);
1015
+ // Convert snake_case to camelCase for eBay API
1016
+ const subPayload = {};
1017
+ if (validated.destination_id !== undefined)
1018
+ subPayload.destinationId = validated.destination_id;
1019
+ if (validated.status !== undefined)
1020
+ subPayload.status = validated.status;
1021
+ if (validated.topic_id !== undefined)
1022
+ subPayload.topicId = validated.topic_id;
1023
+ if (validated.payload !== undefined) {
1024
+ const p = validated.payload;
1025
+ subPayload.payload = {
1026
+ ...(p.delivery_protocol !== undefined && { deliveryProtocol: p.delivery_protocol }),
1027
+ ...(p.format !== undefined && { format: p.format }),
1028
+ ...(p.schema_version !== undefined && { schemaVersion: p.schema_version }),
1029
+ };
1030
+ }
1031
+ return await api.notification.createSubscription(subPayload);
991
1032
  }
992
1033
  case 'ebay_get_notification_subscription': {
993
1034
  const validated = getSubscriptionSchema.parse(args);
@@ -16,7 +16,7 @@ export function getPackageJson() {
16
16
  return cachedPackageJson;
17
17
  }
18
18
  catch {
19
- return { name: 'ebay-mcp-remote-edition', version: '1.0.0' };
19
+ return { name: 'ebay-mcp-remote-edition', version: '3.1.2' };
20
20
  }
21
21
  }
22
22
  export function getVersion() {
@@ -0,0 +1,3 @@
1
+ export function getChartValidationSignals(_request) {
2
+ return {};
3
+ }