ebay-mcp-remote-edition 2.0.14 → 2.0.15

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.
@@ -88,7 +88,9 @@ export class CloudflareKVStore {
88
88
  await this.client.put(`/values/${encodeURIComponent(key)}`, JSON.stringify(value), { params });
89
89
  // Keep the in-memory cache consistent with what we wrote.
90
90
  // If the KV entry has its own TTL, honour it for the cache as well.
91
- const cacheTtl = expirationTtl ? Math.min(expirationTtl * 1_000, this.cacheTtlMs) : this.cacheTtlMs;
91
+ const cacheTtl = expirationTtl
92
+ ? Math.min(expirationTtl * 1_000, this.cacheTtlMs)
93
+ : this.cacheTtlMs;
92
94
  this.cache.set(key, { value, expiresAt: Date.now() + cacheTtl });
93
95
  }
94
96
  async delete(key) {
@@ -118,6 +118,37 @@ export class MultiUserAuthStore {
118
118
  await this.kv.put(`client:${clientId}`, record);
119
119
  return record;
120
120
  }
121
+ /**
122
+ * Upserts a client record using a **caller-supplied** `clientId`.
123
+ *
124
+ * Used by the `/authorize` endpoint to auto-register trusted desktop MCP
125
+ * clients (VS Code, Cursor, Windsurf, localhost loopback) that arrive at
126
+ * `/authorize` without a prior `/register` call (e.g. because the in-memory
127
+ * registration was lost between requests, or the client drives `/authorize`
128
+ * directly).
129
+ *
130
+ * An existing record for `clientId` is overwritten only if the supplied
131
+ * `redirectUri` is not already listed (additive merge otherwise).
132
+ */
133
+ async registerClientWithId(clientId, redirectUris, clientName) {
134
+ const existing = await this.kv.get(`client:${clientId}`);
135
+ const now = new Date().toISOString();
136
+ if (existing) {
137
+ // Merge any new redirect URIs into the existing record
138
+ const merged = Array.from(new Set([...existing.redirectUris, ...redirectUris]));
139
+ const updated = { ...existing, redirectUris: merged };
140
+ await this.kv.put(`client:${clientId}`, updated);
141
+ return updated;
142
+ }
143
+ const record = {
144
+ clientId,
145
+ redirectUris,
146
+ clientName,
147
+ createdAt: now,
148
+ };
149
+ await this.kv.put(`client:${clientId}`, record);
150
+ return record;
151
+ }
121
152
  async getClient(clientId) {
122
153
  return await this.kv.get(`client:${clientId}`);
123
154
  }
@@ -61,6 +61,36 @@ function requireOauthStartKey(req, res, next) {
61
61
  }
62
62
  next();
63
63
  }
64
+ /**
65
+ * Returns true when a redirect URI belongs to a well-known desktop / IDE
66
+ * MCP client (VS Code, Cursor, Windsurf) or a localhost loopback.
67
+ *
68
+ * These clients drive the authorize flow directly and cannot always guarantee
69
+ * that their /register request was persisted before /authorize is called.
70
+ * We allow them to self-register on the fly so the flow doesn't hard-fail on
71
+ * a missing client_id when the user's eBay app credentials are already in env.
72
+ */
73
+ function isTrustedDesktopRedirectUri(redirectUri) {
74
+ try {
75
+ const u = new URL(redirectUri);
76
+ // Desktop IDE callback schemes
77
+ if (u.protocol === 'vscode:' ||
78
+ u.protocol === 'cursor:' ||
79
+ u.protocol === 'windsurf:' ||
80
+ u.protocol === 'claude:') {
81
+ return true;
82
+ }
83
+ // Localhost loopback (any port)
84
+ if ((u.protocol === 'http:' || u.protocol === 'https:') &&
85
+ (u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '::1')) {
86
+ return true;
87
+ }
88
+ }
89
+ catch {
90
+ // Malformed URI – treat as untrusted
91
+ }
92
+ return false;
93
+ }
64
94
  async function createUserScopedApi(userId, environment) {
65
95
  const api = new EbaySellerApi(getEbayConfig(environment), { userId, environment });
66
96
  await api.initialize();
@@ -161,10 +191,20 @@ function createApp() {
161
191
  .json({ error: 'invalid_request', error_description: 'client_id is required' });
162
192
  return;
163
193
  }
164
- const client = await authStore.getClient(clientId);
194
+ // Look up the MCP client. If unknown, auto-register it for trusted desktop
195
+ // redirect URIs (VS Code, Cursor, Windsurf, localhost) so that the flow
196
+ // continues even when /register state was not persisted (e.g. memory backend)
197
+ // or when the IDE drives /authorize directly without a prior /register call.
198
+ let client = await authStore.getClient(clientId);
165
199
  if (!client) {
166
- res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
167
- return;
200
+ if (redirectUri && isTrustedDesktopRedirectUri(redirectUri)) {
201
+ serverLogger.info('[authorize] Auto-registering trusted desktop MCP client (client_id not in store)', { clientId, redirectUri });
202
+ client = await authStore.registerClientWithId(clientId, [redirectUri]);
203
+ }
204
+ else {
205
+ res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
206
+ return;
207
+ }
168
208
  }
169
209
  if (!redirectUri || !client.redirectUris.includes(redirectUri)) {
170
210
  res.status(400).json({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ebay-mcp-remote-edition",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Remote + Local MCP server for eBay APIs - provides access to eBay developer functionality through MCP (Model Context Protocol)",
5
5
  "type": "module",
6
6
  "main": "build/index.js",