ebay-mcp 1.8.9 → 1.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/mcp/http-transport.js +217 -0
- package/build/mcp/runtime.js +71 -0
- package/package.json +2 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
6
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { createBearerAuthMiddleware } from '../auth/oauth-middleware.js';
|
|
10
|
+
import { createMetadataRouter, getProtectedResourceMetadataUrl } from '../auth/oauth-metadata.js';
|
|
11
|
+
import { TokenVerifier } from '../auth/token-verifier.js';
|
|
12
|
+
import { getDefaultScopes, getEbayConfig } from '../config/environment.js';
|
|
13
|
+
import { createEbayMcpRuntime } from '../mcp/runtime.js';
|
|
14
|
+
import { getVersion } from '../utils/version.js';
|
|
15
|
+
function getProjectRoot() {
|
|
16
|
+
const filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const directory = dirname(filename);
|
|
18
|
+
return join(directory, '../..');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build HTTP transport configuration from process environment variables.
|
|
22
|
+
*/
|
|
23
|
+
export function createHttpTransportConfigFromEnv(env = process.env) {
|
|
24
|
+
return {
|
|
25
|
+
host: env.MCP_HOST || 'localhost',
|
|
26
|
+
port: Number(env.MCP_PORT) || 3000,
|
|
27
|
+
oauth: {
|
|
28
|
+
authServerUrl: env.OAUTH_AUTH_SERVER_URL ?? 'http://localhost:8080/realms/master',
|
|
29
|
+
clientId: env.OAUTH_CLIENT_ID,
|
|
30
|
+
clientSecret: env.OAUTH_CLIENT_SECRET,
|
|
31
|
+
requiredScopes: (env.OAUTH_REQUIRED_SCOPES || 'mcp:tools')
|
|
32
|
+
.split(',')
|
|
33
|
+
.map((scope) => scope.trim()),
|
|
34
|
+
useIntrospection: env.OAUTH_USE_INTROSPECTION !== 'false',
|
|
35
|
+
},
|
|
36
|
+
authEnabled: env.OAUTH_ENABLED !== 'false',
|
|
37
|
+
projectRoot: getProjectRoot(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the public server URL used for MCP metadata and OAuth audience checks.
|
|
42
|
+
*/
|
|
43
|
+
export function getHttpServerUrl(config) {
|
|
44
|
+
return `http://${config.host}:${config.port}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the OAuth authorization-server metadata endpoint for the configured issuer.
|
|
48
|
+
*/
|
|
49
|
+
export function getAuthServerMetadataUrl(config) {
|
|
50
|
+
const baseUrl = config.oauth.authServerUrl;
|
|
51
|
+
if (baseUrl.includes('/realms/')) {
|
|
52
|
+
return `${baseUrl}/.well-known/openid-configuration`;
|
|
53
|
+
}
|
|
54
|
+
return `${baseUrl}/.well-known/oauth-authorization-server`;
|
|
55
|
+
}
|
|
56
|
+
function createServerConfig(serverUrl) {
|
|
57
|
+
const iconBaseUrl = `${serverUrl}/icons`;
|
|
58
|
+
const iconSizes = ['16x16', '32x32', '48x48', '128x128', '256x256', '512x512', '1024x1024'];
|
|
59
|
+
return {
|
|
60
|
+
name: 'ebay-mcp',
|
|
61
|
+
version: getVersion(),
|
|
62
|
+
title: 'eBay API MCP Server',
|
|
63
|
+
websiteUrl: 'https://github.com/YosefHayim/ebay-mcp',
|
|
64
|
+
icons: iconSizes.map((size) => ({
|
|
65
|
+
src: `${iconBaseUrl}/${size}.png`,
|
|
66
|
+
mimeType: 'image/png',
|
|
67
|
+
sizes: [size],
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function createMcpServer(serverUrl) {
|
|
72
|
+
const runtime = createEbayMcpRuntime({
|
|
73
|
+
serverConfig: createServerConfig(serverUrl),
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
await runtime.initializeApi();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.error(`Failed to initialize eBay API client: ${message}`);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
return runtime.server;
|
|
84
|
+
}
|
|
85
|
+
async function createAuthMiddleware(config, serverUrl) {
|
|
86
|
+
if (!config.authEnabled) {
|
|
87
|
+
console.error('OAuth is disabled. Server running in unauthenticated mode.');
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
console.log('Initializing OAuth token verifier...');
|
|
91
|
+
const tokenVerifier = new TokenVerifier({
|
|
92
|
+
authServerMetadata: getAuthServerMetadataUrl(config),
|
|
93
|
+
clientId: config.oauth.clientId,
|
|
94
|
+
clientSecret: config.oauth.clientSecret,
|
|
95
|
+
expectedAudience: serverUrl,
|
|
96
|
+
requiredScopes: config.oauth.requiredScopes,
|
|
97
|
+
useIntrospection: config.oauth.useIntrospection,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
await tokenVerifier.initialize();
|
|
101
|
+
console.log('Token verifier initialized');
|
|
102
|
+
return createBearerAuthMiddleware({
|
|
103
|
+
verifier: tokenVerifier,
|
|
104
|
+
resourceMetadataUrl: getProtectedResourceMetadataUrl(serverUrl),
|
|
105
|
+
realm: 'ebay-mcp',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('Failed to initialize token verifier:', error);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function createRequestLogger() {
|
|
114
|
+
return (req, res, next) => {
|
|
115
|
+
const start = Date.now();
|
|
116
|
+
res.on('finish', () => {
|
|
117
|
+
const duration = Date.now() - start;
|
|
118
|
+
console.log(`${req.method} ${req.path} -> ${res.statusCode} (${duration}ms)`);
|
|
119
|
+
});
|
|
120
|
+
next();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function sendInvalidSession(res) {
|
|
124
|
+
res.status(400).json({
|
|
125
|
+
error: 'invalid_session',
|
|
126
|
+
error_description: 'Invalid or missing session ID',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create the Express app that serves OAuth metadata and streamable HTTP MCP sessions.
|
|
131
|
+
*/
|
|
132
|
+
export async function createHttpMcpApp(config) {
|
|
133
|
+
const app = express();
|
|
134
|
+
const serverUrl = getHttpServerUrl(config);
|
|
135
|
+
const ebayConfig = config.ebayConfig ?? getEbayConfig();
|
|
136
|
+
app.use(cors({
|
|
137
|
+
origin: '*',
|
|
138
|
+
exposedHeaders: ['Mcp-Session-Id'],
|
|
139
|
+
}));
|
|
140
|
+
app.use(express.json());
|
|
141
|
+
app.use(helmet({ xPoweredBy: false }));
|
|
142
|
+
app.use(createRequestLogger());
|
|
143
|
+
app.use('/icons', express.static(join(config.projectRoot, 'public', 'icons')));
|
|
144
|
+
app.use(createMetadataRouter({
|
|
145
|
+
resourceServerUrl: serverUrl,
|
|
146
|
+
authServerMetadata: getAuthServerMetadataUrl(config),
|
|
147
|
+
scopesSupported: config.oauth.requiredScopes,
|
|
148
|
+
resourceDocumentation: 'https://github.com/YosefHayim/ebay-mcp',
|
|
149
|
+
resourceName: 'eBay API MCP Server',
|
|
150
|
+
ebayEnvironment: ebayConfig.environment,
|
|
151
|
+
ebayScopes: getDefaultScopes(ebayConfig.environment),
|
|
152
|
+
}));
|
|
153
|
+
app.get('/health', (_req, res) => {
|
|
154
|
+
res.json({
|
|
155
|
+
status: 'healthy',
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
oauth_enabled: config.authEnabled,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
const authMiddleware = await createAuthMiddleware(config, serverUrl);
|
|
161
|
+
const transports = new Map();
|
|
162
|
+
const mcpPostHandler = async (req, res) => {
|
|
163
|
+
const sessionHeader = req.headers['mcp-session-id'];
|
|
164
|
+
const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined;
|
|
165
|
+
let transport;
|
|
166
|
+
if (sessionId && transports.has(sessionId)) {
|
|
167
|
+
transport = transports.get(sessionId);
|
|
168
|
+
}
|
|
169
|
+
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
170
|
+
transport = new StreamableHTTPServerTransport({
|
|
171
|
+
sessionIdGenerator: () => randomUUID(),
|
|
172
|
+
onsessioninitialized: (initializedSessionId) => {
|
|
173
|
+
transports.set(initializedSessionId, transport);
|
|
174
|
+
console.log(`New MCP session initialized: ${initializedSessionId}`);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
transport.onclose = () => {
|
|
178
|
+
if (transport.sessionId) {
|
|
179
|
+
transports.delete(transport.sessionId);
|
|
180
|
+
console.log(`MCP session closed: ${transport.sessionId}`);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const server = await createMcpServer(serverUrl);
|
|
184
|
+
await server.connect(transport);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
res.status(400).json({
|
|
188
|
+
jsonrpc: '2.0',
|
|
189
|
+
error: {
|
|
190
|
+
code: -32000,
|
|
191
|
+
message: 'Bad Request: No valid session ID provided',
|
|
192
|
+
},
|
|
193
|
+
id: null,
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
await transport.handleRequest(req, res, req.body);
|
|
198
|
+
};
|
|
199
|
+
const handleSessionRequest = async (req, res) => {
|
|
200
|
+
const sessionHeader = req.headers['mcp-session-id'];
|
|
201
|
+
const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined;
|
|
202
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
203
|
+
sendInvalidSession(res);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const transport = transports.get(sessionId);
|
|
207
|
+
await transport.handleRequest(req, res);
|
|
208
|
+
};
|
|
209
|
+
const mcpMiddleware = authMiddleware ? [authMiddleware, mcpPostHandler] : [mcpPostHandler];
|
|
210
|
+
const sessionMiddleware = authMiddleware
|
|
211
|
+
? [authMiddleware, handleSessionRequest]
|
|
212
|
+
: [handleSessionRequest];
|
|
213
|
+
app.post('/', ...mcpMiddleware);
|
|
214
|
+
app.get('/', ...sessionMiddleware);
|
|
215
|
+
app.delete('/', ...sessionMiddleware);
|
|
216
|
+
return app;
|
|
217
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { EbaySellerApi } from '../api/index.js';
|
|
3
|
+
import { getEbayConfig, mcpConfig } from '../config/environment.js';
|
|
4
|
+
import { getToolEntries } from '../tools/registry.js';
|
|
5
|
+
import { serverLogger, toolLogger } from '../utils/logger.js';
|
|
6
|
+
function formatToolSuccess(result) {
|
|
7
|
+
return {
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: JSON.stringify(result, null, 2),
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function formatToolFailure(error) {
|
|
17
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: 'text',
|
|
22
|
+
text: JSON.stringify({ error: errorMessage }, null, 2),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function registerTool(server, api, entry, logToolExecution) {
|
|
29
|
+
const { definition, handler } = entry;
|
|
30
|
+
server.registerTool(definition.name, {
|
|
31
|
+
description: definition.description,
|
|
32
|
+
inputSchema: definition.inputSchema,
|
|
33
|
+
}, async (args) => {
|
|
34
|
+
if (logToolExecution) {
|
|
35
|
+
toolLogger.debug(`Executing tool: ${definition.name}`, { args });
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = await handler(api, args);
|
|
39
|
+
if (logToolExecution) {
|
|
40
|
+
toolLogger.debug(`Tool ${definition.name} completed successfully`);
|
|
41
|
+
}
|
|
42
|
+
return formatToolSuccess(result);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
46
|
+
if (logToolExecution) {
|
|
47
|
+
toolLogger.error(`Tool ${definition.name} failed`, { error: errorMessage });
|
|
48
|
+
}
|
|
49
|
+
return formatToolFailure(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create an MCP server runtime and register all eBay tool handlers.
|
|
55
|
+
*/
|
|
56
|
+
export function createEbayMcpRuntime(options = {}) {
|
|
57
|
+
const api = options.api ?? new EbaySellerApi(getEbayConfig());
|
|
58
|
+
const server = new McpServer(options.serverConfig ?? mcpConfig);
|
|
59
|
+
const entries = getToolEntries();
|
|
60
|
+
serverLogger.info(`Registering ${entries.length} tools`);
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
registerTool(server, api, entry, options.logToolExecution ?? false);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
api,
|
|
66
|
+
server,
|
|
67
|
+
async initializeApi() {
|
|
68
|
+
await api.initialize();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebay-mcp",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.10",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server that exposes 100% of eBay's Sell APIs (325 tools across 270 endpoints) to AI assistants like Claude, Cursor, and Cline, covering inventory, order fulfillment, marketing, analytics, and developer tools with OAuth authentication and refresh-token support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"build/api/**/*.js",
|
|
30
30
|
"build/auth/**/*.js",
|
|
31
31
|
"build/config/**/*.js",
|
|
32
|
+
"build/mcp/**/*.js",
|
|
32
33
|
"build/schemas/**/*.js",
|
|
33
34
|
"build/scripts/**/*.js",
|
|
34
35
|
"build/tools/**/*.js",
|