ebay-mcp 1.13.2 → 1.14.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.
Files changed (169) hide show
  1. package/README.de.md +3 -3
  2. package/README.es.md +3 -3
  3. package/README.fr.md +3 -3
  4. package/README.ja.md +3 -3
  5. package/README.ko.md +3 -3
  6. package/README.md +163 -41
  7. package/README.pt-BR.md +3 -3
  8. package/README.ru.md +3 -3
  9. package/README.zh-CN.md +3 -3
  10. package/build/api/account-management/account.js +633 -297
  11. package/build/api/analytics-and-report/analytics.js +111 -65
  12. package/build/api/client.js +199 -140
  13. package/build/api/clientRequestError.js +23 -0
  14. package/build/api/clientTrading.js +213 -0
  15. package/build/api/clientTradingError.js +4 -0
  16. package/build/api/communication/feedback.js +147 -59
  17. package/build/api/communication/message.js +134 -81
  18. package/build/api/communication/negotiation.js +65 -53
  19. package/build/api/communication/notification.js +458 -164
  20. package/build/api/developer/developer.js +99 -45
  21. package/build/api/index.js +23 -8
  22. package/build/api/listing-management/inventory.js +884 -338
  23. package/build/api/listing-metadata/metadata.js +416 -170
  24. package/build/api/listing-metadata/taxonomy.js +187 -42
  25. package/build/api/marketing-and-promotions/marketing.js +1392 -598
  26. package/build/api/marketing-and-promotions/recommendation.js +49 -24
  27. package/build/api/order-management/dispute.js +226 -45
  28. package/build/api/order-management/fulfillment.js +144 -71
  29. package/build/api/order-management/orderIssueHelpers.js +61 -0
  30. package/build/api/other/compliance.js +50 -38
  31. package/build/api/other/edelivery.js +603 -159
  32. package/build/api/other/finding.js +280 -0
  33. package/build/api/other/identity.js +30 -15
  34. package/build/api/other/translation.js +29 -14
  35. package/build/api/other/vero.js +113 -27
  36. package/build/api/rateLimitTracker.js +55 -0
  37. package/build/api/shared/request.js +189 -98
  38. package/build/api/trading/trading.js +154 -87
  39. package/build/auth/credentialSession.js +250 -0
  40. package/build/auth/oauth.js +298 -171
  41. package/build/auth/{oauth-metadata.js → oauthMetadata.js} +22 -6
  42. package/build/auth/oauthMiddleware.js +104 -0
  43. package/build/auth/{scope-utils.js → scopeUtils.js} +101 -28
  44. package/build/auth/tokenVerifier.js +243 -0
  45. package/build/auth/tokenVerifierError.js +21 -0
  46. package/build/config/environment.js +148 -113
  47. package/build/config/{tool-families.js → toolFamilies.js} +30 -14
  48. package/build/index.js +25 -19
  49. package/build/mcp/{http-transport.js → httpTransport.js} +102 -52
  50. package/build/mcp/readOnlyFilter.js +67 -0
  51. package/build/mcp/runtime.js +31 -13
  52. package/build/mcp/{tool-gating.js → toolGating.js} +77 -49
  53. package/build/mcp/{ui-bridge.js → uiBridge.js} +80 -23
  54. package/build/schemas/account-management/account.js +144 -6
  55. package/build/schemas/analytics/analytics.js +33 -19
  56. package/build/schemas/communication/messages.js +12 -42
  57. package/build/schemas/developer/developer.js +135 -0
  58. package/build/schemas/fulfillment/orders.js +183 -21
  59. package/build/schemas/index.js +27 -19
  60. package/build/schemas/inventory-management/inventory.js +17 -9
  61. package/build/schemas/marketing/marketing.js +480 -25
  62. package/build/schemas/metadata/metadata.js +11 -6
  63. package/build/schemas/other/finding.js +27 -0
  64. package/build/schemas/other/otherApis.js +424 -0
  65. package/build/schemas/taxonomy/taxonomy.js +11 -5
  66. package/build/scripts/{auto-setup.js → autoSetup.js} +87 -74
  67. package/build/scripts/{build-ui.js → buildUi.js} +6 -1
  68. package/build/scripts/{dev-sync.js → devSync.js} +169 -110
  69. package/build/scripts/diagnostics.js +52 -44
  70. package/build/scripts/{download-specs.js → downloadSpecs.js} +29 -21
  71. package/build/scripts/{interactive-setup.js → interactiveSetup.js} +84 -78
  72. package/build/scripts/setup.js +241 -178
  73. package/build/scripts/{setup-shared.js → setupShared.js} +29 -7
  74. package/build/scripts/skills.js +35 -27
  75. package/build/scripts/updateApiStatusDoc.js +71 -0
  76. package/build/serverHttp.js +90 -0
  77. package/build/skills/content.js +124 -118
  78. package/build/skills/index.js +22 -10
  79. package/build/skills/install.js +21 -10
  80. package/build/skills/markers.js +50 -15
  81. package/build/skills/{registry-snapshot.js → registrySnapshot.js} +8 -2
  82. package/build/skills/render.js +41 -14
  83. package/build/skills/targets.js +49 -17
  84. package/build/tools/categories/account.js +166 -264
  85. package/build/tools/categories/analytics.js +11 -22
  86. package/build/tools/categories/browse.js +17 -0
  87. package/build/tools/categories/communication.js +124 -399
  88. package/build/tools/categories/connector.js +23 -20
  89. package/build/tools/categories/developer.js +46 -159
  90. package/build/tools/categories/fulfillment.js +84 -215
  91. package/build/tools/categories/index.js +6 -14
  92. package/build/tools/categories/inventory.js +238 -293
  93. package/build/tools/categories/marketing.js +677 -39
  94. package/build/tools/categories/metadata.js +190 -117
  95. package/build/tools/categories/other.js +81 -208
  96. package/build/tools/categories/taxonomy.js +29 -20
  97. package/build/tools/categories/{token-management.js → tokenManagement.js} +187 -192
  98. package/build/tools/categories/trading.js +16 -47
  99. package/build/tools/contracts.js +34 -24
  100. package/build/tools/{define-tool.js → defineTool.js} +44 -29
  101. package/build/tools/registry.js +87 -29
  102. package/build/tools/schemas.js +3 -57
  103. package/build/tools/ui/archetypes.js +1 -0
  104. package/build/tools/ui/{map-helpers.js → mapHelpers.js} +69 -19
  105. package/build/tools/ui/maps.js +294 -80
  106. package/build/types/index.js +1 -1
  107. package/build/ui/card.html +14 -14
  108. package/build/ui/chart.html +14 -14
  109. package/build/ui/stat.html +87 -0
  110. package/build/ui/table.html +14 -14
  111. package/build/utils/apiStatusFeed.js +95 -0
  112. package/build/utils/cliOutput.js +15 -0
  113. package/build/utils/cliUi.js +108 -0
  114. package/build/utils/communication/feedback.js +57 -77
  115. package/build/utils/communication/message.js +69 -98
  116. package/build/utils/communication/negotiation.js +30 -57
  117. package/build/utils/communication/notification.js +112 -252
  118. package/build/utils/{date-converter.js → dateConverter.js} +62 -30
  119. package/build/utils/effectSchema.js +203 -0
  120. package/build/utils/effectSchemaAdapter.js +121 -0
  121. package/build/utils/effectSchemaChainMethods.js +146 -0
  122. package/build/utils/effectSchemaRefinements.js +104 -0
  123. package/build/utils/effectSchemaStringMethods.js +63 -0
  124. package/build/utils/effectSchemaTypes.js +47 -0
  125. package/build/utils/{env-parser.js → envParser.js} +10 -2
  126. package/build/utils/errors.js +7 -3
  127. package/build/utils/http.js +148 -89
  128. package/build/utils/{llm-client-detector.js → llmClientDetector.js} +295 -274
  129. package/build/utils/logger.js +127 -51
  130. package/build/utils/{oauth-helper.js → oauthHelper.js} +127 -90
  131. package/build/utils/other/compliance.js +18 -38
  132. package/build/utils/other/identity.js +2 -2
  133. package/build/utils/other/translation.js +4 -4
  134. package/build/utils/other/vero.js +36 -20
  135. package/build/utils/schemaHelpers.js +22 -0
  136. package/build/utils/scopeHelper.js +270 -0
  137. package/build/utils/securityChecker.js +316 -0
  138. package/build/utils/{setup-validator.js → setupValidator.js} +113 -94
  139. package/build/utils/{setup-wizard.js → setupWizard.js} +57 -46
  140. package/build/utils/{token-utils.js → tokenUtils.js} +2 -2
  141. package/build/utils/trading/trading.js +35 -0
  142. package/build/utils/typeGuards.js +12 -0
  143. package/build/utils/version.js +65 -29
  144. package/package.json +10 -8
  145. package/build/api/client-trading.js +0 -124
  146. package/build/api/communication/shared.js +0 -59
  147. package/build/api/shared/query-params.js +0 -16
  148. package/build/auth/credential-session.js +0 -166
  149. package/build/auth/oauth-middleware.js +0 -100
  150. package/build/auth/token-verifier.js +0 -186
  151. package/build/schemas/other/other-apis.js +0 -390
  152. package/build/scripts/update-api-status-doc.js +0 -44
  153. package/build/server-http.js +0 -85
  154. package/build/tools/definitions/marketing.js +0 -1381
  155. package/build/tools/tool-handlers/marketing.js +0 -312
  156. package/build/utils/account-management/account.js +0 -818
  157. package/build/utils/api-status-feed.js +0 -84
  158. package/build/utils/cli-ui.js +0 -47
  159. package/build/utils/order-management/dispute.js +0 -369
  160. package/build/utils/order-management/fulfillment.js +0 -205
  161. package/build/utils/other/edelivery.js +0 -235
  162. package/build/utils/schema-helpers.js +0 -13
  163. package/build/utils/scope-helper.js +0 -207
  164. package/build/utils/security-checker.js +0 -248
  165. package/build/utils/type-guards.js +0 -6
  166. /package/build/auth/{oauth-types.js → oauthTypes.js} +0 -0
  167. /package/build/{server-http.d.ts → serverHttp.d.ts} +0 -0
  168. /package/build/tools/ui/{view-models.js → viewModels.js} +0 -0
  169. /package/build/types/{ebay-enums.js → ebayEnums.js} +0 -0
@@ -1,13 +1,86 @@
1
- import { z } from 'zod';
2
- import { buildCredentialDisplay, maskToken } from '../../auth/credential-session.js';
1
+ import { z } from '../../utils/effectSchema.js';
2
+ import { buildCredentialDisplay, maskToken } from '../../auth/credentialSession.js';
3
3
  import { getOAuthAuthorizationUrl, validateScopes } from '../../config/environment.js';
4
- import { defineTool } from '../../tools/define-tool.js';
5
- import { convertToTimestamp, validateTokenExpiry } from '../../utils/date-converter.js';
4
+ import { defineTool } from '../../tools/defineTool.js';
5
+ import { convertToTimestamp, validateTokenExpiry } from '../../utils/dateConverter.js';
6
6
  import { getErrorMessage } from '../../utils/errors.js';
7
+ import { Data, Effect, Either } from 'effect';
8
+ /** Tagged failure raised inside token-management tool handlers. */
9
+ class TokenManagementToolError extends Data.TaggedError('TokenManagementToolError') {
10
+ }
11
+ const tokenToolError = (operation, message, error) => new TokenManagementToolError({
12
+ operation,
13
+ message: error === undefined ? message : `${message}: ${getErrorMessage(error, String(error))}`,
14
+ ...(error === undefined ? {} : { cause: error }),
15
+ });
16
+ /** Convert an optional token expiry value into a timestamp inside the token tool Effect. */
17
+ const optionalTokenExpiryTimestamp = (value, operation, message) => {
18
+ if (value === undefined) {
19
+ return Effect.succeed(undefined);
20
+ }
21
+ return convertToTimestamp(value).pipe(Effect.mapError((error) => tokenToolError(operation, message, error)));
22
+ };
23
+ const missingClientIdError = tokenToolError('getOAuthUrl', 'EBAY_CLIENT_ID environment variable is required to generate OAuth URL');
24
+ const missingRedirectUriError = tokenToolError('getOAuthUrl', 'Redirect URI is required. Either provide it as a parameter or set EBAY_REDIRECT_URI in your .env file.');
25
+ const missingUserTokensError = tokenToolError('refreshAccessToken', 'No user tokens available. Please set user tokens first using ebay_set_user_tokens_with_expiry or add EBAY_USER_REFRESH_TOKEN to your .env file.');
26
+ const oauthUrlInputSchema = z.object({
27
+ redirectUri: z
28
+ .string()
29
+ .optional()
30
+ .describe('Optional redirect URI registered with your eBay application (RuName). If not provided, will use EBAY_REDIRECT_URI from .env file.'),
31
+ scopes: z
32
+ .array(z.string())
33
+ .optional()
34
+ .describe('Optional array of OAuth scopes. If not provided, uses environment-specific default scopes (production or sandbox based on EBAY_ENVIRONMENT). Custom scopes will be validated against the environment.'),
35
+ state: z.string().optional().describe('Optional state parameter for CSRF protection'),
36
+ });
37
+ const setUserTokensInputSchema = z.object({
38
+ accessToken: z.string().min(1).describe('The user access token obtained from OAuth flow'),
39
+ refreshToken: z.string().min(1).describe('The refresh token obtained from OAuth flow'),
40
+ });
41
+ const setUserTokensWithExpiryInputSchema = z.object({
42
+ accessToken: z.string().min(1).describe('eBay user access token'),
43
+ refreshToken: z.string().min(1).describe('eBay user refresh token'),
44
+ accessTokenExpiry: z
45
+ .union([z.string(), z.number()])
46
+ .optional()
47
+ .describe('Optional access-token expiry. Supports ISO date strings, Unix timestamps, and relative time.'),
48
+ refreshTokenExpiry: z
49
+ .union([z.string(), z.number()])
50
+ .optional()
51
+ .describe('Optional refresh-token expiry. Supports ISO date strings, Unix timestamps, and relative time.'),
52
+ autoRefresh: z
53
+ .boolean()
54
+ .optional()
55
+ .default(true)
56
+ .describe('Whether to validate and refresh the access token immediately'),
57
+ });
58
+ const getTokenStatusInputSchema = z.object({});
59
+ const clearTokensInputSchema = z.object({});
60
+ const validateTokenExpiryInputSchema = z.object({
61
+ accessTokenExpiry: z
62
+ .union([z.string(), z.number()])
63
+ .describe('Access token expiry as an ISO date string, Unix timestamp, or relative time'),
64
+ refreshTokenExpiry: z
65
+ .union([z.string(), z.number()])
66
+ .describe('Refresh token expiry as an ISO date string, Unix timestamp, or relative time'),
67
+ });
68
+ const convertDateToTimestampInputSchema = z.object({
69
+ dateInput: z
70
+ .union([z.string(), z.number()])
71
+ .describe('Date to convert as an ISO date string, Unix timestamp, or relative time'),
72
+ });
73
+ const displayCredentialsInputSchema = z.object({});
74
+ const refreshAccessTokenInputSchema = z.object({});
75
+ const exchangeAuthorizationCodeInputSchema = z.object({
76
+ code: z
77
+ .string()
78
+ .min(1)
79
+ .describe('The authorization code received from eBay after user authorization'),
80
+ });
7
81
  /**
8
82
  * OAuth token management tools for consent URLs, token storage, refresh, status,
9
- * and date/expiry helpers. Several handlers read configuration from `process.env`
10
- * (e.g. `EBAY_CLIENT_ID`, `EBAY_REDIRECT_URI`) rather than the eBay API client.
83
+ * and date/expiry helpers. Handlers receive validated runtime config from the API facade.
11
84
  */
12
85
  export const tokenManagementEntries = [
13
86
  defineTool({
@@ -33,111 +106,59 @@ export const tokenManagementEntries = [
33
106
  '- "Insufficient permissions" errors mean you need to re-authorize with additional scopes\n' +
34
107
  '- OAuth URL format: Use + to separate scopes (e.g., scope=scope1+scope2), not %2B\n' +
35
108
  '- Refresh tokens last 18 months and are saved to .env file for persistence',
36
- inputSchema: {
37
- redirectUri: z
38
- .string()
39
- .optional()
40
- .describe('Optional redirect URI registered with your eBay application (RuName). If not provided, will use EBAY_REDIRECT_URI from .env file.'),
41
- scopes: z
42
- .array(z.string())
43
- .optional()
44
- .describe('Optional array of OAuth scopes. If not provided, uses environment-specific default scopes (production or sandbox based on EBAY_ENVIRONMENT). Custom scopes will be validated against the environment.'),
45
- state: z.string().optional().describe('Optional state parameter for CSRF protection'),
46
- },
47
- handler: (_api, args) => {
48
- const clientId = process.env.EBAY_CLIENT_ID ?? '';
49
- const environment = (process.env.EBAY_ENVIRONMENT ?? 'sandbox');
50
- const envRedirectUri = process.env.EBAY_REDIRECT_URI;
51
- const redirectUri = args.redirectUri ?? envRedirectUri;
52
- const scopes = args.scopes;
53
- const state = args.state;
54
- if (!clientId) {
55
- throw new Error('EBAY_CLIENT_ID environment variable is required to generate OAuth URL');
109
+ inputSchema: oauthUrlInputSchema.shape,
110
+ handler: (api, args) => Effect.runPromise(Effect.gen(function* () {
111
+ const config = api.getConfig();
112
+ const redirectUri = args.redirectUri ?? config.redirectUri;
113
+ if (!config.clientId) {
114
+ return yield* Effect.fail(missingClientIdError);
56
115
  }
57
116
  if (!redirectUri) {
58
- throw new Error('Redirect URI is required. Either provide it as a parameter or set EBAY_REDIRECT_URI in your .env file.');
59
- }
60
- let scopeWarnings = [];
61
- let validatedScopes = scopes;
62
- if (scopes && scopes.length > 0) {
63
- const validation = validateScopes(scopes, environment);
64
- scopeWarnings = validation.warnings;
65
- validatedScopes = validation.validScopes;
117
+ return yield* Effect.fail(missingRedirectUriError);
66
118
  }
67
- const authUrl = getOAuthAuthorizationUrl(clientId, redirectUri, environment, validatedScopes, state);
119
+ const scopes = args.scopes;
120
+ const scopeValidation = scopes && scopes.length > 0
121
+ ? validateScopes(scopes, config.environment)
122
+ : { warnings: [], validScopes: scopes };
123
+ const authUrl = getOAuthAuthorizationUrl(config.clientId, redirectUri, config.environment, scopeValidation.validScopes, args.state);
68
124
  const result = {
69
125
  authorizationUrl: authUrl,
70
126
  redirectUri,
71
127
  instructions: 'Open this URL in a browser to authorize the application. After authorization, you will be redirected to your redirect URI with an authorization code that can be exchanged for an access token.',
72
- environment,
128
+ environment: config.environment,
73
129
  scopes: scopes ?? 'default (all Sell API scopes)',
74
130
  };
75
- if (scopeWarnings.length > 0) {
76
- result.warnings = scopeWarnings;
131
+ if (scopeValidation.warnings.length > 0) {
132
+ result.warnings = scopeValidation.warnings;
77
133
  }
78
134
  return result;
79
- },
135
+ })),
80
136
  }),
81
137
  defineTool({
82
138
  name: 'ebay_set_user_tokens',
83
139
  description: 'Set the user access token and refresh token for authenticated API requests. These tokens should be obtained through the OAuth authorization code flow. Tokens will be persisted to disk and automatically refreshed when needed. User tokens provide higher rate limits (10,000-50,000 requests/day) compared to client credentials (1,000 requests/day).',
84
- inputSchema: {
85
- accessToken: z.string().describe('The user access token obtained from OAuth flow'),
86
- refreshToken: z.string().describe('The refresh token obtained from OAuth flow'),
87
- },
88
- handler: async (api, args) => {
89
- const { accessToken, refreshToken } = args;
90
- if (!accessToken || !refreshToken) {
91
- throw new Error('Both accessToken and refreshToken are required');
92
- }
93
- await api.setUserTokens(accessToken, refreshToken);
94
- return {
95
- success: true,
96
- message: 'User tokens successfully stored. These tokens will be used for all subsequent API requests and will be automatically refreshed when needed.',
97
- tokenInfo: api.getTokenInfo(),
98
- };
99
- },
140
+ inputSchema: setUserTokensInputSchema.shape,
141
+ handler: (api, args) => Effect.runPromise(api.setUserTokens(args.accessToken, args.refreshToken).pipe(Effect.mapError((error) => tokenToolError('setUserTokens', 'Failed to set user tokens', error)), Effect.map(() => ({
142
+ success: true,
143
+ message: 'User tokens successfully stored. These tokens will be used for all subsequent API requests and will be automatically refreshed when needed.',
144
+ tokenInfo: api.getTokenInfo(),
145
+ })))),
100
146
  }),
101
147
  defineTool({
102
148
  name: 'ebay_set_user_tokens_with_expiry',
103
149
  description: "Set user access and refresh tokens with custom expiry times. This is an enhanced version of ebay_set_user_tokens that accepts expiry times and can automatically refresh the access token if it's expired but the refresh token is valid. Useful when user provides tokens that may already be partially expired.",
104
- inputSchema: {
105
- accessToken: z.string().min(1).describe('eBay user access token'),
106
- refreshToken: z.string().min(1).describe('eBay user refresh token'),
107
- accessTokenExpiry: z
108
- .union([z.string(), z.number()])
109
- .optional()
110
- .describe('Optional: Access token expiry time. If not provided, defaults to 2 hours from now. Can be ISO date string, Unix timestamp, or relative time (e.g., "in 7200 seconds")'),
111
- refreshTokenExpiry: z
112
- .union([z.string(), z.number()])
113
- .optional()
114
- .describe('Optional: Refresh token expiry time. If not provided, defaults to 18 months from now. Can be ISO date string, Unix timestamp, or relative time'),
115
- autoRefresh: z
116
- .boolean()
117
- .optional()
118
- .default(true)
119
- .describe('If true and access token is expired but refresh token is valid, automatically refresh the access token. Default: true'),
120
- },
121
- handler: async (api, args) => {
150
+ inputSchema: setUserTokensWithExpiryInputSchema.shape,
151
+ handler: (api, args) => {
122
152
  const { accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry, autoRefresh } = args;
123
- if (!accessToken || !refreshToken) {
124
- throw new Error('Both accessToken and refreshToken are required');
125
- }
126
- try {
127
- let accessExpiry;
128
- let refreshExpiry;
129
- if (accessTokenExpiry !== undefined) {
130
- accessExpiry = convertToTimestamp(accessTokenExpiry);
131
- }
132
- if (refreshTokenExpiry !== undefined) {
133
- refreshExpiry = convertToTimestamp(refreshTokenExpiry);
134
- }
135
- await api.setUserTokens(accessToken, refreshToken, accessExpiry, refreshExpiry);
136
- // The OAuth client refreshes internally if the access token is expired.
153
+ return Effect.runPromise(Effect.gen(function* () {
154
+ const accessExpiry = yield* optionalTokenExpiryTimestamp(accessTokenExpiry, 'setUserTokens', 'Failed to set user tokens');
155
+ const refreshExpiry = yield* optionalTokenExpiryTimestamp(refreshTokenExpiry, 'setUserTokens', 'Failed to set user tokens');
156
+ yield* api
157
+ .setUserTokens(accessToken, refreshToken, accessExpiry, refreshExpiry)
158
+ .pipe(Effect.mapError((error) => tokenToolError('setUserTokens', 'Failed to set user tokens', error)));
137
159
  if (autoRefresh) {
138
- try {
139
- const authClient = api.getAuthClient().getOAuthClient();
140
- await authClient.getAccessToken();
160
+ const refreshResult = yield* Effect.either(api.getAuthClient().getOAuthClient().getAccessToken());
161
+ if (Either.isRight(refreshResult)) {
141
162
  return {
142
163
  success: true,
143
164
  message: 'User tokens stored successfully in memory. Access token validated and refreshed if needed. To persist tokens, update EBAY_USER_REFRESH_TOKEN in .env file.',
@@ -145,15 +166,13 @@ export const tokenManagementEntries = [
145
166
  refreshed: true,
146
167
  };
147
168
  }
148
- catch (refreshError) {
149
- return {
150
- success: true,
151
- message: 'User tokens stored, but failed to validate/refresh access token. You may need to re-authorize.',
152
- tokenInfo: api.getTokenInfo(),
153
- refreshed: false,
154
- refreshError: getErrorMessage(refreshError),
155
- };
156
- }
169
+ return {
170
+ success: true,
171
+ message: 'User tokens stored, but failed to validate/refresh access token. You may need to re-authorize.',
172
+ tokenInfo: api.getTokenInfo(),
173
+ refreshed: false,
174
+ refreshError: getErrorMessage(refreshResult.left),
175
+ };
157
176
  }
158
177
  return {
159
178
  success: true,
@@ -161,28 +180,28 @@ export const tokenManagementEntries = [
161
180
  tokenInfo: api.getTokenInfo(),
162
181
  refreshed: false,
163
182
  };
164
- }
165
- catch (error) {
166
- throw new Error(`Failed to set user tokens: ${getErrorMessage(error, String(error))}`);
167
- }
183
+ }));
168
184
  },
169
185
  }),
170
186
  defineTool({
171
187
  name: 'ebay_get_token_status',
172
188
  description: 'Check the current OAuth token status. Returns information about whether user tokens or client credentials are being used, and whether tokens are valid.',
173
- inputSchema: {},
189
+ inputSchema: getTokenStatusInputSchema.shape,
174
190
  handler: (api) => {
175
191
  const tokenInfo = api.getTokenInfo();
176
192
  const hasUserTokens = api.hasUserTokens();
193
+ let currentTokenType = 'none';
194
+ if (tokenInfo.hasUserToken) {
195
+ currentTokenType = 'user_token (10,000-50,000 req/day)';
196
+ }
197
+ else if (tokenInfo.hasAppAccessToken) {
198
+ currentTokenType = 'app_access_token (1,000 req/day)';
199
+ }
177
200
  return {
178
201
  hasUserToken: tokenInfo.hasUserToken,
179
202
  hasAppAccessToken: tokenInfo.hasAppAccessToken,
180
203
  authenticated: api.isAuthenticated(),
181
- currentTokenType: tokenInfo.hasUserToken
182
- ? 'user_token (10,000-50,000 req/day)'
183
- : tokenInfo.hasAppAccessToken
184
- ? 'app_access_token (1,000 req/day)'
185
- : 'none',
204
+ currentTokenType,
186
205
  message: hasUserTokens
187
206
  ? 'Using user access token with automatic refresh'
188
207
  : 'Using app access token from client credentials flow (lower rate limits). Consider setting user tokens for higher rate limits.',
@@ -192,7 +211,7 @@ export const tokenManagementEntries = [
192
211
  defineTool({
193
212
  name: 'ebay_clear_tokens',
194
213
  description: 'Clear all stored OAuth tokens (both user tokens and client credentials). This will require re-authentication for subsequent API calls.',
195
- inputSchema: {},
214
+ inputSchema: clearTokensInputSchema.shape,
196
215
  handler: (api) => {
197
216
  const authClient = api.getAuthClient().getOAuthClient();
198
217
  authClient.clearAllTokens();
@@ -205,44 +224,28 @@ export const tokenManagementEntries = [
205
224
  defineTool({
206
225
  name: 'ebay_validate_token_expiry',
207
226
  description: 'Validate token expiry times and get recommendations. Checks if access/refresh tokens are expired or expiring soon, and provides actionable recommendations (e.g., refresh access token, re-authorize user).',
208
- inputSchema: {
209
- accessTokenExpiry: z
210
- .union([z.string(), z.number()])
211
- .describe('Access token expiry time. Can be ISO date string, Unix timestamp (seconds or milliseconds), or relative time'),
212
- refreshTokenExpiry: z
213
- .union([z.string(), z.number()])
214
- .describe('Refresh token expiry time. Can be ISO date string, Unix timestamp (seconds or milliseconds), or relative time'),
215
- },
216
- handler: (_api, args) => {
217
- try {
218
- const accessExpiry = convertToTimestamp(args.accessTokenExpiry);
219
- const refreshExpiry = convertToTimestamp(args.refreshTokenExpiry);
220
- const validation = validateTokenExpiry(accessExpiry, refreshExpiry);
221
- return {
222
- ...validation,
223
- accessTokenExpiryTimestamp: accessExpiry,
224
- refreshTokenExpiryTimestamp: refreshExpiry,
225
- accessTokenExpiryDate: new Date(accessExpiry).toISOString(),
226
- refreshTokenExpiryDate: new Date(refreshExpiry).toISOString(),
227
- };
228
- }
229
- catch (error) {
230
- throw new Error(`Failed to validate token expiry: ${getErrorMessage(error, String(error))}`);
231
- }
232
- },
227
+ inputSchema: validateTokenExpiryInputSchema.shape,
228
+ handler: (_api, args) => Effect.runPromise(Effect.gen(function* () {
229
+ const accessExpiry = yield* convertToTimestamp(args.accessTokenExpiry).pipe(Effect.mapError((error) => tokenToolError('validateTokenExpiry', 'Failed to validate token expiry', error)));
230
+ const refreshExpiry = yield* convertToTimestamp(args.refreshTokenExpiry).pipe(Effect.mapError((error) => tokenToolError('validateTokenExpiry', 'Failed to validate token expiry', error)));
231
+ const validation = validateTokenExpiry(accessExpiry, refreshExpiry);
232
+ return {
233
+ ...validation,
234
+ accessTokenExpiryTimestamp: accessExpiry,
235
+ refreshTokenExpiryTimestamp: refreshExpiry,
236
+ accessTokenExpiryDate: new Date(accessExpiry).toISOString(),
237
+ refreshTokenExpiryDate: new Date(refreshExpiry).toISOString(),
238
+ };
239
+ })),
233
240
  }),
234
241
  defineTool({
235
242
  name: 'ebay_convert_date_to_timestamp',
236
243
  description: 'Convert a date string or number to Unix timestamp (milliseconds). Supports ISO 8601 dates, Unix timestamps (seconds or milliseconds), and relative time (e.g., "in 2 hours", "in 7200 seconds"). Useful when setting token expiry times from user input.',
237
- inputSchema: {
238
- dateInput: z
239
- .union([z.string(), z.number()])
240
- .describe('Date to convert. Supports ISO 8601 strings (e.g., "2025-01-15T10:30:00Z"), Unix timestamps (seconds or milliseconds), or relative time (e.g., "in 2 hours")'),
241
- },
244
+ inputSchema: convertDateToTimestampInputSchema.shape,
242
245
  handler: (_api, args) => {
243
246
  const { dateInput } = args;
244
- try {
245
- const timestamp = convertToTimestamp(dateInput);
247
+ return Effect.runPromise(Effect.gen(function* () {
248
+ const timestamp = yield* convertToTimestamp(dateInput).pipe(Effect.mapError((error) => tokenToolError('convertDateToTimestamp', 'Failed to convert date', error)));
246
249
  return {
247
250
  success: true,
248
251
  timestamp,
@@ -250,16 +253,13 @@ export const tokenManagementEntries = [
250
253
  formattedDate: new Date(timestamp).toISOString(),
251
254
  message: `Successfully converted to timestamp: ${timestamp}ms (${new Date(timestamp).toISOString()})`,
252
255
  };
253
- }
254
- catch (error) {
255
- throw new Error(`Failed to convert date: ${getErrorMessage(error, String(error))}`);
256
- }
256
+ }));
257
257
  },
258
258
  }),
259
259
  defineTool({
260
260
  name: 'ebay_display_credentials',
261
261
  description: 'Display all eBay API credentials and current token information. Shows client ID, client secret (masked), environment (production/sandbox), redirect URI, and current token status including access token (masked), refresh token (masked), app token (masked), and their expiry times. Useful for debugging authentication issues and verifying configuration.',
262
- inputSchema: {},
262
+ inputSchema: displayCredentialsInputSchema.shape,
263
263
  handler: (api) => {
264
264
  const tokenInfo = api.getTokenInfo();
265
265
  const authClient = api.getAuthClient().getOAuthClient();
@@ -267,7 +267,7 @@ export const tokenManagementEntries = [
267
267
  appAccessToken: authClient.getCachedAppAccessToken(),
268
268
  appAccessTokenExpiry: authClient.getCachedAppAccessTokenExpiry(),
269
269
  authenticated: api.isAuthenticated(),
270
- env: process.env,
270
+ config: api.getConfig(),
271
271
  tokenInfo,
272
272
  userTokens: authClient.getUserTokens(),
273
273
  });
@@ -276,39 +276,40 @@ export const tokenManagementEntries = [
276
276
  defineTool({
277
277
  name: 'ebay_refresh_access_token',
278
278
  description: 'Manually refresh the user access token using the stored refresh token. This is useful when you want to proactively refresh an access token before it expires, or when recovering from authentication errors. Requires that user tokens are already set (either via EBAY_USER_REFRESH_TOKEN in .env or via ebay_set_user_tokens_with_expiry). Returns the new access token and expiry time.',
279
- inputSchema: {},
279
+ inputSchema: refreshAccessTokenInputSchema.shape,
280
280
  outputSchema: {
281
281
  type: 'object',
282
282
  properties: {},
283
283
  description: 'Success response',
284
284
  },
285
- handler: async (api) => {
285
+ handler: (api) => {
286
286
  const authClient = api.getAuthClient().getOAuthClient();
287
- if (!api.hasUserTokens()) {
288
- throw new Error('No user tokens available. Please set user tokens first using ebay_set_user_tokens_with_expiry or add EBAY_USER_REFRESH_TOKEN to your .env file.');
289
- }
290
- try {
291
- await authClient.refreshUserToken();
292
- const internalTokens = authClient.getUserTokens();
293
- return {
294
- success: true,
295
- message: 'Access token refreshed successfully',
296
- accessToken: internalTokens?.userAccessToken
297
- ? maskToken(internalTokens.userAccessToken)
298
- : 'Not available',
299
- accessTokenExpiry: internalTokens?.userAccessTokenExpiry
300
- ? {
301
- timestamp: internalTokens.userAccessTokenExpiry,
302
- date: new Date(internalTokens.userAccessTokenExpiry).toISOString(),
303
- expiresInSeconds: Math.floor((internalTokens.userAccessTokenExpiry - Date.now()) / 1000),
304
- }
305
- : 'Not available',
306
- tokenInfo: api.getTokenInfo(),
307
- };
308
- }
309
- catch (error) {
310
- throw new Error(`Failed to refresh access token: ${getErrorMessage(error, String(error))}`);
311
- }
287
+ return Effect.runPromise(Effect.gen(function* () {
288
+ if (!api.hasUserTokens()) {
289
+ return yield* Effect.fail(missingUserTokensError);
290
+ }
291
+ yield* authClient
292
+ .refreshUserToken()
293
+ .pipe(Effect.mapError((error) => tokenToolError('refreshAccessToken', 'Failed to refresh access token', error)));
294
+ return yield* Effect.sync(() => {
295
+ const internalTokens = authClient.getUserTokens();
296
+ return {
297
+ success: true,
298
+ message: 'Access token refreshed successfully',
299
+ accessToken: internalTokens?.userAccessToken
300
+ ? maskToken(internalTokens.userAccessToken)
301
+ : null,
302
+ accessTokenExpiry: internalTokens?.userAccessTokenExpiry
303
+ ? {
304
+ timestamp: internalTokens.userAccessTokenExpiry,
305
+ date: new Date(internalTokens.userAccessTokenExpiry).toISOString(),
306
+ expiresInSeconds: Math.floor((internalTokens.userAccessTokenExpiry - Date.now()) / 1000),
307
+ }
308
+ : null,
309
+ tokenInfo: api.getTokenInfo(),
310
+ };
311
+ });
312
+ }));
312
313
  },
313
314
  }),
314
315
  defineTool({
@@ -324,27 +325,24 @@ export const tokenManagementEntries = [
324
325
  '- "invalid or was issued to another client": Code expired, get fresh code\n' +
325
326
  '- "Insufficient permissions": Re-run OAuth flow with additional scopes in ebay_get_oauth_url\n\n' +
326
327
  'For complete OAuth guide with scopes, troubleshooting, and examples, see: docs/auth/OAUTH_QUICK_REFERENCE.md',
327
- inputSchema: {
328
- code: z
329
- .string()
330
- .min(1)
331
- .describe('The authorization code received from eBay after user authorization. This is the "code" parameter in the redirect URL.'),
332
- },
328
+ inputSchema: exchangeAuthorizationCodeInputSchema.shape,
333
329
  outputSchema: {
334
330
  type: 'object',
335
331
  properties: {},
336
332
  description: 'Token exchange response including access token, refresh token, and expiry times',
337
333
  },
338
- handler: async (api, args) => {
334
+ handler: (api, args) => {
339
335
  const { code } = args;
340
- if (!code) {
341
- throw new Error('Authorization code is required');
342
- }
343
- try {
336
+ return Effect.runPromise(Effect.gen(function* () {
344
337
  // Codes arriving from a redirect URL may be percent-encoded.
345
- const decodedCode = code.includes('%') ? decodeURIComponent(code) : code;
338
+ const decodedCode = yield* Effect.try({
339
+ try: () => (code.includes('%') ? decodeURIComponent(code) : code),
340
+ catch: (error) => tokenToolError('exchangeAuthorizationCode', 'Failed to exchange authorization code', error),
341
+ });
346
342
  const authClient = api.getAuthClient().getOAuthClient();
347
- const tokenData = await authClient.exchangeCodeForToken(decodedCode);
343
+ const tokenData = yield* authClient
344
+ .exchangeCodeForToken(decodedCode)
345
+ .pipe(Effect.mapError((error) => tokenToolError('exchangeAuthorizationCode', 'Failed to exchange authorization code', error)));
348
346
  return {
349
347
  success: true,
350
348
  message: 'Authorization code successfully exchanged for tokens. Tokens have been stored and will be used for subsequent API requests.',
@@ -358,10 +356,7 @@ export const tokenManagementEntries = [
358
356
  },
359
357
  note: 'The refresh token has been saved to your .env file for future use.',
360
358
  };
361
- }
362
- catch (error) {
363
- throw new Error(`Failed to exchange authorization code: ${getErrorMessage(error, String(error))}`);
364
- }
359
+ }));
365
360
  },
366
361
  }),
367
362
  ];
@@ -1,79 +1,48 @@
1
- import { z } from 'zod';
2
- import { defineTool } from '../../tools/define-tool.js';
3
- /** Trading API tools for legacy listing, order, feedback, and dispute operations. */
1
+ import { defineTool } from '../../tools/defineTool.js';
2
+ import { createListingSchema, endListingSchema, getActiveListingsSchema, getListingSchema, relistItemSchema, reviseListingSchema, } from '../../utils/trading/trading.js';
3
+ import { Effect } from 'effect';
4
+ /** Trading API tools for fixed-price listing operations. */
4
5
  export const tradingEntries = [
5
6
  defineTool({
6
7
  name: 'ebay_get_active_listings',
7
8
  description: 'Get all active fixed-price listings with SKU, quantity, price, and watch count.\n\nUses the Trading API (GetMyeBaySelling). Returns listings created via any method (UI, Trading API, or REST API).\n\nRequired: User OAuth token.',
8
- inputSchema: {
9
- page: z.number().optional().describe('Page number (default 1)'),
10
- entriesPerPage: z.number().optional().describe('Items per page, max 200 (default 50)'),
11
- },
9
+ inputSchema: getActiveListingsSchema.shape,
12
10
  annotations: { readOnlyHint: true },
13
- handler: (api, args) => api.trading.getActiveListings(args.page, args.entriesPerPage),
11
+ handler: (api, args) => Effect.runPromise(api.trading.getActiveListings(args)),
14
12
  }),
15
13
  defineTool({
16
14
  name: 'ebay_get_listing',
17
15
  description: 'Get full details for a single listing by item ID.\n\nUses the Trading API (GetItem). Returns all listing fields including description, specifics, shipping, and images.\n\nRequired: User OAuth token.',
18
- inputSchema: {
19
- itemId: z.string().describe('The eBay item ID (e.g., "167382780779")'),
20
- },
16
+ inputSchema: getListingSchema.shape,
21
17
  annotations: { readOnlyHint: true },
22
- handler: (api, args) => api.trading.getListing(args.itemId),
18
+ handler: (api, args) => Effect.runPromise(api.trading.getListing(args)),
23
19
  }),
24
20
  defineTool({
25
21
  name: 'ebay_create_listing',
26
22
  description: 'Create a new fixed-price listing.\n\nUses the Trading API (AddFixedPriceItem). Requires complete item details.\n\nRequired: User OAuth token.',
27
- inputSchema: {
28
- item: z
29
- .record(z.unknown())
30
- .describe('Item details object. Required fields: Title, PrimaryCategory.CategoryID, StartPrice, ConditionID, Country, Currency, DispatchTimeMax, ListingDuration, ListingType ("FixedPriceItem"), Quantity, SKU.'),
31
- },
23
+ inputSchema: createListingSchema.shape,
32
24
  annotations: { readOnlyHint: false },
33
- handler: (api, args) => api.trading.createListing(args.item),
25
+ handler: (api, args) => Effect.runPromise(api.trading.createListing(args)),
34
26
  }),
35
27
  defineTool({
36
28
  name: 'ebay_revise_listing',
37
29
  description: 'Revise an existing fixed-price listing. Update quantity, price, title, description, or any other field.\n\nUses the Trading API (ReviseFixedPriceItem). Only send the fields you want to change.\n\nExamples:\n- Update quantity: { "Quantity": 10 }\n- Update price: { "StartPrice": 14.99 }\n- Update title: { "Title": "New Title" }\n- Multiple fields: { "Quantity": 10, "StartPrice": 14.99 }\n\nRequired: User OAuth token.',
38
- inputSchema: {
39
- itemId: z.string().describe('The eBay item ID to revise'),
40
- fields: z
41
- .record(z.unknown())
42
- .describe('Fields to update (e.g., { "Quantity": 10, "StartPrice": 14.99 })'),
43
- },
30
+ inputSchema: reviseListingSchema.shape,
44
31
  annotations: { readOnlyHint: false },
45
- handler: (api, args) => api.trading.reviseListing(args.itemId, args.fields),
32
+ handler: (api, args) => Effect.runPromise(api.trading.reviseListing(args)),
46
33
  }),
47
34
  defineTool({
48
35
  name: 'ebay_end_listing',
49
36
  description: 'End/remove an active fixed-price listing.\n\nUses the Trading API (EndFixedPriceItem).\n\nRequired: User OAuth token.',
50
- inputSchema: {
51
- itemId: z.string().describe('The eBay item ID to end'),
52
- reason: z
53
- .enum([
54
- 'NotAvailable',
55
- 'Incorrect',
56
- 'LostOrBroken',
57
- 'OtherListingError',
58
- 'SellToHighBidder',
59
- ])
60
- .optional()
61
- .describe('Reason for ending (default: NotAvailable)'),
62
- },
37
+ inputSchema: endListingSchema.shape,
63
38
  annotations: { readOnlyHint: false, destructiveHint: true },
64
- handler: (api, args) => api.trading.endListing(args.itemId, args.reason),
39
+ handler: (api, args) => Effect.runPromise(api.trading.endListing(args)),
65
40
  }),
66
41
  defineTool({
67
42
  name: 'ebay_relist_item',
68
43
  description: 'Relist an ended fixed-price listing, optionally with modifications.\n\nUses the Trading API (RelistFixedPriceItem).\n\nRequired: User OAuth token.',
69
- inputSchema: {
70
- itemId: z.string().describe('The eBay item ID to relist'),
71
- modifications: z
72
- .record(z.unknown())
73
- .optional()
74
- .describe('Optional fields to change when relisting (e.g., { "Quantity": 20 })'),
75
- },
44
+ inputSchema: relistItemSchema.shape,
76
45
  annotations: { readOnlyHint: false },
77
- handler: (api, args) => api.trading.relistItem(args.itemId, args.modifications),
46
+ handler: (api, args) => Effect.runPromise(api.trading.relistItem(args)),
78
47
  }),
79
48
  ];