ebay-mcp 1.18.0 → 1.18.3
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 +24 -35
- package/build/api/account-management/accountV2.js +22 -22
- package/build/api/account-management/finances.js +10 -10
- package/build/api/analytics-and-report/analytics.js +1 -1
- package/build/api/{client.js → client/ebayApiClient.js} +37 -41
- package/build/api/{clientTrading.js → client/tradingApiClient.js} +22 -22
- package/build/api/communication/feedback.js +21 -21
- package/build/api/communication/message.js +23 -23
- package/build/api/communication/negotiation.js +7 -7
- package/build/api/communication/notification.js +52 -52
- package/build/api/developer/developer.js +3 -3
- package/build/api/{index.js → ebaySellerApi.js} +4 -4
- package/build/api/listing-management/feedReportTasks.js +6 -6
- package/build/api/listing-management/feedRequest.js +6 -6
- package/build/api/listing-management/feedSchedules.js +4 -4
- package/build/api/listing-management/feedTasks.js +2 -2
- package/build/api/listing-management/items.js +22 -22
- package/build/api/listing-management/locations.js +4 -4
- package/build/api/listing-management/media.js +14 -14
- package/build/api/listing-management/mediaAttach.js +14 -14
- package/build/api/listing-management/mediaResources.js +15 -13
- package/build/api/listing-management/offerFormat.js +1 -1
- package/build/api/listing-management/offers.js +23 -23
- package/build/api/listing-management/stores.js +9 -9
- package/build/api/listing-metadata/metadata.js +10 -10
- package/build/api/marketing-and-promotions/ads.js +34 -34
- package/build/api/marketing-and-promotions/campaigns.js +19 -19
- package/build/api/marketing-and-promotions/promotions.js +22 -22
- package/build/api/marketing-and-promotions/reports.js +7 -7
- package/build/api/order-management/dispute.js +11 -9
- package/build/api/order-management/fulfillment.js +4 -4
- package/build/api/order-management/logistics.js +7 -7
- package/build/api/other/browseMappers.js +15 -13
- package/build/api/other/browseValidators.js +4 -4
- package/build/api/other/edelivery.js +19 -19
- package/build/api/other/finding.js +13 -13
- package/build/api/other/translation.js +2 -2
- package/build/api/other/vero.js +2 -2
- package/build/api/shared/download.js +7 -7
- package/build/api/shared/request.js +28 -19
- package/build/api/trading/listingFormat.js +37 -30
- package/build/api/trading/trading.js +33 -33
- package/build/auth/credentialSession.js +5 -5
- package/build/auth/oauth.js +3 -3
- package/build/auth/oauthHelper.js +24 -24
- package/build/auth/oauthMetadata.js +4 -4
- package/build/auth/oauthMiddleware.js +16 -16
- package/build/auth/scopeUtils.js +2 -2
- package/build/auth/tokenVerifier.js +23 -23
- package/build/config/environment.js +4 -45
- package/build/config/oauthScopes.js +132 -0
- package/build/index.js +5 -5
- package/build/mcp/httpTransport.js +32 -20
- package/build/mcp/runtime.js +12 -12
- package/build/mcp/toolGating.js +6 -12
- package/build/mcp/uiBridge.js +3 -3
- package/build/schemas/account-management/account.js +2 -52
- package/build/schemas/account-management/accountV2.js +2 -2
- package/build/schemas/account-management/finances.js +1 -1
- package/build/schemas/analytics/analytics.js +2 -48
- package/build/schemas/communication/messages.js +2 -46
- package/build/schemas/developer/developer.js +1 -27
- package/build/schemas/fulfillment/logistics.js +1 -1
- package/build/schemas/fulfillment/orders.js +2 -47
- package/build/schemas/inventory-management/feed.js +1 -1
- package/build/schemas/inventory-management/inventory.js +2 -50
- package/build/schemas/inventory-management/media.js +2 -2
- package/build/schemas/inventory-management/mediaDocuments.js +1 -1
- package/build/schemas/inventory-management/stores.js +1 -1
- package/build/schemas/marketing/ads.js +2 -2
- package/build/schemas/marketing/campaigns.js +2 -2
- package/build/schemas/marketing/common.js +2 -2
- package/build/schemas/marketing/marketing.js +0 -1
- package/build/schemas/marketing/promotions.js +2 -2
- package/build/schemas/marketing/reports.js +2 -2
- package/build/schemas/metadata/metadata.js +2 -90
- package/build/schemas/metadata/shipping.js +1 -1
- package/build/schemas/other/browse.js +2 -2
- package/build/schemas/other/finding.js +2 -2
- package/build/schemas/other/otherApis.js +2 -114
- package/build/schemas/taxonomy/categoryTree.js +1 -1
- package/build/schemas/taxonomy/charity.js +1 -1
- package/build/schemas/taxonomy/taxonomy.js +2 -69
- package/build/scripts/autoSetup.js +3 -3
- package/build/scripts/{buildUi.js → buildMcpApps.js} +13 -13
- package/build/scripts/devSync.js +19 -68
- package/build/scripts/diagnostics.js +15 -16
- package/build/scripts/interactiveSetup.js +25 -25
- package/build/scripts/scopeHelper.js +2 -2
- package/build/scripts/securityChecker.js +20 -20
- package/build/scripts/setup.js +76 -76
- package/build/scripts/setupShared.js +5 -30
- package/build/scripts/setupValidator.js +9 -9
- package/build/scripts/setupWizard.js +23 -23
- package/build/scripts/skills.js +14 -17
- package/build/scripts/specRefs.js +1 -1
- package/build/scripts/specSources.js +144 -0
- package/build/serverHttp.js +4 -7
- package/build/skills/content.js +13 -13
- package/build/skills/install.js +9 -9
- package/build/skills/render.js +2 -2
- package/build/tools/categories/communication.js +1 -1
- package/build/tools/categories/connector.js +19 -16
- package/build/tools/categories/fulfillment.js +4 -4
- package/build/tools/categories/inventory.js +1 -1
- package/build/tools/categories/metadata.js +1 -1
- package/build/tools/categories/taxonomy.js +1 -1
- package/build/tools/categories/tokenManagement.js +8 -8
- package/build/tools/contracts.js +2 -2
- package/build/tools/defineTool.js +27 -12
- package/build/tools/registry.js +2 -2
- package/build/tools/schemas.js +2 -2
- package/build/tools/ui/mapHelpers.js +12 -12
- package/build/tools/ui/maps.js +101 -97
- package/build/tools/ui/viewModels.js +2 -2
- package/build/utils/apiStatusFeed.js +12 -12
- package/build/utils/communication/feedback.js +3 -3
- package/build/utils/communication/message.js +3 -3
- package/build/utils/communication/negotiation.js +3 -3
- package/build/utils/communication/notification.js +3 -3
- package/build/utils/entryModule.js +33 -0
- package/build/utils/envFile.js +47 -0
- package/build/utils/envParser.js +5 -5
- package/build/utils/errors.js +4 -4
- package/build/utils/http.js +38 -38
- package/build/utils/localFeedFiles.js +3 -3
- package/build/utils/logRedaction.js +21 -0
- package/build/utils/logger.js +49 -35
- package/build/utils/other/compliance.js +3 -3
- package/build/utils/other/identity.js +3 -3
- package/build/utils/other/translation.js +3 -3
- package/build/utils/other/vero.js +3 -3
- package/build/utils/schemaHelpers.js +2 -2
- package/build/utils/tokenUtils.js +2 -2
- package/build/utils/trading/trading.js +1 -1
- package/build/utils/version.js +1 -1
- package/build/utils/zodIssues.js +14 -0
- package/package.json +8 -11
- package/README.de.md +0 -426
- package/README.es.md +0 -426
- package/README.fr.md +0 -426
- package/README.ja.md +0 -426
- package/README.ko.md +0 -426
- package/README.pt-BR.md +0 -426
- package/README.ru.md +0 -426
- package/README.zh-CN.md +0 -426
- package/build/schemas/index.js +0 -76
- package/build/schemas/marketing/jsonSchemas.js +0 -118
- package/build/scripts/downloadSpecs.js +0 -117
- package/build/scripts/updateApiStatusDoc.js +0 -71
- package/build/utils/effectSchema.js +0 -203
- package/build/utils/effectSchemaAdapter.js +0 -121
- package/build/utils/effectSchemaChainMethods.js +0 -146
- package/build/utils/effectSchemaRefinements.js +0 -104
- package/build/utils/effectSchemaStringMethods.js +0 -63
- package/build/utils/effectSchemaTypes.js +0 -47
- package/docs/auth/production_scopes.json +0 -111
- package/docs/auth/sandbox_scopes.json +0 -142
- /package/build/api/{clientRequestError.js → client/ebayClientRequestError.js} +0 -0
- /package/build/api/{rateLimitTracker.js → client/rateLimitTracker.js} +0 -0
- /package/build/api/{clientTradingError.js → client/tradingApiFailure.js} +0 -0
|
@@ -8,7 +8,7 @@ import { Effect, Either } from 'effect';
|
|
|
8
8
|
* Create Bearer token authentication middleware
|
|
9
9
|
*
|
|
10
10
|
* @param config - Token verifier and RFC 6750 challenge settings.
|
|
11
|
-
* @returns Express middleware that verifies bearer tokens and attaches `
|
|
11
|
+
* @returns Express middleware that verifies bearer tokens and attaches `request.auth`.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
@@ -17,11 +17,11 @@ import { Effect, Either } from 'effect';
|
|
|
17
17
|
*/
|
|
18
18
|
export const createBearerAuthMiddleware = (config) => {
|
|
19
19
|
const realm = config.realm || 'mcp';
|
|
20
|
-
return async (
|
|
20
|
+
return async (request, response, next) => {
|
|
21
21
|
// Extract token from Authorization header
|
|
22
|
-
const authHeader =
|
|
22
|
+
const authHeader = request.headers.authorization;
|
|
23
23
|
if (!authHeader) {
|
|
24
|
-
sendUnauthorized(
|
|
24
|
+
sendUnauthorized(response, realm, config.resourceMetadataUrl, {
|
|
25
25
|
error: 'invalid_token',
|
|
26
26
|
error_description: 'No authorization header provided',
|
|
27
27
|
});
|
|
@@ -30,7 +30,7 @@ export const createBearerAuthMiddleware = (config) => {
|
|
|
30
30
|
// Check Bearer scheme
|
|
31
31
|
const parts = authHeader.split(' ');
|
|
32
32
|
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
33
|
-
sendUnauthorized(
|
|
33
|
+
sendUnauthorized(response, realm, config.resourceMetadataUrl, {
|
|
34
34
|
error: 'invalid_token',
|
|
35
35
|
error_description: 'Invalid authorization header format. Expected: Bearer <token>',
|
|
36
36
|
});
|
|
@@ -40,20 +40,20 @@ export const createBearerAuthMiddleware = (config) => {
|
|
|
40
40
|
const verified = await Effect.runPromise(Effect.either(config.verifier.verifyToken(token)));
|
|
41
41
|
if (Either.isLeft(verified)) {
|
|
42
42
|
const errorMessage = getErrorMessage(verified.left, 'Token verification failed');
|
|
43
|
-
sendUnauthorized(
|
|
43
|
+
sendUnauthorized(response, realm, config.resourceMetadataUrl, {
|
|
44
44
|
error: 'invalid_token',
|
|
45
45
|
error_description: errorMessage,
|
|
46
46
|
});
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
request.auth = verified.right;
|
|
50
50
|
next();
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
54
|
* Send 401 Unauthorized response with RFC 6750 compliant WWW-Authenticate header
|
|
55
55
|
*/
|
|
56
|
-
const sendUnauthorized = (
|
|
56
|
+
const sendUnauthorized = (response, realm, resourceMetadataUrl, challenge) => {
|
|
57
57
|
// Build WWW-Authenticate header per RFC 6750
|
|
58
58
|
let authenticateValue = `Bearer realm="${realm}", resource_metadata="${resourceMetadataUrl}"`;
|
|
59
59
|
if (challenge.error) {
|
|
@@ -65,8 +65,8 @@ const sendUnauthorized = (res, realm, resourceMetadataUrl, challenge) => {
|
|
|
65
65
|
if (challenge.scope) {
|
|
66
66
|
authenticateValue += `, scope="${challenge.scope}"`;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
response.setHeader('WWW-Authenticate', authenticateValue);
|
|
69
|
+
response.status(401).json({
|
|
70
70
|
error: challenge.error || 'unauthorized',
|
|
71
71
|
error_description: challenge.error_description || 'Authorization required',
|
|
72
72
|
});
|
|
@@ -82,21 +82,21 @@ const sendUnauthorized = (res, realm, resourceMetadataUrl, challenge) => {
|
|
|
82
82
|
* app.get('/admin', requireScopes(['mcp:admin']), handler);
|
|
83
83
|
* ```
|
|
84
84
|
*/
|
|
85
|
-
export const requireScopes = (requiredScopes) => (
|
|
86
|
-
if (!
|
|
87
|
-
|
|
85
|
+
export const requireScopes = (requiredScopes) => (request, response, next) => {
|
|
86
|
+
if (!request.auth) {
|
|
87
|
+
response.status(401).json({
|
|
88
88
|
error: 'unauthorized',
|
|
89
89
|
error_description: 'No authentication information found',
|
|
90
90
|
});
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
const hasRequiredScopes = requiredScopes.every((scope) =>
|
|
93
|
+
const hasRequiredScopes = requiredScopes.every((scope) => request.auth.scopes.includes(scope));
|
|
94
94
|
if (!hasRequiredScopes) {
|
|
95
|
-
|
|
95
|
+
response.status(403).json({
|
|
96
96
|
error: 'insufficient_scope',
|
|
97
97
|
error_description: `Missing required scopes: ${requiredScopes.join(', ')}`,
|
|
98
98
|
required_scopes: requiredScopes,
|
|
99
|
-
provided_scopes:
|
|
99
|
+
provided_scopes: request.auth.scopes,
|
|
100
100
|
});
|
|
101
101
|
return;
|
|
102
102
|
}
|
package/build/auth/scopeUtils.js
CHANGED
|
@@ -28,7 +28,7 @@ const optionalConsent = (tools, scope, description) => Object.fromEntries(tools.
|
|
|
28
28
|
*
|
|
29
29
|
* @example
|
|
30
30
|
* ```ts
|
|
31
|
-
* const
|
|
31
|
+
* const scopeValidation = validateScopesDetailed(scopes, 'production');
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export const validateScopesDetailed = (scopes, environment) => {
|
|
@@ -381,7 +381,7 @@ export const getReadonlyScope = (writeScope) => {
|
|
|
381
381
|
'commerce.feedback',
|
|
382
382
|
];
|
|
383
383
|
const scopeType = writeScope.split('/').pop();
|
|
384
|
-
if (scopeType && hasReadonly.some((
|
|
384
|
+
if (scopeType && hasReadonly.some((readonlyMarker) => scopeType.includes(readonlyMarker))) {
|
|
385
385
|
return `${writeScope}.readonly`;
|
|
386
386
|
}
|
|
387
387
|
return null;
|
|
@@ -93,7 +93,7 @@ export class TokenVerifier {
|
|
|
93
93
|
}
|
|
94
94
|
// Prepare introspection request. Client credentials authenticate via HTTP
|
|
95
95
|
// Basic (RFC 7662 §2.1) rather than form body fields.
|
|
96
|
-
const
|
|
96
|
+
const introspectionRequest = {
|
|
97
97
|
token,
|
|
98
98
|
token_type_hint: 'access_token',
|
|
99
99
|
};
|
|
@@ -104,17 +104,17 @@ export class TokenVerifier {
|
|
|
104
104
|
headers.Authorization = `Basic ${Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64')}`;
|
|
105
105
|
}
|
|
106
106
|
const params = new URLSearchParams({
|
|
107
|
-
token:
|
|
107
|
+
token: introspectionRequest.token,
|
|
108
108
|
});
|
|
109
109
|
// Make introspection request
|
|
110
|
-
const
|
|
110
|
+
const introspection = yield* this.requestVerifierData('verifyViaIntrospection', 'introspectionRequest', 'Token introspection failed', {
|
|
111
111
|
method: 'POST',
|
|
112
112
|
url: introspectionEndpoint,
|
|
113
113
|
body: params,
|
|
114
114
|
headers,
|
|
115
115
|
});
|
|
116
116
|
// Check if token is active
|
|
117
|
-
if (!
|
|
117
|
+
if (!introspection.active) {
|
|
118
118
|
return yield* Effect.fail(tokenVerifierError({
|
|
119
119
|
operation: 'verifyViaIntrospection',
|
|
120
120
|
reason: 'inactiveToken',
|
|
@@ -122,15 +122,15 @@ export class TokenVerifier {
|
|
|
122
122
|
}));
|
|
123
123
|
}
|
|
124
124
|
// Validate audience
|
|
125
|
-
if (!this.validateAudience(
|
|
125
|
+
if (!this.validateAudience(introspection.aud)) {
|
|
126
126
|
return yield* Effect.fail(tokenVerifierError({
|
|
127
127
|
operation: 'verifyViaIntrospection',
|
|
128
128
|
reason: 'invalidAudience',
|
|
129
|
-
message: `Invalid audience. Expected: ${this.config.expectedAudience}, Got: ${
|
|
129
|
+
message: `Invalid audience. Expected: ${this.config.expectedAudience}, Got: ${introspection.aud}`,
|
|
130
130
|
}));
|
|
131
131
|
}
|
|
132
132
|
// Validate scopes
|
|
133
|
-
const scopes =
|
|
133
|
+
const scopes = introspection.scope ? introspection.scope.split(' ') : [];
|
|
134
134
|
if (this.config.requiredScopes) {
|
|
135
135
|
const hasRequiredScopes = this.config.requiredScopes.every((scope) => scopes.includes(scope));
|
|
136
136
|
if (!hasRequiredScopes) {
|
|
@@ -143,11 +143,11 @@ export class TokenVerifier {
|
|
|
143
143
|
}
|
|
144
144
|
return {
|
|
145
145
|
token,
|
|
146
|
-
clientId:
|
|
146
|
+
clientId: introspection.client_id || 'missing-client-id',
|
|
147
147
|
scopes,
|
|
148
|
-
expiresAt:
|
|
149
|
-
audience:
|
|
150
|
-
subject:
|
|
148
|
+
expiresAt: introspection.exp,
|
|
149
|
+
audience: introspection.aud,
|
|
150
|
+
subject: introspection.sub,
|
|
151
151
|
};
|
|
152
152
|
});
|
|
153
153
|
/**
|
|
@@ -179,7 +179,7 @@ export class TokenVerifier {
|
|
|
179
179
|
cause,
|
|
180
180
|
}),
|
|
181
181
|
});
|
|
182
|
-
const { payload } = yield* Effect.tryPromise({
|
|
182
|
+
const { payload: claims } = yield* Effect.tryPromise({
|
|
183
183
|
try: () => jwtVerify(token, JWKS, {
|
|
184
184
|
issuer: metadata.issuer,
|
|
185
185
|
audience: this.config.expectedAudience,
|
|
@@ -192,11 +192,11 @@ export class TokenVerifier {
|
|
|
192
192
|
}),
|
|
193
193
|
});
|
|
194
194
|
let scopes = [];
|
|
195
|
-
if (typeof
|
|
196
|
-
scopes =
|
|
195
|
+
if (typeof claims.scope === 'string') {
|
|
196
|
+
scopes = claims.scope.split(' ');
|
|
197
197
|
}
|
|
198
|
-
else if (Array.isArray(
|
|
199
|
-
scopes =
|
|
198
|
+
else if (Array.isArray(claims.scope)) {
|
|
199
|
+
scopes = claims.scope.filter((scope) => typeof scope === 'string');
|
|
200
200
|
}
|
|
201
201
|
if (this.config.requiredScopes) {
|
|
202
202
|
const hasRequiredScopes = this.config.requiredScopes.every((scope) => scopes.includes(scope));
|
|
@@ -209,19 +209,19 @@ export class TokenVerifier {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
let clientId = 'missing-client-id';
|
|
212
|
-
if (typeof
|
|
213
|
-
clientId =
|
|
212
|
+
if (typeof claims.client_id === 'string') {
|
|
213
|
+
clientId = claims.client_id;
|
|
214
214
|
}
|
|
215
|
-
else if (typeof
|
|
216
|
-
clientId =
|
|
215
|
+
else if (typeof claims.azp === 'string') {
|
|
216
|
+
clientId = claims.azp;
|
|
217
217
|
}
|
|
218
218
|
return {
|
|
219
219
|
token,
|
|
220
220
|
clientId,
|
|
221
221
|
scopes,
|
|
222
|
-
expiresAt:
|
|
223
|
-
audience:
|
|
224
|
-
subject:
|
|
222
|
+
expiresAt: claims.exp,
|
|
223
|
+
audience: claims.aud,
|
|
224
|
+
subject: claims.sub,
|
|
225
225
|
};
|
|
226
226
|
});
|
|
227
227
|
/**
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { config } from 'dotenv';
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { dirname, join } from 'path';
|
|
5
4
|
import { CREDENTIAL_ENV_PATH } from '../config/credentialFile.js';
|
|
6
5
|
import { getToolGatingConfigError } from '../config/toolFamilies.js';
|
|
7
6
|
import { getMediaAccessConfig } from '../config/mediaAccess.js';
|
|
8
|
-
import {
|
|
7
|
+
import { productionScopes, sandboxScopes } from '../config/oauthScopes.js';
|
|
9
8
|
import { getVersion } from '../utils/version.js';
|
|
10
9
|
import { Effect, Either } from 'effect';
|
|
11
10
|
import process from 'node:process';
|
|
12
|
-
// Get the current directory for loading scope files and .env
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = dirname(__filename);
|
|
15
11
|
// Load .env from the package root (two levels up from src/config/), not process.cwd().
|
|
16
12
|
// MCP servers inherit cwd from the host (e.g. Claude Code's project dir), so
|
|
17
13
|
// process.cwd() may point to an unrelated project with a different .env.
|
|
@@ -46,40 +42,6 @@ export const getLoggingConfig = () => ({
|
|
|
46
42
|
export const getUiRuntimeConfig = () => ({
|
|
47
43
|
enabled: process.env.EBAY_MCP_UI !== 'off',
|
|
48
44
|
});
|
|
49
|
-
/** Loads and parses scopes from one checked-in scope JSON file. */
|
|
50
|
-
const loadScopes = (fileName, label) => {
|
|
51
|
-
const loaded = Effect.runSync(Effect.either(Effect.try({
|
|
52
|
-
try: () => {
|
|
53
|
-
const scopesPath = join(__dirname, '../../docs/auth', fileName);
|
|
54
|
-
const scopesData = readFileSync(scopesPath, 'utf-8');
|
|
55
|
-
const scopes = JSON.parse(scopesData);
|
|
56
|
-
// Filter out empty objects and extract unique scope strings
|
|
57
|
-
const uniqueScopes = new Set();
|
|
58
|
-
scopes.forEach((item) => {
|
|
59
|
-
if (item.Scope) {
|
|
60
|
-
uniqueScopes.add(item.Scope);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return Array.from(uniqueScopes);
|
|
64
|
-
},
|
|
65
|
-
catch: (error) => error,
|
|
66
|
-
})));
|
|
67
|
-
if (Either.isLeft(loaded)) {
|
|
68
|
-
const error = loaded.left;
|
|
69
|
-
writeConfigDiagnostic(`Failed to load ${label} scopes: ${getErrorMessage(error)}`);
|
|
70
|
-
// Return a minimal set of core scopes as fallback
|
|
71
|
-
return ['https://api.ebay.com/oauth/api_scope'];
|
|
72
|
-
}
|
|
73
|
-
return loaded.right;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Loads and parses production scopes from the checked-in eBay scope table.
|
|
77
|
-
*/
|
|
78
|
-
const getProductionScopes = () => loadScopes('production_scopes.json', 'production');
|
|
79
|
-
/**
|
|
80
|
-
* Loads and parses sandbox scopes from the checked-in eBay scope table.
|
|
81
|
-
*/
|
|
82
|
-
const getSandboxScopes = () => loadScopes('sandbox_scopes.json', 'sandbox');
|
|
83
45
|
/**
|
|
84
46
|
* Gets default OAuth scopes for the specified eBay environment.
|
|
85
47
|
*
|
|
@@ -91,10 +53,7 @@ const getSandboxScopes = () => loadScopes('sandbox_scopes.json', 'sandbox');
|
|
|
91
53
|
* ```
|
|
92
54
|
*/
|
|
93
55
|
export const getDefaultScopes = (environment) => {
|
|
94
|
-
|
|
95
|
-
return getProductionScopes();
|
|
96
|
-
}
|
|
97
|
-
return getSandboxScopes();
|
|
56
|
+
return [...(environment === 'production' ? productionScopes : sandboxScopes)];
|
|
98
57
|
};
|
|
99
58
|
/** Optional scope requiring keyset eligibility and fresh user consent. */
|
|
100
59
|
export const POST_ORDER_DOCUMENT_SCOPE = 'https://api.ebay.com/oauth/api_scope/commerce.post_order.document';
|
|
@@ -159,7 +118,7 @@ export const getRequestedScopes = (environment, env = process.env) => {
|
|
|
159
118
|
* @returns Requested scopes plus warnings for environment-only or unknown scopes.
|
|
160
119
|
* @example
|
|
161
120
|
* ```ts
|
|
162
|
-
* const
|
|
121
|
+
* const scopeValidation = validateScopes(scopes, 'sandbox');
|
|
163
122
|
* ```
|
|
164
123
|
*/
|
|
165
124
|
export const validateScopes = (scopes, environment) => {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth scopes eBay grants to seller apps, copied from the developer portal scope tables.
|
|
3
|
+
* Each comment is eBay's description of the scope below it.
|
|
4
|
+
*/
|
|
5
|
+
/** Scopes available to apps in the eBay production environment. */
|
|
6
|
+
export const productionScopes = [
|
|
7
|
+
// View public data from eBay
|
|
8
|
+
'https://api.ebay.com/oauth/api_scope',
|
|
9
|
+
// View your eBay marketing activities, such as ad campaigns and listing promotions
|
|
10
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing.readonly',
|
|
11
|
+
// View and manage your eBay marketing activities, such as ad campaigns and listing promotions
|
|
12
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing',
|
|
13
|
+
// View your inventory and offers
|
|
14
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
15
|
+
// View and manage your inventory and offers
|
|
16
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
17
|
+
// View your account settings
|
|
18
|
+
'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
|
|
19
|
+
// View and manage your account settings
|
|
20
|
+
'https://api.ebay.com/oauth/api_scope/sell.account',
|
|
21
|
+
// View your order fulfillments
|
|
22
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
23
|
+
// View and manage your order fulfillments
|
|
24
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
25
|
+
// View your selling analytics data, such as performance reports
|
|
26
|
+
'https://api.ebay.com/oauth/api_scope/sell.analytics.readonly',
|
|
27
|
+
// View and manage your payment and order information to display this information to you and allow you to initiate refunds using the third party application
|
|
28
|
+
'https://api.ebay.com/oauth/api_scope/sell.finances',
|
|
29
|
+
// View and manage disputes and related details (including payment and order information).
|
|
30
|
+
'https://api.ebay.com/oauth/api_scope/sell.payment.dispute',
|
|
31
|
+
// View a user's basic information, such as username or business account details, from their eBay member account
|
|
32
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.readonly',
|
|
33
|
+
// View and manage your reputation data, such as feedback.
|
|
34
|
+
'https://api.ebay.com/oauth/api_scope/sell.reputation',
|
|
35
|
+
// View your reputation data, such as feedback.
|
|
36
|
+
'https://api.ebay.com/oauth/api_scope/sell.reputation.readonly',
|
|
37
|
+
// View and manage your event notification subscriptions
|
|
38
|
+
'https://api.ebay.com/oauth/api_scope/commerce.notification.subscription',
|
|
39
|
+
// View your event notification subscriptions
|
|
40
|
+
'https://api.ebay.com/oauth/api_scope/commerce.notification.subscription.readonly',
|
|
41
|
+
// View and manage eBay stores
|
|
42
|
+
'https://api.ebay.com/oauth/api_scope/sell.stores',
|
|
43
|
+
// View eBay stores
|
|
44
|
+
'https://api.ebay.com/oauth/api_scope/sell.stores.readonly',
|
|
45
|
+
// Allows access to eDelivery International Shipping APIs.
|
|
46
|
+
'https://api.ebay.com/oauth/scope/sell.edelivery',
|
|
47
|
+
// Allows access to APIs that are related to eBay's Verified Rights Owner (VeRO) program.
|
|
48
|
+
'https://api.ebay.com/oauth/api_scope/commerce.vero',
|
|
49
|
+
// Enables applications to manage and enhance inventory listings through the Inventory Mapping Public API.
|
|
50
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.mapping',
|
|
51
|
+
// Allows access to eBay Message APIs.
|
|
52
|
+
'https://api.ebay.com/oauth/api_scope/commerce.message',
|
|
53
|
+
// Allows access to Feedback APIs.
|
|
54
|
+
'https://api.ebay.com/oauth/api_scope/commerce.feedback',
|
|
55
|
+
// View and manage shipping information
|
|
56
|
+
'https://api.ebay.com/oauth/api_scope/commerce.shipping',
|
|
57
|
+
// Allows readonly access to Feedback APIs.
|
|
58
|
+
'https://api.ebay.com/oauth/api_scope/commerce.feedback.readonly',
|
|
59
|
+
];
|
|
60
|
+
/** Scopes available to apps in the eBay sandbox environment. */
|
|
61
|
+
export const sandboxScopes = [
|
|
62
|
+
// View public data from eBay
|
|
63
|
+
'https://api.ebay.com/oauth/api_scope',
|
|
64
|
+
// View your order details
|
|
65
|
+
'https://api.ebay.com/oauth/api_scope/buy.order.readonly',
|
|
66
|
+
// Purchase eBay items off eBay
|
|
67
|
+
'https://api.ebay.com/oauth/api_scope/buy.guest.order',
|
|
68
|
+
// View your eBay marketing activities, such as ad campaigns and listing promotions
|
|
69
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing.readonly',
|
|
70
|
+
// View and manage your eBay marketing activities, such as ad campaigns and listing promotions
|
|
71
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketing',
|
|
72
|
+
// View your inventory and offers
|
|
73
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
|
|
74
|
+
// View and manage your inventory and offers
|
|
75
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
76
|
+
// View your account settings
|
|
77
|
+
'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
|
|
78
|
+
// View and manage your account settings
|
|
79
|
+
'https://api.ebay.com/oauth/api_scope/sell.account',
|
|
80
|
+
// View your order fulfillments
|
|
81
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
|
|
82
|
+
// View and manage your order fulfillments
|
|
83
|
+
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
|
|
84
|
+
// View your selling analytics data, such as performance reports
|
|
85
|
+
'https://api.ebay.com/oauth/api_scope/sell.analytics.readonly',
|
|
86
|
+
// This scope would allow signed in users read only access to marketplace insights.
|
|
87
|
+
'https://api.ebay.com/oauth/api_scope/sell.marketplace.insights.readonly',
|
|
88
|
+
// This scope would allow signed in user to read catalog data.
|
|
89
|
+
'https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly',
|
|
90
|
+
// This scope would allow signed in user to access shopping carts
|
|
91
|
+
'https://api.ebay.com/oauth/api_scope/buy.shopping.cart',
|
|
92
|
+
// View and manage bidding activities for auctions
|
|
93
|
+
'https://api.ebay.com/oauth/api_scope/buy.offer.auction',
|
|
94
|
+
// View a user's basic information, such as username or business account details, from their eBay member account
|
|
95
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.readonly',
|
|
96
|
+
// View a user's personal email information from their eBay member account
|
|
97
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.email.readonly',
|
|
98
|
+
// View a user's personal telephone information from their eBay member account
|
|
99
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.phone.readonly',
|
|
100
|
+
// View a user's personal address information from their eBay member account
|
|
101
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.address.readonly',
|
|
102
|
+
// View a user's first and last name from their eBay member account
|
|
103
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.name.readonly',
|
|
104
|
+
// View a user's eBay member account status
|
|
105
|
+
'https://api.ebay.com/oauth/api_scope/commerce.identity.status.readonly',
|
|
106
|
+
// View and manage your payment and order information to display this information to you and allow you to initiate refunds using the third party application
|
|
107
|
+
'https://api.ebay.com/oauth/api_scope/sell.finances',
|
|
108
|
+
// View and manage disputes and related details (including payment and order information).
|
|
109
|
+
'https://api.ebay.com/oauth/api_scope/sell.payment.dispute',
|
|
110
|
+
// View and manage your item drafts.
|
|
111
|
+
'https://api.ebay.com/oauth/api_scope/sell.item.draft',
|
|
112
|
+
// View and manage your item information.
|
|
113
|
+
'https://api.ebay.com/oauth/api_scope/sell.item',
|
|
114
|
+
// View and manage your reputation data, such as feedback.
|
|
115
|
+
'https://api.ebay.com/oauth/api_scope/sell.reputation',
|
|
116
|
+
// View your reputation data, such as feedback.
|
|
117
|
+
'https://api.ebay.com/oauth/api_scope/sell.reputation.readonly',
|
|
118
|
+
// View and manage your event notification subscriptions
|
|
119
|
+
'https://api.ebay.com/oauth/api_scope/commerce.notification.subscription',
|
|
120
|
+
// View your event notification subscriptions
|
|
121
|
+
'https://api.ebay.com/oauth/api_scope/commerce.notification.subscription.readonly',
|
|
122
|
+
// View and manage eBay stores
|
|
123
|
+
'https://api.ebay.com/oauth/api_scope/sell.stores',
|
|
124
|
+
// View eBay stores
|
|
125
|
+
'https://api.ebay.com/oauth/api_scope/sell.stores.readonly',
|
|
126
|
+
// Allows access to APIs that are related to eBay's Verified Rights Owner (VeRO) program.
|
|
127
|
+
'https://api.ebay.com/oauth/api_scope/commerce.vero',
|
|
128
|
+
// Allows access to Feedback APIs.
|
|
129
|
+
'https://api.ebay.com/oauth/api_scope/commerce.feedback',
|
|
130
|
+
// Enables applications to manage and enhance inventory listings through the Inventory Mapping Public API.
|
|
131
|
+
'https://api.ebay.com/oauth/api_scope/sell.inventory.mapping',
|
|
132
|
+
];
|
package/build/index.js
CHANGED
|
@@ -12,13 +12,13 @@ import { Effect, Either } from 'effect';
|
|
|
12
12
|
import process from 'node:process';
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const runCliCommand = async (label, command) => {
|
|
15
|
-
const
|
|
15
|
+
const commandOutcome = await Effect.runPromise(Effect.either(Effect.tryPromise({
|
|
16
16
|
try: command,
|
|
17
17
|
catch: (error) => error,
|
|
18
18
|
})));
|
|
19
|
-
if (Either.isLeft(
|
|
19
|
+
if (Either.isLeft(commandOutcome)) {
|
|
20
20
|
serverLogger.error(`${label} failed`, {
|
|
21
|
-
error: getErrorMessage(
|
|
21
|
+
error: getErrorMessage(commandOutcome.left, String(commandOutcome.left)),
|
|
22
22
|
});
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
@@ -68,8 +68,8 @@ class EbayMcpServer {
|
|
|
68
68
|
// Validate environment configuration
|
|
69
69
|
const validation = validateEnvironmentConfig();
|
|
70
70
|
// Log informational notices (e.g. proxy auth mode)
|
|
71
|
-
validation.infos.forEach((
|
|
72
|
-
serverLogger.info(
|
|
71
|
+
validation.infos.forEach((notice) => {
|
|
72
|
+
serverLogger.info(notice);
|
|
73
73
|
});
|
|
74
74
|
// Log warnings
|
|
75
75
|
if (validation.warnings.length > 0) {
|
|
@@ -15,6 +15,17 @@ import { getErrorMessage } from '../utils/errors.js';
|
|
|
15
15
|
import { serverLogger } from '../utils/logger.js';
|
|
16
16
|
import { getVersion } from '../utils/version.js';
|
|
17
17
|
import { Effect, Either } from 'effect';
|
|
18
|
+
const LOOPBACK_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
|
|
19
|
+
const parseCorsOrigins = (value) => {
|
|
20
|
+
const origins = (value || '')
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((origin) => origin.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
if (origins.length === 0) {
|
|
25
|
+
return [LOOPBACK_ORIGIN];
|
|
26
|
+
}
|
|
27
|
+
return origins;
|
|
28
|
+
};
|
|
18
29
|
const getProjectRoot = () => {
|
|
19
30
|
const filename = fileURLToPath(import.meta.url);
|
|
20
31
|
const directory = dirname(filename);
|
|
@@ -46,6 +57,7 @@ export const createHttpTransportConfigFromEnv = (env) => ({
|
|
|
46
57
|
useIntrospection: env.OAUTH_USE_INTROSPECTION !== 'false',
|
|
47
58
|
},
|
|
48
59
|
authEnabled: env.OAUTH_ENABLED !== 'false',
|
|
60
|
+
corsOrigins: parseCorsOrigins(env.MCP_CORS_ORIGINS),
|
|
49
61
|
projectRoot: getProjectRoot(),
|
|
50
62
|
staticAuthToken: env.MCP_AUTH_TOKEN,
|
|
51
63
|
});
|
|
@@ -114,12 +126,12 @@ async function createAuthMiddleware(config, serverUrl) {
|
|
|
114
126
|
if (config.staticAuthToken) {
|
|
115
127
|
const staticToken = config.staticAuthToken;
|
|
116
128
|
serverLogger.info('Static bearer auth enabled via MCP_AUTH_TOKEN.');
|
|
117
|
-
return (
|
|
118
|
-
if (
|
|
129
|
+
return (request, response, next) => {
|
|
130
|
+
if (request.headers.authorization === `Bearer ${staticToken}`) {
|
|
119
131
|
next();
|
|
120
132
|
return;
|
|
121
133
|
}
|
|
122
|
-
|
|
134
|
+
response.status(401).json({
|
|
123
135
|
error: 'unauthorized',
|
|
124
136
|
error_description: 'Missing or invalid bearer token',
|
|
125
137
|
});
|
|
@@ -154,17 +166,17 @@ async function createAuthMiddleware(config, serverUrl) {
|
|
|
154
166
|
});
|
|
155
167
|
}
|
|
156
168
|
function createRequestLogger() {
|
|
157
|
-
return (
|
|
169
|
+
return (request, response, next) => {
|
|
158
170
|
const start = Date.now();
|
|
159
|
-
|
|
171
|
+
response.on('finish', () => {
|
|
160
172
|
const duration = Date.now() - start;
|
|
161
|
-
serverLogger.http(`${
|
|
173
|
+
serverLogger.http(`${request.method} ${request.path} -> ${response.statusCode} (${duration}ms)`);
|
|
162
174
|
});
|
|
163
175
|
next();
|
|
164
176
|
};
|
|
165
177
|
}
|
|
166
|
-
function sendInvalidSession(
|
|
167
|
-
|
|
178
|
+
function sendInvalidSession(response) {
|
|
179
|
+
response.status(400).json({
|
|
168
180
|
error: 'invalid_session',
|
|
169
181
|
error_description: 'Invalid or missing session ID',
|
|
170
182
|
});
|
|
@@ -185,7 +197,7 @@ export const createHttpMcpApp = async (config) => {
|
|
|
185
197
|
const serverUrl = getHttpServerUrl(config);
|
|
186
198
|
const ebayConfig = config.ebayConfig ?? getEbayConfig();
|
|
187
199
|
app.use(cors({
|
|
188
|
-
origin:
|
|
200
|
+
origin: config.corsOrigins,
|
|
189
201
|
exposedHeaders: ['Mcp-Session-Id'],
|
|
190
202
|
}));
|
|
191
203
|
app.use(express.json());
|
|
@@ -201,8 +213,8 @@ export const createHttpMcpApp = async (config) => {
|
|
|
201
213
|
ebayEnvironment: ebayConfig.environment,
|
|
202
214
|
ebayScopes: getDefaultScopes(ebayConfig.environment),
|
|
203
215
|
}));
|
|
204
|
-
app.get('/health', (
|
|
205
|
-
|
|
216
|
+
app.get('/health', (_request, response) => {
|
|
217
|
+
response.json({
|
|
206
218
|
status: 'healthy',
|
|
207
219
|
timestamp: new Date().toISOString(),
|
|
208
220
|
oauth_enabled: config.authEnabled,
|
|
@@ -210,14 +222,14 @@ export const createHttpMcpApp = async (config) => {
|
|
|
210
222
|
});
|
|
211
223
|
const authMiddleware = await createAuthMiddleware(config, serverUrl);
|
|
212
224
|
const transports = new Map();
|
|
213
|
-
const mcpPostHandler = async (
|
|
214
|
-
const sessionHeader =
|
|
225
|
+
const mcpPostHandler = async (request, response) => {
|
|
226
|
+
const sessionHeader = request.headers['mcp-session-id'];
|
|
215
227
|
const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined;
|
|
216
228
|
let transport;
|
|
217
229
|
if (sessionId && transports.has(sessionId)) {
|
|
218
230
|
transport = transports.get(sessionId);
|
|
219
231
|
}
|
|
220
|
-
else if (!sessionId && isInitializeRequest(
|
|
232
|
+
else if (!sessionId && isInitializeRequest(request.body)) {
|
|
221
233
|
transport = new StreamableHTTPServerTransport({
|
|
222
234
|
sessionIdGenerator: () => randomUUID(),
|
|
223
235
|
onsessioninitialized: (initializedSessionId) => {
|
|
@@ -235,7 +247,7 @@ export const createHttpMcpApp = async (config) => {
|
|
|
235
247
|
await server.connect(transport);
|
|
236
248
|
}
|
|
237
249
|
else {
|
|
238
|
-
|
|
250
|
+
response.status(400).json({
|
|
239
251
|
jsonrpc: '2.0',
|
|
240
252
|
error: {
|
|
241
253
|
code: -32_000,
|
|
@@ -245,17 +257,17 @@ export const createHttpMcpApp = async (config) => {
|
|
|
245
257
|
});
|
|
246
258
|
return;
|
|
247
259
|
}
|
|
248
|
-
await transport.handleRequest(
|
|
260
|
+
await transport.handleRequest(request, response, request.body);
|
|
249
261
|
};
|
|
250
|
-
const handleSessionRequest = async (
|
|
251
|
-
const sessionHeader =
|
|
262
|
+
const handleSessionRequest = async (request, response) => {
|
|
263
|
+
const sessionHeader = request.headers['mcp-session-id'];
|
|
252
264
|
const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined;
|
|
253
265
|
if (!(sessionId && transports.has(sessionId))) {
|
|
254
|
-
sendInvalidSession(
|
|
266
|
+
sendInvalidSession(response);
|
|
255
267
|
return;
|
|
256
268
|
}
|
|
257
269
|
const transport = transports.get(sessionId);
|
|
258
|
-
await transport.handleRequest(
|
|
270
|
+
await transport.handleRequest(request, response);
|
|
259
271
|
};
|
|
260
272
|
const mcpMiddleware = authMiddleware ? [authMiddleware, mcpPostHandler] : [mcpPostHandler];
|
|
261
273
|
const sessionMiddleware = authMiddleware
|