ebay-mcp-remote-edition 4.5.1 → 4.7.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.
package/README.md CHANGED
@@ -16,23 +16,21 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server providi
16
16
 
17
17
  ## Overview
18
18
 
19
- This project extends [Yosef Hayim's eBay MCP](https://github.com/YosefHayim/ebay-mcp) with a full hosted, multi-user deployment mode while keeping the original local STDIO mode intact. The key additions are:
19
+ This project extends [Yosef Hayim's eBay MCP](https://github.com/YosefHayim/ebay-mcp) with hosted, multi-user deployment while preserving local STDIO mode. Key additions:
20
20
 
21
- - **Hosted Streamable HTTP mode** — deploy to Render (or any Node.js host) and serve multiple users from one instance
22
- - **MCP OAuth 2.1 authorization server** — full browser-based eBay login with automatic token management; Cline and other OAuth-aware clients connect with zero manual token pasting
23
- - **Environment-scoped route trees** — `/sandbox/mcp` and `/production/mcp` hard-bind their eBay environment; no query param needed
24
- - **Cloudflare KV / Upstash Redis** token and session storage for persistent multi-user auth
21
+ - **Hosted Streamable HTTP** — deploy anywhere, serve multiple users from one instance
22
+ - **MCP OAuth 2.1** — browser-based eBay login with automatic token management; OAuth-aware clients (Cline) connect without manual token pasting
23
+ - **Environment-scoped routes** — `/sandbox/mcp` and `/production/mcp` hard-bind their eBay environment
24
+ - **Cloudflare KV / Upstash Redis** — persistent multi-user token and session storage with TTL-aligned expiry
25
25
  - **Admin session management** — inspect, revoke, or delete sessions via authenticated endpoints
26
- - **TTL-aligned records** — every stored record (OAuth state, auth code, session, user token) carries an `expiresAt` timestamp and a matching KV/Redis TTL so storage and application expiry are always in sync
27
- - **Env-selected eBay Research session persistence** — the first-party research bootstrap/runtime can persist Playwright storage state to Cloudflare KV, Upstash KV, or explicit filesystem mode via `EBAY_RESEARCH_SESSION_STORE`
28
- - **QStash-triggered Telegram alerts for eBay Research session expiry** — bootstrap can schedule version-aware expiry callbacks that notify operators before first-party research auth silently degrades
29
- - **Alert-safe scheduling guardrails** — expiry callbacks are only scheduled when the callback URL is externally reachable and the research session store supports shared alert locks (`upstash-redis` or `filesystem`)
26
+ - **eBay Research session persistence** — Playwright storage state to KV/Redis/filesystem via `EBAY_RESEARCH_SESSION_STORE`
27
+ - **QStash-triggered Telegram alerts** — expiry callbacks notify operators before research auth degrades
30
28
 
31
29
  ---
32
30
 
33
31
  ## ⚠️ Disclaimer
34
32
 
35
- This is an open-source project provided "as is" without warranty of any kind. The authors accept **no responsibility or liability** for any damages arising from use of this software. This project is **not affiliated with, endorsed by, or sponsored by eBay Inc.** Test thoroughly in eBay's sandbox before using in production.
33
+ This is an open-source project provided "as is" without warranty of any kind. Not affiliated with, endorsed by, or sponsored by eBay Inc. Test thoroughly in sandbox before production.
36
34
 
37
35
  ---
38
36
 
@@ -41,21 +39,8 @@ This is an open-source project provided "as is" without warranty of any kind. Th
41
39
  - [Choose a runtime mode](#choose-a-runtime-mode)
42
40
  - [Prerequisites](#prerequisites)
43
41
  - [Local mode setup](#local-mode-setup)
44
- - [Install](#install)
45
- - [Configure credentials](#configure-credentials)
46
- - [Run the setup wizard](#run-the-setup-wizard)
47
- - [Local client configuration](#local-client-configuration)
48
42
  - [Hosted mode setup](#hosted-mode-setup)
49
- - [Environment variables](#hosted-environment-variables)
50
- - [Secret file](#secret-file)
51
- - [Deploy to Render](#deploy-to-render)
52
- - [OAuth flows](#oauth-flows)
53
- - [MCP endpoints](#mcp-endpoints)
54
- - [Validation architecture](#validation-architecture)
55
- - [Validation endpoints and auth model](#validation-endpoints-and-auth-model)
56
- - [Diagnostics and health endpoints](#diagnostics-and-health-endpoints)
57
- - [Validation provider behavior and limitations](#validation-provider-behavior-and-limitations)
58
- - [Remote client configuration](#remote-client-configuration)
43
+ - [Tool discovery for agents](#tool-discovery-for-agents)
59
44
  - [Available tools](#available-tools)
60
45
  - [Development](#development)
61
46
  - [Testing & validation](#testing--validation)
@@ -66,12 +51,12 @@ This is an open-source project provided "as is" without warranty of any kind. Th
66
51
 
67
52
  ## Choose a runtime mode
68
53
 
69
- | Mode | Transport | Best for |
70
- |------|-----------|----------|
71
- | **Local STDIO** | stdin/stdout | Single-user local AI client (Claude Desktop, Cline, Cursor, etc.) |
72
- | **Hosted HTTP** | Streamable HTTP | Multi-user server deployment; remote MCP clients |
54
+ | Mode | Command | Transport | Best for | Authorization model |
55
+ |------|---------|-----------|----------|---------------------|
56
+ | **Local STDIO** | `pnpm start` / `pnpm run dev` | stdin/stdout | Single-user local AI client (Claude Desktop, Cline, Cursor, etc.) | The local process reads eBay credentials and optional `EBAY_USER_REFRESH_TOKEN` from environment variables. |
57
+ | **Hosted HTTP** | `pnpm run start:http` / `pnpm run dev:http` | Streamable HTTP | Multi-user server deployment; remote MCP clients | Users authorize through browser OAuth. Requests can use normal session Bearer auth or opt into server-request auth with `X-Ebay-Server-Request: true`. |
73
58
 
74
- Both modes use the same eBay tools. The local mode reads credentials from environment variables or a `.env` file. The hosted mode handles multi-user OAuth server-side and authenticates clients with session tokens.
59
+ Both modes use the same eBay tool registry. Local STDIO is best when one trusted local client owns the eBay credentials. Hosted HTTP runs an Express server with OAuth 2.1 discovery, environment-scoped route trees, server-side token/session storage, and admin-only operational endpoints.
75
60
 
76
61
  ---
77
62
 
@@ -82,16 +67,16 @@ Both modes use the same eBay tools. The local mode reads credentials from enviro
82
67
  - [pnpm](https://pnpm.io/) (or npm — `npm install -g pnpm`)
83
68
  - An [eBay Developer Account](https://developer.ebay.com/)
84
69
 
85
- **Getting eBay credentials:**
86
- 1. Log in to the [eBay Developer Portal](https://developer.ebay.com/my/keys)
87
- 2. Create an application and copy your **App ID (Client ID)** and **Cert ID (Client Secret)**
88
- 3. Under **User Tokens → Add RuName**, register your OAuth callback URL and copy the generated **RuName** string
70
+ **Getting credentials:**
71
+ 1. Log in to [eBay Developer Portal](https://developer.ebay.com/my/keys)
72
+ 2. Create an application, copy **App ID (Client ID)** and **Cert ID (Client Secret)**
73
+ 3. Under **User Tokens → Add RuName**, register your public HTTPS OAuth callback URL and copy the generated **RuName** string
89
74
 
90
- > **`EBAY_RUNAME` is the RuName string eBay generates, not the callback URL itself.** It looks like `YourApp-YourApp-SBX-abcdefghi`. The callback URL is set separately (see below).
75
+ > **`EBAY_RUNAME` and the public Redirect URL are distinct.** `EBAY_RUNAME` is the eBay-generated RuName string used as eBay's OAuth `redirect_uri` identifier (for example, `YourApp-YourApp-SB-abcdefghi`). The public Redirect URL is the real browser callback URL you register in eBay, such as `https://your-server.com/oauth/callback` or `https://ebay-local.test:3000/oauth/callback`; it is derived from `PUBLIC_BASE_URL` in this project. Do not use either value as a fallback for the other.
91
76
 
92
77
  ### HTTPS callback URL (required by eBay)
93
78
 
94
- eBay requires an HTTPS callback URL for OAuth. For local development, use [mkcert](https://github.com/FiloSottile/mkcert):
79
+ eBay requires HTTPS for OAuth callbacks. For local dev, use [mkcert](https://github.com/FiloSottile/mkcert):
95
80
 
96
81
  ```bash
97
82
  brew install mkcert nss
@@ -100,7 +85,7 @@ mkcert ebay-local.test
100
85
  echo "127.0.0.1 ebay-local.test" | sudo tee -a /etc/hosts
101
86
  ```
102
87
 
103
- Register `https://ebay-local.test:3000/oauth/callback` in the eBay Developer Portal as your Accept URL. Then add to `.env`:
88
+ Register `https://ebay-local.test:3000/oauth/callback` in the Developer Portal. Add to `.env`:
104
89
 
105
90
  ```bash
106
91
  PUBLIC_BASE_URL=https://ebay-local.test:3000
@@ -108,17 +93,15 @@ EBAY_LOCAL_TLS_CERT_PATH=/path/to/ebay-local.test.pem
108
93
  EBAY_LOCAL_TLS_KEY_PATH=/path/to/ebay-local.test-key.pem
109
94
  ```
110
95
 
111
- #### ⚠️ Trust the mkcert CA in Node.js (required for MCP clients like Cline)
96
+ #### Trust mkcert CA in Node.js
112
97
 
113
- VS Code's extension host (where Cline runs) uses Node.js for outbound HTTPS requests. Node.js does **not** automatically read macOS's system keychain, so the `ebay-local.test` certificate is not trusted by default. This causes the OAuth token exchange (`POST /sandbox/token`) to fail silently — the browser flow completes, the "Open in VS Code" page appears, but Cline never receives a session token.
114
-
115
- **Fix — run these two commands once, then fully quit and reopen VS Code:**
98
+ VS Code's extension host uses Node.js, which doesn't read macOS system keychain by default. Trust the cert:
116
99
 
117
100
  ```bash
118
- # 1. Set for the current macOS session (affects all Dock/Spotlight-launched apps):
101
+ # Current session (Dock/Spotlight-launched apps):
119
102
  launchctl setenv NODE_EXTRA_CA_CERTS "$(mkcert -CAROOT)/rootCA.pem"
120
103
 
121
- # 2. Create a LaunchAgent so it persists across reboots:
104
+ # Persist across reboots:
122
105
  cat > ~/Library/LaunchAgents/com.local.mkcert-node-trust.plist <<'EOF'
123
106
  <?xml version="1.0" encoding="UTF-8"?>
124
107
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -137,23 +120,13 @@ cat > ~/Library/LaunchAgents/com.local.mkcert-node-trust.plist <<'EOF'
137
120
  EOF
138
121
  launchctl load ~/Library/LaunchAgents/com.local.mkcert-node-trust.plist
139
122
 
140
- # 3. For terminal-launched VS Code — add to ~/.zshrc:
123
+ # Terminal-launched apps — add to ~/.zshrc:
141
124
  echo 'export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"' >> ~/.zshrc
142
125
  ```
143
126
 
144
- > Replace `YOUR_USERNAME` with your actual macOS username in the plist, or use the full path printed by `mkcert -CAROOT`.
145
-
146
- After running these commands and **fully quitting VS Code (Cmd+Q on macOS)** and reopening it, Cline's extension host will trust the `ebay-local.test` certificate and the MCP OAuth flow will complete successfully.
127
+ Then fully quit and reopen VS Code.
147
128
 
148
- **Verify the fix works (without restarting VS Code):**
149
- ```bash
150
- NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem" node -e "
151
- require('https').get('https://ebay-local.test:3000/health', r => console.log('TLS OK — status:', r.statusCode)).on('error', e => console.error('TLS FAIL:', e.message));
152
- "
153
- # Expected: TLS OK — status: 200
154
- ```
155
-
156
- For hosted deployments, register your server's public HTTPS URL instead (e.g. `https://your-server.com/oauth/callback`).
129
+ For hosted deployments, register your server's public HTTPS URL instead.
157
130
 
158
131
  ---
159
132
 
@@ -161,64 +134,48 @@ For hosted deployments, register your server's public HTTPS URL instead (e.g. `h
161
134
 
162
135
  ### Install
163
136
 
164
- **Option A — pnpm global install (no build step):**
165
-
166
137
  ```bash
138
+ # Option A — global install (no build step):
167
139
  pnpm install -g ebay-mcp-remote-edition
168
- ```
169
140
 
170
- **Option B — clone and build (for contributors or self-hosting):**
171
-
172
- ```bash
141
+ # Option B — clone and build (contributors):
173
142
  git clone https://github.com/mrnajiboy/ebay-mcp-remote-edition.git
174
143
  cd ebay-mcp-remote-edition
175
- pnpm install
176
- pnpm run build
144
+ pnpm install && pnpm run build
177
145
  ```
178
146
 
179
147
  ### Configure credentials
180
148
 
181
- Create a `.env` file in the project root (see `.env.example`):
149
+ Create `.env` (see `.env.example`):
182
150
 
183
151
  ```bash
184
152
  EBAY_CLIENT_ID=your_client_id
185
153
  EBAY_CLIENT_SECRET=your_client_secret
186
- EBAY_RUNAME=your_runame_string
154
+ EBAY_RUNAME=your_runame_string # eBay-generated RuName, not a URL
155
+ EBAY_REDIRECT_URI= # legacy env name; do not set to the public callback URL
187
156
  EBAY_ENVIRONMENT=sandbox # or production
188
157
  EBAY_MARKETPLACE_ID=EBAY_US # optional, defaults to EBAY_US
189
158
  EBAY_CONTENT_LANGUAGE=en-US # optional, defaults to en-US
190
-
191
- # Populated by the setup wizard:
192
- EBAY_USER_REFRESH_TOKEN=
159
+ EBAY_USER_REFRESH_TOKEN= # populated by setup wizard
193
160
  ```
194
161
 
195
162
  **Authentication tiers:**
196
163
 
197
164
  | Method | Rate limit | How |
198
165
  |--------|-----------|-----|
199
- | Client credentials (default) | 1,000 req/day | Just set `EBAY_CLIENT_ID` + `EBAY_CLIENT_SECRET` |
200
- | User tokens (recommended) | 10,000–50,000 req/day | Run the setup wizard to complete OAuth and populate `EBAY_USER_REFRESH_TOKEN` |
166
+ | Client credentials (default) | 1,000 req/day | Set `EBAY_CLIENT_ID` + `EBAY_CLIENT_SECRET` |
167
+ | User tokens (recommended) | 10,000–50,000 req/day | Run `pnpm run setup` to complete OAuth |
201
168
 
202
169
  ### Run the setup wizard
203
170
 
204
- The interactive wizard guides you through environment selection, credential entry, OAuth login, and MCP client configuration:
205
-
206
171
  ```bash
207
- pnpm run setup
172
+ pnpm run setup # interactive: env selection, credentials, OAuth, client config
173
+ pnpm run setup --quick # skip optional steps
174
+ pnpm run setup --diagnose # connectivity and token checks only
208
175
  ```
209
176
 
210
- Options:
211
- - `--quick` — skip optional steps
212
- - `--diagnose` — run connectivity and token checks only
213
-
214
- After completing OAuth, the wizard writes `EBAY_USER_REFRESH_TOKEN` to `.env` and optionally configures Claude Desktop automatically.
215
-
216
177
  ### Local client configuration
217
178
 
218
- For direct STDIO usage, configure your MCP client to launch the server as a subprocess. All clients use the same JSON pattern:
219
-
220
- **Using npm (no clone needed):**
221
-
222
179
  ```json
223
180
  {
224
181
  "mcpServers": {
@@ -230,6 +187,7 @@ For direct STDIO usage, configure your MCP client to launch the server as a subp
230
187
  "EBAY_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
231
188
  "EBAY_ENVIRONMENT": "sandbox",
232
189
  "EBAY_RUNAME": "YOUR_RUNAME",
190
+ "EBAY_REDIRECT_URI": "",
233
191
  "EBAY_USER_REFRESH_TOKEN": "YOUR_REFRESH_TOKEN"
234
192
  }
235
193
  }
@@ -237,35 +195,14 @@ For direct STDIO usage, configure your MCP client to launch the server as a subp
237
195
  }
238
196
  ```
239
197
 
240
- **Using a local build:**
241
-
242
- ```json
243
- {
244
- "mcpServers": {
245
- "ebay": {
246
- "command": "node",
247
- "args": ["/absolute/path/to/ebay-mcp-remote-edition/build/index.js"],
248
- "env": {
249
- "EBAY_CLIENT_ID": "YOUR_CLIENT_ID",
250
- "EBAY_CLIENT_SECRET": "YOUR_CLIENT_SECRET",
251
- "EBAY_ENVIRONMENT": "sandbox",
252
- "EBAY_RUNAME": "YOUR_RUNAME",
253
- "EBAY_USER_REFRESH_TOKEN": "YOUR_REFRESH_TOKEN"
254
- }
255
- }
256
- }
257
- }
258
- ```
259
-
260
- **Config file locations by client:**
198
+ **Config file locations:**
261
199
 
262
200
  | Client | Config file |
263
201
  |--------|-------------|
264
202
  | Cline | `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` |
265
203
  | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
266
204
  | Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
267
- | Cursor (global) | `~/.cursor/mcp.json` |
268
- | Cursor (project) | `.cursor/mcp.json` |
205
+ | Cursor | `~/.cursor/mcp.json` or `.cursor/mcp.json` |
269
206
 
270
207
  Zed, Windsurf, Continue.dev, Roo Code, and Amazon Q follow the same `mcpServers` JSON shape.
271
208
 
@@ -273,543 +210,416 @@ Zed, Windsurf, Continue.dev, Roo Code, and Amazon Q follow the same `mcpServers`
273
210
 
274
211
  ## Hosted mode setup
275
212
 
276
- The hosted HTTP server exposes environment-scoped MCP and OAuth endpoints, handles eBay OAuth server-side, and issues session tokens to MCP clients.
213
+ ### Environment variables
277
214
 
278
- ### Hosted environment variables
215
+ Hosted HTTP reads the same eBay credential variables as local STDIO plus the HTTP, storage, and security variables below. The start script is `pnpm run start:http`; development uses `pnpm run dev:http`.
279
216
 
280
217
  ```bash
281
- # Server
282
- PORT=3000
283
- MCP_HOST=0.0.0.0
218
+ # Required for hosted OAuth URLs and eBay callback registration
284
219
  PUBLIC_BASE_URL=https://your-server.com
285
220
 
286
- # eBay credentials (prefer secret file below instead of raw env)
287
- EBAY_DEFAULT_ENVIRONMENT=production # sandbox or production
288
-
289
- # Persistent token/session storage backend (required for multi-user hosted mode)
290
- EBAY_TOKEN_STORE_BACKEND=cloudflare-kv # cloudflare-kv | upstash-redis | memory
291
-
292
- # Cloudflare KV (when EBAY_TOKEN_STORE_BACKEND=cloudflare-kv)
293
- CLOUDFLARE_ACCOUNT_ID=
294
- CLOUDFLARE_KV_NAMESPACE_ID=
295
- CLOUDFLARE_API_TOKEN=
296
-
297
- # Upstash Redis (when EBAY_TOKEN_STORE_BACKEND=upstash-redis)
298
- UPSTASH_REDIS_REST_URL=
299
- UPSTASH_REDIS_REST_TOKEN=
300
-
301
- # eBay Research session-expiry alerts (optional but recommended when using
302
- # first-party research in hosted mode)
303
- TELEGRAM_BOT_TOKEN=
304
- TELEGRAM_CHAT_ID=1574052684
305
- QSTASH_URL=
306
- QSTASH_TOKEN=
307
- QSTASH_CURRENT_SIGNING_KEY=
308
- QSTASH_NEXT_SIGNING_KEY=
309
- EBAY_RESEARCH_SESSION_ALERTS_ENABLED=true
310
- EBAY_RESEARCH_SESSION_ALERT_WINDOW_24H=true
311
- EBAY_RESEARCH_SESSION_ALERT_WINDOW_6H=true
312
- EBAY_RESEARCH_SESSION_ALERT_ON_EXPIRED=true
313
- EBAY_RESEARCH_SESSION_ALERT_CALLBACK_URL=
314
-
315
- # Alert scheduling additionally requires:
316
- # - PUBLIC_BASE_URL or EBAY_RESEARCH_SESSION_ALERT_CALLBACK_URL to be externally reachable
317
- # - EBAY_RESEARCH_SESSION_STORE=upstash-redis or filesystem
318
-
319
- # Security
320
- ADMIN_API_KEY= # required for admin session endpoints
321
- OAUTH_START_KEY= # optional; protects /oauth/start with a shared secret
221
+ # Required eBay credentials unless EBAY_CONFIG_FILE provides them
222
+ EBAY_CLIENT_ID=
223
+ EBAY_CLIENT_SECRET=
224
+ EBAY_RUNAME=
225
+ EBAY_REDIRECT_URI= # legacy env name; not the public callback URL
226
+ # Recommended when serving both environments from one host
227
+ EBAY_PRODUCTION_CLIENT_ID=
228
+ EBAY_PRODUCTION_CLIENT_SECRET=
229
+ EBAY_PRODUCTION_RUNAME=
230
+ EBAY_PRODUCTION_REDIRECT_URI=
231
+ EBAY_SANDBOX_CLIENT_ID=
232
+ EBAY_SANDBOX_CLIENT_SECRET=
233
+ EBAY_SANDBOX_RUNAME=
234
+ EBAY_SANDBOX_REDIRECT_URI=
235
+
236
+ # Required for hosted multi-user token/session persistence
237
+ EBAY_TOKEN_STORE_BACKEND=upstash-redis # cloudflare-kv | upstash-redis | memory
238
+ UPSTASH_REDIS_REST_URL= # required when backend is upstash-redis
239
+ UPSTASH_REDIS_REST_TOKEN= # required when backend is upstash-redis
240
+ CLOUDFLARE_ACCOUNT_ID= # required when backend is cloudflare-kv
241
+ CLOUDFLARE_KV_NAMESPACE_ID= # required when backend is cloudflare-kv
242
+ CLOUDFLARE_API_TOKEN= # required when backend is cloudflare-kv
243
+
244
+ # Required for admin/validation endpoints and privileged MCP bypass
245
+ ADMIN_API_KEY=
246
+
247
+ # Optional HTTP/server behavior
248
+ PORT=3000 # defaults to 3000; many hosts inject this
249
+ MCP_HOST=0.0.0.0 # defaults to 0.0.0.0
250
+ EBAY_ENVIRONMENT=production # root/legacy default selector
251
+ EBAY_DEFAULT_ENVIRONMENT=production # fallback when EBAY_ENVIRONMENT is unset
252
+ SESSION_TTL_SECONDS=2592000 # default: 30 days
253
+ OAUTH_START_KEY= # optional gate for /oauth/start
254
+ EBAY_CONFIG_FILE=/etc/secrets/ebay-config.json
255
+ EBAY_MARKETPLACE_ID=EBAY_US
256
+ EBAY_CONTENT_LANGUAGE=en-US
257
+ EBAY_LOG_LEVEL=info
322
258
 
323
- # Validation runner identity (required for hosted /validation/* routes)
324
- # Reuses a stored refresh-token-backed user in the existing multi-user auth store.
325
- # Use the env-specific values when sandbox and production need different runner users.
259
+ # Optional validation runner identity
326
260
  VALIDATION_RUNNER_USER_ID=
327
261
  VALIDATION_RUNNER_USER_ID_SANDBOX=
328
262
  VALIDATION_RUNNER_USER_ID_PRODUCTION=
329
263
 
330
- # Temporary sold-data enrichment provider for validation.
331
- # This is an interim external abstraction and will be replaced by an internal
332
- # sales-data implementation without changing the validation orchestration route.
264
+ # Optional validation/research providers and alerts
333
265
  SOLD_ITEMS_API_URL=
334
266
  SOLD_ITEMS_API_KEY=
335
-
336
- # Future orchestration-side historical research provider.
337
- # Currently only used to enable the placeholder research contract.
338
267
  PERPLEXITY_API_KEY=
339
-
340
- # Optional phase-1 social-signal providers used by hosted validation.
341
- # These signals are supportive only and should not be treated as authoritative
342
- # automated buy triggers on their own.
343
268
  TWITTER_BEARER_TOKEN=
344
269
  YOUTUBE_API_KEY=
345
270
  REDDIT_CLIENT_ID=
346
271
  REDDIT_CLIENT_SECRET=
347
272
  REDDIT_USER_AGENT=
273
+ TELEGRAM_BOT_TOKEN=
274
+ TELEGRAM_CHAT_ID=
275
+ QSTASH_URL=
276
+ QSTASH_TOKEN=
277
+ QSTASH_CURRENT_SIGNING_KEY=
278
+ QSTASH_NEXT_SIGNING_KEY=
279
+ EBAY_RESEARCH_SESSION_ALERTS_ENABLED=true
280
+ EBAY_RESEARCH_SESSION_ALERT_CALLBACK_URL=
348
281
 
349
- # Session TTL (optional; default 30 days)
350
- SESSION_TTL_SECONDS=2592000
351
-
352
- # Logging
353
- EBAY_LOG_LEVEL=info
354
- EBAY_MARKETPLACE_ID=EBAY_US
355
- EBAY_CONTENT_LANGUAGE=en-US
356
-
357
- # Path to secret file (see below)
358
- EBAY_CONFIG_FILE=/etc/secrets/ebay-config.json
282
+ # eBay Research/Terapeak first-party session source.
283
+ # Set this explicitly in hosted deployments. If unset, research sessions default
284
+ # to Cloudflare KV even when EBAY_TOKEN_STORE_BACKEND=upstash-redis.
285
+ EBAY_RESEARCH_SESSION_STORE=upstash-redis # cloudflare_kv | upstash-redis | filesystem | none
286
+ EBAY_RESEARCH_SESSION_ALLOW_FILESYSTEM_FALLBACK=false
359
287
  ```
360
288
 
361
- > Use `EBAY_TOKEN_STORE_BACKEND=memory` only for local development or tests. All OAuth state, session tokens, and user tokens are lost on restart.
289
+ > `EBAY_TOKEN_STORE_BACKEND` defaults to Cloudflare KV when unset or unrecognized. Use `memory` only for tests or throwaway local development because hosted sessions and tokens are lost on restart.
290
+ > `EBAY_RESEARCH_SESSION_STORE` is separate from OAuth token storage. Set it to `upstash-redis` when Terapeak/eBay Research cookies are stored in Upstash.
362
291
 
363
292
  ### Secret file
364
293
 
365
- Store eBay credentials in a mounted secret file rather than raw environment variables. On Render, create a **Secret File** named `ebay-config.json` mounted at `/etc/secrets/ebay-config.json`:
294
+ Mount a JSON file with credentials (e.g., Render Secret File at `/etc/secrets/ebay-config.json`):
366
295
 
367
296
  ```json
368
297
  {
369
298
  "production": {
370
299
  "clientId": "PROD_CLIENT_ID",
371
300
  "clientSecret": "PROD_CLIENT_SECRET",
372
- "redirectUri": "YOUR_PRODUCTION_RUNAME"
301
+ "redirectUri": "YOUR_PRODUCTION_RUNAME",
302
+ "ruName": "YOUR_PRODUCTION_RUNAME"
373
303
  },
374
304
  "sandbox": {
375
305
  "clientId": "SANDBOX_CLIENT_ID",
376
306
  "clientSecret": "SANDBOX_CLIENT_SECRET",
377
- "redirectUri": "YOUR_SANDBOX_RUNAME"
307
+ "redirectUri": "YOUR_SANDBOX_RUNAME",
308
+ "ruName": "YOUR_SANDBOX_RUNAME"
378
309
  }
379
310
  }
380
311
  ```
381
312
 
382
- ### Deploy to Render / Railway / other Nixpacks hosts
313
+ ### Deploy to Render / Railway / Coolify
383
314
 
384
- 1. Connect your repo to Render as a **Web Service**
385
- 2. Set **Build command:**
386
- ```bash
387
- pnpm install && pnpm run build
388
- ```
389
- 3. Set **Start command:**
390
- ```bash
391
- pnpm run start:http
392
- ```
393
- 4. Add the environment variables listed above
394
- 5. Add the `ebay-config.json` secret file
315
+ 1. Connect your repo as a **Web Service**
316
+ 2. **Build:** `pnpm install && pnpm run build`
317
+ 3. **Start:** `pnpm run start:http`
318
+ 4. Add environment variables + secret file
395
319
 
396
- The server starts on the port Render assigns via `$PORT` and logs the active KV backend on startup.
397
-
398
- For Nixpacks-based platforms such as Railway and Coolify:
399
-
400
- - The repository now includes `nixpacks.toml` so the generated image uses `pnpm install --frozen-lockfile`, runs `pnpm run build`, installs Chromium for the Playwright-backed validation paths, and starts with `pnpm run start:http`.
401
- - Runtime secrets should be configured in the platform dashboard as runtime environment variables or mounted secret files. Do not bake secrets into Docker build arguments or `ENV` layers.
402
- - Keep `pnpm-lock.yaml` committed and in sync with `package.json`; Nixpacks installs with a frozen lockfile.
320
+ For Nixpacks-based platforms (Railway, Coolify): `nixpacks.toml` handles `pnpm`, build, Chromium install, and start automatically.
403
321
 
404
322
  ### OAuth flows
405
323
 
406
- eBay requires a single registered callback URL per application. The hosted server registers `/oauth/callback` at the root and recovers the environment from the stored OAuth state record.
407
-
408
- **Start an OAuth flow (browser):**
409
-
410
324
  ```
411
- GET /sandbox/oauth/start # always sandbox
412
- GET /production/oauth/start # always production
325
+ GET /sandbox/oauth/start # sandbox browser login
326
+ GET /production/oauth/start # production browser login
413
327
  ```
414
328
 
415
- If `OAUTH_START_KEY` is set, include it as a query parameter or header:
416
- ```
417
- GET /sandbox/oauth/start?key=YOUR_OAUTH_START_KEY
418
- # or header: X-OAuth-Start-Key: YOUR_OAUTH_START_KEY
419
- ```
329
+ If `OAUTH_START_KEY` is set, start URLs require either `?key=YOUR_KEY` or the `X-OAuth-Start-Key: YOUR_KEY` header. The server also includes this key as `key` in generated `authorization_url` values for unauthenticated MCP requests.
330
+
331
+ After login, the callback page shows three hosted auth options with copy buttons:
420
332
 
421
- After a successful login, the callback page displays your **session token**, **eBay access token**, and **eBay refresh token** with one-click copy buttons.
333
+ | Hosted auth mode | How to select it | Best for |
334
+ |------------------|------------------|----------|
335
+ | **User/session mode** | Send `Authorization: Bearer <session-token>` and omit `X-Ebay-Server-Request` | Normal OAuth-aware MCP clients and user-scoped desktop clients. |
336
+ | **Server request mode — identity headers** | Send `X-Ebay-Server-Request: true`, `X-Ebay-Client-Id`, `X-Ebay-User-Id`, and optional `X-Ebay-Environment` | Server/client setups that need to handle both regular user requests and backend server requests without copying a session token. |
337
+ | **Server request mode — bearer-capable clients** | Send `X-Ebay-Server-Request: true` plus `Authorization: Bearer <mcp-server-issued-bearer-token>` | MCP clients or automation platforms that can store an authorization header but should not use the admin key. |
422
338
 
423
- **Session token TTL schedule:**
339
+ Switching between modes is per request, not a server-wide environment toggle. The same hosted MCP server can handle user/session requests and server requests concurrently; the client chooses server mode by adding `X-Ebay-Server-Request: true`.
424
340
 
425
- | Record | `expiresAt` field | Backend TTL |
426
- |--------|-------------------|-------------|
427
- | OAuth state | 15 minutes | 15 minutes |
428
- | MCP auth code | 10 minutes | 10 minutes |
429
- | Session | 30 days (configurable) | Matches `SESSION_TTL_SECONDS` |
430
- | User token record | eBay refresh token expiry (fallback: 18 months) | Matches token expiry |
341
+ **Session TTL schedule:**
342
+
343
+ | Record | TTL |
344
+ |--------|-----|
345
+ | OAuth state | 15 minutes |
346
+ | MCP auth code | 10 minutes |
347
+ | Session | 30 days (configurable) |
348
+ | User token | eBay refresh token expiry (fallback: 18 months) |
431
349
 
432
350
  ### MCP endpoints
433
351
 
434
352
  **Environment-scoped (recommended):**
435
-
436
353
  ```
437
354
  POST/GET/DELETE /sandbox/mcp
438
355
  POST/GET/DELETE /production/mcp
439
356
  ```
440
357
 
441
- Each scoped path includes its own OAuth 2.1 discovery document:
442
- ```
443
- GET /sandbox/.well-known/oauth-authorization-server
444
- GET /production/.well-known/oauth-authorization-server
445
- ```
358
+ Each includes OAuth 2.1 discovery: `GET /sandbox/.well-known/oauth-authorization-server`
446
359
 
447
- **Legacy auto-detect (backward-compatible):**
360
+ **Legacy auto-detect:**
448
361
  ```
449
- POST/GET/DELETE /mcp # resolves environment from ?env= or EBAY_DEFAULT_ENVIRONMENT
362
+ POST/GET/DELETE /mcp # resolves from ?env= or EBAY_ENVIRONMENT/EBAY_DEFAULT_ENVIRONMENT
450
363
  ```
451
364
 
452
- **Authentication behavior:**
453
- - `GET /mcp` (or scoped variant) without a valid Bearer token redirects the browser to the matching `oauth/start` URL
454
- - `POST /mcp` without a valid Bearer token returns a structured `401` JSON with an `authorization_url` field
455
- - All requests supply a session token via `Authorization: Bearer <session-token>`
365
+ **Auth behavior:**
366
+ - `GET /mcp` without token redirects to `oauth/start`
367
+ - `POST /mcp` without token `401` JSON with `authorization_url`, `resource_metadata`, and a `WWW-Authenticate` Bearer challenge
368
+ - Normal user requests: `Authorization: Bearer <session-token>`
369
+ - Server requests with identity headers: `X-Ebay-Server-Request: true`, `X-Ebay-Client-Id: <client-id>`, `X-Ebay-User-Id: <user-id>`, `X-Ebay-Environment: sandbox|production`
370
+ - Server requests with bearer-capable clients: `X-Ebay-Server-Request: true` plus `Authorization: Bearer <mcp-server-issued-bearer-token>`
371
+ - Privileged admin bypass: `Authorization: Bearer <ADMIN_API_KEY>` when `ADMIN_API_KEY` is configured
456
372
 
457
- **Other utility endpoints:**
373
+ The `X-Ebay-Server-Request` header is intentionally client-side and per-request. Leave it off for normal user/session OAuth calls. Add it when the MCP client is making a server-style request and should resolve the stored Redis/KV user token record by headers or by the MCP server-issued bearer lookup token. This bearer lookup token is generated by this MCP server on the OAuth callback page; it is **not** an eBay user access token, eBay refresh token, eBay client-credentials/app token, legacy hosted session token, or `ADMIN_API_KEY`.
458
374
 
459
- ```
460
- GET /health # Server health check (no auth required)
461
- GET /whoami # Session identity; requires Bearer session token
462
- GET /admin/session/:sessionToken # View session; requires X-Admin-API-Key
463
- POST /admin/session/:sessionToken/revoke # Revoke session
464
- DELETE /admin/session/:sessionToken # Delete session
465
- ```
375
+ #### Admin key bypass
466
376
 
467
- `/whoami` response:
468
- ```json
469
- {
470
- "userId": "...",
471
- "environment": "sandbox",
472
- "createdAt": "2026-03-23T08:00:00.000Z",
473
- "expiresAt": "2026-04-22T08:00:00.000Z",
474
- "lastUsedAt": "2026-03-23T09:30:00.000Z",
475
- "revokedAt": null
476
- }
477
- ```
478
-
479
- `/whoami` is the quickest hosted-session debugging check when an MCP client appears authenticated but requests still fail. It confirms which stored user session is active, which environment it is bound to, and whether the session has expired or been revoked.
480
-
481
- ### Validation architecture
482
-
483
- The hosted backend now includes a deployment-oriented validation pipeline for non-MCP server-side execution. The route handlers live in [`src/server-http.ts`](src/server-http.ts), while the validation module lives under [`src/validation/`](src/validation).
484
-
485
- Current module layout:
486
-
487
- - [`src/validation/types.ts`](src/validation/types.ts) — request/response contracts for validation runs, decision payloads, debug payloads, and provider signal types
488
- - [`src/validation/effective-context.ts`](src/validation/effective-context.ts) — source-aware normalization layer that converts raw request payloads into a first-class effective validation context for item and event runs
489
- - [`src/validation/run-validation.ts`](src/validation/run-validation.ts) — orchestration entrypoint that validates input, queries providers, merges signals, and returns writes/decision/debug output
490
- - [`src/validation/recommendation.ts`](src/validation/recommendation.ts) — recommendation and automation decision logic
491
- - [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts) — live eBay browse-market snapshot provider using the server's existing user-scoped eBay API client
492
- - [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) — temporary sold-data provider backed by an external API via `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`
493
- - [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) — authenticated eBay Research provider orchestration for current-market and previous-POB metrics, including candidate scoring, fallback diagnostics, and sold-velocity bucketing
494
- - [`src/validation/providers/ebay-research.ts`](src/validation/providers/ebay-research.ts) — low-level authenticated eBay Research fetcher with session-cookie sourcing, response parsing, and auth-aware cache invalidation
495
- - [`src/validation/providers/query-utils.ts`](src/validation/providers/query-utils.ts) — shared multi-tier query candidate and fallback helpers used by browse and sold providers
496
- - [`src/validation/providers/social.ts`](src/validation/providers/social.ts) — phase-1 social provider for recent Twitter/X activity, YouTube view-rate proxy data, and Reddit recent-post counts with graceful degradation
497
- - [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts) — chart-signal stub reserved for later implementation
498
- - [`src/validation/providers/research.ts`](src/validation/providers/research.ts) — stable previous-comeback research contract provider for orchestration-side historical inference; currently a placeholder contract with optional future `PERPLEXITY_API_KEY` support
499
-
500
- Current provider domains called by [`runValidation()`](src/validation/run-validation.ts:106):
501
-
502
- - **browse/current-market** via [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts)
503
- - **sold enrichment** via [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts)
504
- - **Terapeak / eBay research contract** via [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts)
505
- - **social support signals** via [`src/validation/providers/social.ts`](src/validation/providers/social.ts)
506
- - **chart support signals** via [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts)
507
- - **previous comeback research inference** via [`src/validation/providers/research.ts`](src/validation/providers/research.ts)
508
-
509
- Architecturally, the validation stack is split into two practical classes of providers:
510
-
511
- - **Server-side authenticated providers** — these run with the hosted backend's stored eBay user context and are the right place for authenticated marketplace retrieval. Today that means the live browse/current-market provider in [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts), the sold enrichment layer in [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts), and the Terapeak/eBay research contract in [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts).
512
- - **Orchestration-side research providers** — these run as supporting inference layers inside orchestration rather than as part of the user-scoped eBay API client surface. Today that means previous comeback resolution and external historical-research inference in [`src/validation/providers/research.ts`](src/validation/providers/research.ts), plus non-authoritative support providers such as [`src/validation/providers/social.ts`](src/validation/providers/social.ts) and [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts).
377
+ `ADMIN_API_KEY` is used in two distinct ways:
513
378
 
514
- Operationally, validation works like this:
379
+ | Use | Exact implementation | Scope |
380
+ |-----|----------------------|-------|
381
+ | Admin/validation HTTP routes | `X-Admin-API-Key: <ADMIN_API_KEY>` header | Required for `/admin/session/:sessionToken`, `/admin/session/:sessionToken/revoke`, `/admin/session/:sessionToken`, `/sandbox/validation/*`, `/production/validation/*`, and legacy `/validation/*`. |
382
+ | MCP authorization bypass | `Authorization: Bearer <ADMIN_API_KEY>` header | Lets privileged server-to-server/admin tooling call `/sandbox/mcp`, `/production/mcp`, or `/mcp` without a hosted user session lookup. The request runs with `userId` set to `admin` and the requested environment. |
515
383
 
516
- 1. An admin caller invokes an environment-scoped validation route.
517
- 2. The server resolves the environment (`sandbox` or `production`) from the mounted route tree.
518
- 3. The route looks up the configured validation runner user ID for that environment.
519
- 4. The server loads that user's stored refresh-token-backed credentials from the existing hosted auth store.
520
- 5. The validation orchestrator calls all six provider domains and gathers browse/current-market, sold enrichment, Terapeak/research contract data, social support signals, chart stub output, and previous-comeback research output.
521
- 6. Before provider execution, [`runValidation()`](src/validation/run-validation.ts) builds a normalized `effectiveContext` so downstream logic consumes a source-aware model (`item` or `event`) instead of relying on empty item placeholders.
522
- 7. [`runValidation()`](src/validation/run-validation.ts) deterministically merges the provider outputs into normalized field writes.
523
- 8. The response returns those writes, a conservative buy/track decision block, and provider debug metadata for downstream systems.
384
+ The admin bypass does **not** use query parameters or request body fields. `ADMIN_API_KEY` must be configured on the server; if it is unset, admin HTTP routes return `500` and the MCP bypass is disabled.
524
385
 
525
- #### Effective validation context
386
+ Security caveats:
387
+ - Treat `ADMIN_API_KEY` as a privileged root credential for this MCP server.
388
+ - Generate a long, random value and store it only in your deployment secret manager.
389
+ - Use it only for server-to-server automation, operational checks, or admin tooling. Do not ship it to browsers, desktop clients, or regular users.
390
+ - OAuth browser authorization and hosted session tokens remain the normal path for user MCP access.
526
391
 
527
- Validation runs now normalize incoming request data into an internal effective context before provider query planning and recommendation logic execute.
528
-
529
- - **Item-scope runs** normalize to an item-oriented context with the resolved artist, album/item phrase, location, and resolved search query.
530
- - **Event-scope runs** normalize to an event-oriented context with `searchArtist`, `searchEvent`, `searchItem`, `searchLocation`, timing metadata, and a derived `effectiveSearchQuery` when no direct resolved query is present.
531
- - Providers and recommendation logic consume that normalized context rather than reasoning about blank `item.recordId` or `item.name` fields.
532
- - Debug output now exposes `effectiveSourceType`, `effectiveContextMode`, `effectiveSearchQuery`, `hasItem`, and `hasEvent` so operators can confirm whether an event run was normalized correctly.
533
-
534
- The request schema also now accepts source-aware query-context fields for hosted validation runs:
535
-
536
- - `resolvedSearchArtist`
537
- - `resolvedSearchItem`
538
- - `resolvedSearchEvent`
539
- - `resolvedSearchLocation`
540
- - `resolvedSearchQuery`
541
-
542
- The validation contract is intentionally split between stable route orchestration and swappable providers. That is why the current sold-data source can be replaced later without changing downstream orchestration or the hosted route contract implemented in [`src/validation/run-validation.ts`](src/validation/run-validation.ts).
543
-
544
- #### Deterministic merge precedence
545
-
546
- The current merge order is fixed in [`runValidation()`](src/validation/run-validation.ts:106) so downstream systems can treat the writes as predictable rather than provider-order dependent:
547
-
548
- - **Watchers / preorder count / shipping / competition** prefer Terapeak contract output when available, then fall back to the browse/current-market provider.
549
- - **Market price** prefers Terapeak contract output, then the sold provider's median sold price, then the browse/current-market provider.
550
- - **Sold day buckets** (`day1Sold` through `day5Sold`, plus `daysTracked`) prefer the sold provider, then authenticated eBay Research sold-row bucketing, then the browse/current-market provider.
551
- - **Previous POB metrics** (`previousPobAvgPriceUsd`, `previousPobSellThroughPct`) are written from the Terapeak contract output when available.
552
- - **Previous comeback first-week sales** (`previousComebackFirstWeekSales`) is written from the orchestration-side research provider when available.
553
- - **Supportive social fields** are only written when a value is actually resolved, so the pipeline avoids blanking previously stored downstream data.
554
-
555
- The validation signal contracts in [`TerapeakValidationSignals`](src/validation/types.ts:142) and [`PreviousComebackResearchSignals`](src/validation/types.ts:164) also back the new write fields in [`ValidationWrites`](src/validation/types.ts:176): `previousPobAvgPriceUsd`, `previousPobSellThroughPct`, and `previousComebackFirstWeekSales`.
556
-
557
- ### Validation endpoints and auth model
558
-
559
- Use the environment-scoped hosted routes for validation:
392
+ **Utility endpoints:**
560
393
 
561
394
  ```
562
- POST /sandbox/validation/run
563
- POST /production/validation/run
564
-
565
- GET /sandbox/validation/health
566
- GET /production/validation/health
395
+ GET /health # health check (no auth)
396
+ GET /whoami # session identity (Bearer token)
397
+ GET /admin/session/:sessionToken # view session (admin key)
398
+ POST /admin/session/:sessionToken/revoke # revoke session
399
+ DELETE /admin/session/:sessionToken # delete session
567
400
  ```
568
401
 
569
- Both routes require the admin key:
402
+ ### Admin endpoints
403
+
404
+ All `/admin/*` routes accept authentication via either:
405
+ - Header: `X-Admin-API-Key: <ADMIN_API_KEY>`
406
+ - Query param: `?key=<ADMIN_API_KEY>` (useful for browser access)
570
407
 
571
408
  ```
572
- X-Admin-API-Key: YOUR_ADMIN_API_KEY
409
+ GET /admin/token-status # OAuth + Playwright session health
410
+ POST /admin/oauth/start-for-validation # Start OAuth flow for validation runner
411
+ POST /admin/playwright-session # Store Playwright storage state JSON
412
+ GET /admin/playwright-capture # Browser UI to capture eBay Research cookies
573
413
  ```
574
414
 
575
- Auth model summary:
576
-
577
- - Validation routes are **hosted HTTP backend routes**, not MCP tool endpoints.
578
- - They do **not** use MCP client auth for execution.
579
- - They reuse the existing stored refresh-token-backed hosted user architecture.
580
- - The caller authenticates with `X-Admin-API-Key`, and the server then impersonates the configured validation runner user for the target environment.
581
- - Validation runner identity comes from `VALIDATION_RUNNER_USER_ID`, `VALIDATION_RUNNER_USER_ID_SANDBOX`, or `VALIDATION_RUNNER_USER_ID_PRODUCTION`.
582
- - The validation runner must already have stored hosted tokens in the configured token store backend.
583
-
584
- #### `POST /validation/run`
585
-
586
- Runs the validation pipeline for the target environment.
587
-
588
- - Uses the configured validation runner user ID for that environment
589
- - Requires that stored refresh-token-backed eBay credentials already exist for that user
590
- - Returns either:
591
- - `status: "ok"` with `writes`, `decision`, and `debug`, or
592
- - `status: "error"` with `errorCode`, `message`, `retryable`, and `nextCheckAt`
593
-
594
- The request/response contract is defined in [`src/validation/types.ts`](src/validation/types.ts), and the orchestration behavior is implemented in [`src/validation/run-validation.ts`](src/validation/run-validation.ts).
595
-
596
- The `writes` payload is intentionally non-destructive for supportive and optional fields: if a social, authenticated eBay Research, or previous-comeback research provider cannot resolve data, the orchestration omits those optional writes instead of overwriting existing downstream values with empty placeholders.
597
-
598
- #### `GET /validation/health`
599
-
600
- Checks whether the validation runner is operational in the target environment.
601
-
602
- This endpoint is intended for deployment diagnostics and returns:
603
-
604
- - configured environment
605
- - configured validation runner user ID
606
- - whether stored tokens are present
607
- - whether token refresh/authentication succeeded
608
- - token status from the user-scoped eBay API client
609
- - `authDebug` diagnostics including token endpoint resolution and credential presence
610
- - provider availability summary
611
-
612
- The diagnostics are especially useful after the OAuth token endpoint fix in [`getOAuthTokenBaseUrl()`](src/config/environment.ts:373) and the debug additions in [`getAuthDebugInfo()`](src/auth/oauth.ts:282). If the validation runner cannot refresh tokens, `/validation/health` shows the resolved token endpoint and any captured upstream response status/body excerpt.
613
-
614
- ### Diagnostics and health endpoints
615
-
616
- Use these endpoints together when validating a hosted deployment:
415
+ **`/admin/playwright-capture`** renders a self-service page for renewing the eBay Research Playwright session. Open it in a browser with the admin key as a query parameter:
617
416
 
618
417
  ```
619
- GET /health
620
- GET /whoami
621
- GET /sandbox/validation/health
622
- GET /production/validation/health
623
- POST /internal/ebay-research/check-session-expiry
418
+ https://your-server.com/admin/playwright-capture?key=YOUR_ADMIN_API_KEY
624
419
  ```
625
420
 
626
- Recommended debugging flow:
627
-
628
- 1. Call `/health` to confirm the HTTP service is up.
629
- 2. Call `/whoami` with a Bearer hosted session token to confirm the active hosted user session, bound environment, expiry, and revocation status.
630
- 3. Call the matching env-scoped `/validation/health` route with `X-Admin-API-Key` to confirm the validation runner user is configured, stored tokens exist, and token refresh succeeds.
631
- 4. The internal `POST /internal/ebay-research/check-session-expiry` route is reserved for signed QStash callbacks and should not be used as an unauthenticated public endpoint.
632
-
633
- `/whoami` is especially useful when an operator wants to verify which hosted session is currently active before registering or troubleshooting the validation runner user. Validation routes themselves still authenticate with the admin key and a stored hosted runner identity, not with MCP auth.
634
-
635
- The validation health response is also the main place to verify the OAuth token-endpoint derivation fix from [`getOAuthTokenBaseUrl()`](src/config/environment.ts:373). If a refresh fails, the `authDebug` block exposes the resolved endpoint, credential-presence flags, and captured upstream response excerpts.
636
-
637
- ### Validation provider behavior and limitations
638
-
639
- Current backend status:
421
+ The page has two tabs:
422
+ 1. **Auto-Capture** — loads eBay Research in an iframe (may be blocked by eBay's security policies)
423
+ 2. **Manual Export** — provides step-by-step instructions to export cookies from Chrome DevTools, a bookmarklet for one-click cookie copying, and a text area to paste and submit the JSON
640
424
 
641
- - eBay live market snapshot support is implemented and wired into orchestration.
642
- - Sold-data enrichment is implemented through a **temporary external provider** abstraction.
643
- - Authenticated eBay Research is wired into orchestration for current-market and previous-POB retrieval, while previous-comeback research remains a separate placeholder contract.
644
- - Social support signals are implemented in phase 1.
645
- - Chart data remains a stub.
646
- - Validation is currently an **admin-operated hosted backend workflow**, not an MCP tool surface.
647
- - Event-scope validations are now handled as first-class normalized runs instead of as item-shaped requests with null item identity tolerated for compatibility.
425
+ Submitted cookies are validated against the authenticated eBay Research ACTIVE endpoint before persistence, stored in the configured session backend (Upstash Redis / Cloudflare KV / filesystem), and written with a long KV TTL. Cookie expiry is tracked in metadata so the runtime can report missing/expired Terapeak auth separately from provider parsing failures.
648
426
 
649
- Provider behavior:
427
+ ### Validation endpoints
650
428
 
651
- - **Browse/eBay provider:** [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts) uses the eBay Browse API plus shared query fallback logic from [`src/validation/providers/query-utils.ts`](src/validation/providers/query-utils.ts). It walks multiple query candidates, records the selected query and tier in debug output, and uses heuristic matching rather than a strict catalog identity join. Event-driven runs now build those fallback queries from normalized event context instead of raw item title assumptions.
652
- - **Browse debug semantics:** validation debug now keeps browse candidate generation, selected query/tier, browse-specific sample size, and per-candidate result counts separate from sold-provider result counts so operators can tell whether the browse layer contributed a field, fell back to a weaker query, or returned no usable match.
653
- - **Sold provider:** [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) uses a temporary external sold-data source configured by `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`. It uses the same query-fallback strategy as the browse provider and returns sold-price ranges, sample sold items, and recent sold-velocity buckets when available.
654
- - **Terapeak / eBay research provider:** [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) now evaluates authenticated eBay Research candidates for both current-market and previous-POB contexts, scores them against title alignment and subtype coverage, preserves per-candidate diagnostics in debug output, and derives sold-day buckets from sold-row timestamps when available.
655
- - **Authenticated research session source:** [`src/validation/providers/ebay-research.ts`](src/validation/providers/ebay-research.ts) now prefers KV-backed Playwright storage state first, then environment-provided storage state / cookie fallbacks, then local storage-state/profile fallbacks for local development only. Parsed ACTIVE and SOLD tab responses are cached, automatically invalidated when the authenticated cookie fingerprint changes, and emit explicit auth-resolution debug fields including `sessionSource`, KV/env/filesystem attempt status, and fallback reasons.
656
- - **Social provider:** [`src/validation/providers/social.ts`](src/validation/providers/social.ts) supports phase-1 Twitter/X recent activity, YouTube average-daily-views proxy data exposed through the `youtubeViews24hMillions` field, and Reddit recent post counts. These signals degrade gracefully on provider/API failure and are used as supportive indicators rather than authoritative demand truth.
657
- - **Chart provider:** [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts) is still a stub and does not currently contribute chart-based metrics.
658
- - **Previous comeback research provider:** [`src/validation/providers/research.ts`](src/validation/providers/research.ts) now performs Perplexity-backed historical research when `PERPLEXITY_API_KEY` is configured. It attempts to resolve the prior comeback, normalize previous first-week sales when support exists, assign a `perplexityHistoricalContextScore`, generate concise `historicalContextNotes`, and emit debug diagnostics covering the research query, citations/snippets, resolved prior release, confidence, and score reasoning.
659
-
660
- Recommendation behavior:
661
-
662
- - [`src/validation/recommendation.ts`](src/validation/recommendation.ts) now accepts Terapeak and research inputs alongside browse, sold, social, and chart signals.
663
- - Recommendation generation also consumes the normalized effective context so event runs can carry source-aware monitoring notes and avoid item-only assumptions when no usable item identity exists.
664
- - Automatic tracking now pauses when the validation is still nominally in a watch state but the required source context or a usable derived query is missing.
665
- - The decisioning remains intentionally conservative: Terapeak and research data can improve monitoring notes and confidence context, but the system still avoids aggressive automatic buy-state changes from partial or proxy signals alone.
666
-
667
- Known limitations in the current implementation:
668
-
669
- - The sold-data provider depends on external configuration via `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`.
670
- - If those sold-data variables are missing, validation still runs but sold enrichment degrades to an unavailable/error state rather than providing full historical-sales signals.
671
- - The sold-data provider is temporary and intended to be replaced by an internal implementation later.
672
- - Authenticated eBay Research requires a valid session source such as KV-backed Playwright storage state, `EBAY_RESEARCH_STORAGE_STATE_JSON`, `EBAY_RESEARCH_COOKIES_JSON`, a local Playwright storage-state file, or a local browser profile directory; without one, the provider degrades to diagnostic-only output with explicit structured auth-resolution debug.
673
-
674
- #### eBay Research bootstrap and hosted runtime notes
429
+ ```
430
+ POST /sandbox/validation/run # run validation pipeline
431
+ POST /production/validation/run
675
432
 
676
- - Install Chromium for hosted runtimes with [`package.json`](package.json) script `playwright:install` (`pnpm run playwright:install`).
677
- - The Docker deployment path now provisions Chromium during image build in [`Dockerfile`](Dockerfile).
678
- - Canonical production session source of truth is KV-backed Playwright storage-state JSON stored under `ebay_research_storage_state_json` with companion metadata in `ebay_research_storage_state_meta`, including `updatedAt`, `expiresAt`, `ttlSeconds`, `marketplace`, `sessionStore`, and `sessionVersion`.
679
- - Bootstrap a signed-in eBay Research storage state into KV with [`src/scripts/bootstrap-ebay-research-session.ts`](src/scripts/bootstrap-ebay-research-session.ts) via the packaged/runtime-safe [`package.json`](package.json) script `research:bootstrap` (`pnpm run build && pnpm run research:bootstrap`).
680
- - Inspect canonical eBay Research session persistence and fresh-client readback diagnostics with [`src/scripts/inspect-ebay-research-session.ts`](src/scripts/inspect-ebay-research-session.ts) via the packaged/runtime-safe [`package.json`](package.json) script `research:inspect-session` (`pnpm run build && pnpm run research:inspect-session`).
681
- - Verify headless Chromium launchability with [`src/scripts/check-playwright.ts`](src/scripts/check-playwright.ts) via the packaged/runtime-safe [`package.json`](package.json) script `research:check-browser` (`pnpm run build && pnpm run research:check-browser`).
682
- - Runtime precedence is: KV storage state → `EBAY_RESEARCH_STORAGE_STATE_JSON` → `EBAY_RESEARCH_COOKIES_JSON` → local storage-state file → local Playwright profile → explicit auth-missing fallback.
683
- - Every candidate session source is validated against the first-party ACTIVE endpoint before the provider reports `authState = loaded`; failed validation is surfaced through debug fields including `kvStorageStateBytes`, `authValidationAttempted`, and `authValidationSucceeded`.
684
- - Once a validated session is loaded, ACTIVE and SOLD endpoint fetches automatically become the preferred first-party research source while legacy active/sold fallbacks remain intact when auth is missing or invalid.
685
- - Successful bootstrap also schedules signed QStash callbacks for 24 hours before expiry, 6 hours before expiry, and at expiry. Those callbacks target `POST /internal/ebay-research/check-session-expiry`, which verifies QStash signatures, suppresses stale reminders by `sessionVersion`, and sends Telegram alerts to `TELEGRAM_CHAT_ID`.
686
- - Alert scheduling is intentionally skipped when the callback URL resolves to localhost/loopback or when `EBAY_RESEARCH_SESSION_STORE` uses a backend without shared lock support, because those configurations cannot safely deliver or deduplicate hosted reminders.
687
- - Session refresh is manual by design for now: rerun `pnpm run build && pnpm run research:bootstrap` whenever eBay expires the stored session, then redeploy or restart the hosted service if your platform does not hot-reload env/KV-backed state.
688
- - The previous-comeback research provider depends on grounded external research and therefore degrades to low-confidence notes with a zero historical score when `PERPLEXITY_API_KEY` is missing, the response cannot be normalized, or reliable evidence is not found.
689
- - The browse provider still relies on heuristic query selection and fallback matching.
690
- - The YouTube-backed `youtubeViews24hMillions` field is currently an **average daily views proxy**, not a true trailing 24-hour delta.
691
- - Social signals are supportive/proxy data only and should not be presented as decisive automated buy logic.
692
- - eBay-derived metrics are intentionally practical rather than exhaustive, but authenticated ACTIVE research rows now populate watcher-derived metrics whenever watcher counts are present in the first-party response.
433
+ GET /sandbox/validation/health # check runner status
434
+ GET /production/validation/health
435
+ ```
693
436
 
694
- ### Roadmap note: provider maturation
437
+ Both require `X-Admin-API-Key: <ADMIN_API_KEY>`. The server impersonates the configured validation runner user from `VALIDATION_RUNNER_USER_ID` env vars.
695
438
 
696
- - The current sold-data implementation is explicitly interim. It is isolated behind [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) so we can replace the external-provider-backed implementation with our own internal sales-data system later **without changing downstream validation orchestration or the hosted validation route contract**.
697
- - The Terapeak/eBay research layer is intentionally isolated behind [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) so a future authenticated research integration can drop in without changing the route contract or downstream writes.
698
- - The orchestration-side historical research layer is intentionally isolated behind [`src/validation/providers/research.ts`](src/validation/providers/research.ts) so future previous-comeback resolution or external inference providers can be added without rewriting the validation runner.
439
+ See [Validation architecture](#validation-architecture) below for provider details.
699
440
 
700
441
  ### Remote client configuration
701
442
 
702
- Replace `https://your-server.com` with your actual `PUBLIC_BASE_URL`.
703
-
704
- #### Cline (automatic OAuth — no manual token needed)
705
-
706
- Cline supports MCP OAuth 2.1 discovery natively. It fetches the discovery document, registers itself, opens the eBay browser login, exchanges the auth code for a session token, and stores it — all automatically.
443
+ **Cline (automatic OAuth):**
444
+ ```json
445
+ {
446
+ "mcpServers": {
447
+ "ebay-sandbox": { "url": "https://your-server.com/sandbox/mcp" },
448
+ "ebay-production": { "url": "https://your-server.com/production/mcp" }
449
+ }
450
+ }
451
+ ```
452
+ Cline auto-discovers OAuth, opens browser login, exchanges auth code, and stores session token.
707
453
 
454
+ **Claude Desktop / Cursor (Bearer token):**
455
+ 1. Open `https://your-server.com/sandbox/oauth/start` → complete eBay login → copy session token
456
+ 2. Configure client:
708
457
  ```json
709
458
  {
710
459
  "mcpServers": {
711
460
  "ebay-sandbox": {
712
- "url": "https://your-server.com/sandbox/mcp"
713
- },
714
- "ebay-production": {
715
- "url": "https://your-server.com/production/mcp"
461
+ "url": "https://your-server.com/sandbox/mcp",
462
+ "headers": { "Authorization": "Bearer YOUR_SESSION_TOKEN" }
716
463
  }
717
464
  }
718
465
  }
719
466
  ```
720
467
 
721
- What Cline does automatically:
722
- 1. Fetches `/.well-known/oauth-authorization-server` for the scoped path
723
- 2. Registers at `POST /sandbox/register` (or `/production/register`)
724
- 3. Your browser opens `GET /sandbox/authorize`, which redirects to eBay login
725
- 4. After you grant access, eBay redirects to `/oauth/callback`, which issues an auth code
726
- 5. Cline exchanges the code at `POST /sandbox/token` for a session token and stores it
727
- 6. All subsequent `/sandbox/mcp` requests authenticate automatically
728
-
729
- #### Claude Desktop and Cursor (Bearer token)
730
-
731
- Claude Desktop and most other remote MCP clients require a pre-obtained session token. Complete the browser OAuth flow first:
732
-
733
- 1. Open `https://your-server.com/sandbox/oauth/start` (or `/production/oauth/start`) in a browser
734
- 2. Log in with your eBay account
735
- 3. Copy the session token from the confirmation page
736
-
737
- Then configure your client:
738
-
468
+ **Server request mode (custom headers):**
469
+ 1. Open `https://your-server.com/sandbox/oauth/start` or `https://your-server.com/production/oauth/start`
470
+ 2. Complete eBay login
471
+ 3. Copy the server request headers shown on the callback page
472
+ 4. Configure the MCP client with those headers:
739
473
  ```json
740
474
  {
741
475
  "mcpServers": {
742
- "ebay-sandbox": {
743
- "url": "https://your-server.com/sandbox/mcp",
476
+ "ebay-production-server": {
477
+ "url": "https://your-server.com/production/mcp",
744
478
  "headers": {
745
- "Authorization": "Bearer YOUR_SESSION_TOKEN"
479
+ "X-Ebay-Server-Request": "true",
480
+ "X-Ebay-Client-Id": "YOUR_EBAY_CLIENT_ID",
481
+ "X-Ebay-User-Id": "STORED_USER_ID_FROM_CALLBACK",
482
+ "X-Ebay-Environment": "production"
746
483
  }
747
484
  }
748
485
  }
749
486
  }
750
487
  ```
751
488
 
752
- #### Make / Zapier / TypingMind and similar platforms
489
+ **Server request mode (bearer-capable clients):**
490
+ Use this when the MCP client can set `Authorization` but cannot easily send several custom identity headers:
491
+ ```json
492
+ {
493
+ "mcpServers": {
494
+ "ebay-production-server": {
495
+ "url": "https://your-server.com/production/mcp",
496
+ "headers": {
497
+ "X-Ebay-Server-Request": "true",
498
+ "Authorization": "Bearer MCP_SERVER_ISSUED_BEARER_TOKEN_FROM_CALLBACK"
499
+ }
500
+ }
501
+ }
502
+ }
503
+ ```
753
504
 
754
- 1. Open `https://your-server.com/sandbox/oauth/start` in a browser and complete eBay login
755
- 2. Copy the session token from the confirmation page
756
- 3. Paste it as the **API Key / Bearer token** in the platform's MCP connector settings
757
- 4. Set the MCP endpoint URL to `https://your-server.com/sandbox/mcp`
505
+ **Make / Zapier / other platforms:**
506
+ 1. Complete OAuth via browser at `/oauth/start`
507
+ 2. If the platform supports multiple headers, use server request mode identity headers
508
+ 3. If the platform only supports one auth header, use server request mode with the MCP server-issued bearer lookup token from the callback page
509
+ 4. Set MCP URL to `https://your-server.com/sandbox/mcp`
758
510
 
759
511
  ---
760
512
 
761
- ## Available tools
513
+ ## Tool discovery for agents
514
+
515
+ AI agents discover tools through the MCP protocol's `tools/list` call. The eBay MCP server exposes 325+ tools with a predictable naming and description structure optimized for agent searchability.
516
+
517
+ ### Tool naming convention
518
+
519
+ All tools follow the pattern `ebay_<action>_<resource>`:
520
+
521
+ | Action | Meaning | Examples |
522
+ |--------|---------|----------|
523
+ | `get` | Read/fetch | `ebay_get_inventory_item`, `ebay_get_offers` |
524
+ | `create` | Create new | `ebay_create_offer`, `ebay_create_fulfillment_policy` |
525
+ | `update` | Modify existing | `ebay_update_offer`, `ebay_update_inventory_item` |
526
+ | `delete` | Remove | `ebay_delete_offer`, `ebay_delete_inventory_item` |
527
+ | `publish` | Activate | `ebay_publish_offer` |
528
+ | `withdraw` | Deactivate | `ebay_withdraw_offer` |
529
+ | `revise` | Modify listing | `ebay_revise_listing` |
530
+ | `bulk_` | Batch operations | `ebay_bulk_create_offer`, `ebay_bulk_update_price_quantity` |
531
+
532
+ ### Tool descriptions are self-documenting
533
+
534
+ Each tool description includes:
535
+ - **What it does** — concise action description
536
+ - **OAuth scope required** — the minimum scope needed
537
+ - **Minimum scope URL** — for fine-grained permission setup
538
+
539
+ Example:
540
+ ```
541
+ Get a specific inventory item by SKU.
542
+
543
+ Required OAuth Scope: sell.inventory.readonly or sell.inventory
544
+ Minimum Scope: https://api.ebay.com/oauth/api_scope/sell.inventory.readonly
545
+ ```
762
546
 
763
- 325+ tools across all eBay Sell API categories:
547
+ ### Tools organized by category
764
548
 
765
- - Account Management
766
- - Inventory Management
767
- - Order Fulfillment
768
- - Marketing & Promotions
769
- - Analytics & Reporting
770
- - Communication (messages, feedback, notifications, negotiation)
771
- - Metadata & Taxonomy
772
- - Developer Tools (key management, analytics)
773
- - Auth / Token helper tools
549
+ Tools are grouped into 13 category files in `src/tools/definitions/`:
774
550
 
775
- Full tool source: [`src/tools/definitions/`](src/tools/definitions/)
551
+ | Category file | Tool prefix | Key operations |
552
+ |---------------|-------------|----------------|
553
+ | `inventory.ts` | `ebay_*inventory*`, `ebay_*offer*` | CRUD inventory items, offers, locations, bulk ops |
554
+ | `fulfillment.ts` | `ebay_*fulfillment*`, `ebay_*shipment*` | Shipments, fulfillment orders, shipping labels |
555
+ | `account.ts` | `ebay_*policy*` | Payment, return, fulfillment policies |
556
+ | `marketing.ts` | `ebay_*promotion*`, `ebay_*markdown*` | Promotions, markdown listings |
557
+ | `analytics.ts` | `ebay_*analytics*`, `ebay_*report*` | Analytics, reporting |
558
+ | `communication.ts` | `ebay_*message*`, `ebay_*feedback*` | Messages, feedback, notifications |
559
+ | `metadata.ts` | `ebay_*metadata*`, `ebay_*policies*` | Category policies, listing metadata |
560
+ | `taxonomy.ts` | `ebay_*category*` | Category tree, suggestions, item specifics |
561
+ | `browse.ts` | `ebay_*browse*`, `ebay_*product*` | Browse/search products |
562
+ | `trading.ts` | `ebay_*listing*`, `ebay_*create_listing*` | Trading API (XML/SOAP) operations |
563
+ | `developer.ts` | `ebay_*signing*`, `ebay_*vero*` | Key management, VERO reports |
564
+ | `other.ts` | Mixed | Misc/legacy endpoints |
565
+ | `token-management.ts` | `ebay_*oauth*`, `ebay_*token*` | OAuth helpers, token management |
566
+
567
+ ### How agents find the right tool
568
+
569
+ 1. **Search by name pattern** — `ebay_get_*` for reads, `ebay_create_*` for writes, `ebay_*offer*` for offers
570
+ 2. **Search by description** — descriptions contain action keywords and resource names
571
+ 3. **Search by OAuth scope** — `sell.inventory` for inventory, `sell.fulfillment` for shipping
572
+ 4. **Use `tools/list`** — agents receive the full tool list with names and descriptions; filter programmatically
573
+
574
+ ### Tool annotations
575
+
576
+ Tools carry MCP annotations that help agents understand behavior:
577
+
578
+ | Annotation | Meaning |
579
+ |-----------|---------|
580
+ | `readOnlyHint: true` | Safe to call; no side effects |
581
+ | `destructiveHint: true` | Irreversible; use caution |
582
+ | `idempotentHint: true` | Safe to retry |
583
+ | `openWorldHint: true` | May interact with external services |
584
+
585
+ ---
586
+
587
+ ## Available tools
588
+
589
+ 325+ tools across all eBay Sell API categories. Full source: [`src/tools/definitions/`](src/tools/definitions/)
776
590
 
777
591
  ---
778
592
 
779
593
  ## Development
780
594
 
781
- ### Commands reference
595
+ ### Commands
782
596
 
783
597
  | Command | Description |
784
598
  |---------|-------------|
785
- | `pnpm run build` | Compile TypeScript to JavaScript |
786
- | `pnpm start` | Run local STDIO MCP server |
787
- | `pnpm run start:http` | Run hosted HTTP MCP server |
788
- | `pnpm run dev` | Local STDIO server with hot reload |
789
- | `pnpm run dev:http` | Hosted HTTP server with hot reload |
790
- | `pnpm test` | Run test suite |
791
- | `pnpm run setup` | Interactive local setup wizard |
792
- | `pnpm run sync` | Download latest eBay OpenAPI specs and regenerate types |
793
- | `pnpm run diagnose` | Check configuration and connectivity |
794
- | `pnpm run typecheck` | Run TypeScript type checking |
795
- | `pnpm run check` | Typecheck + lint + format check |
796
- | `pnpm run fix` | Auto-fix lint and format issues |
599
+ | `pnpm run build` | Compile TypeScript |
600
+ | `pnpm start` | Run local STDIO server |
601
+ | `pnpm run start:http` | Run hosted HTTP server |
602
+ | `pnpm run dev` | STDIO with hot reload |
603
+ | `pnpm run dev:http` | HTTP with hot reload |
604
+ | `pnpm test` | Run tests |
605
+ | `pnpm run setup` | Interactive setup wizard |
606
+ | `pnpm run sync` | Download eBay OpenAPI specs, regenerate types |
607
+ | `pnpm run diagnose` | Check config and connectivity |
608
+ | `pnpm run typecheck` | TypeScript type checking |
609
+ | `pnpm run check` | Typecheck + lint + format |
610
+ | `pnpm run fix` | Auto-fix lint and format |
797
611
 
798
612
  ### `pnpm run sync`
799
613
 
800
- Downloads the latest eBay OpenAPI specs, regenerates TypeScript types, and reports implemented vs missing endpoints. Run this when you want to pick up new eBay API surface:
614
+ Download latest eBay OpenAPI specs and regenerate types:
801
615
 
802
616
  ```bash
803
- pnpm run sync
804
- pnpm run typecheck
805
- pnpm run build
617
+ pnpm run sync && pnpm run typecheck && pnpm run build
806
618
  ```
807
619
 
808
- Review the diff, commit the generated changes you want to keep, and deploy.
809
-
810
- ### Local env management
620
+ Review the diff, commit changes you want to keep, and deploy.
811
621
 
812
- For local development, standard runtime scripts load `.env` via dotenvx only when a real local [`.env`](.env) file is present. Hosted platforms should provide environment variables directly — the server skips dotenvx for hosted/runtime environments (including Nixpacks-style deployments, which set [`DISABLE_DOTENVX`](nixpacks.toml:26)) and whenever no local [`.env`](.env) file exists.
622
+ ### Env management
813
623
 
814
624
  ```bash
815
625
  pnpm run env:encrypt # encrypt .env for safe sharing
@@ -825,40 +635,74 @@ EBAY_ENABLE_FILE_LOGGING=true # write logs to files
825
635
 
826
636
  ---
827
637
 
638
+ ## Validation architecture
639
+
640
+ The hosted backend includes a validation pipeline for item evaluation. Routes live in [`src/server-http.ts`](src/server-http.ts), logic in [`src/validation/`](src/validation).
641
+
642
+ **Module layout:**
643
+
644
+ | File | Purpose |
645
+ |------|---------|
646
+ | `types.ts` | Request/response contracts |
647
+ | `effective-context.ts` | Source-aware normalization (item vs event runs) |
648
+ | `run-validation.ts` | Orchestration entrypoint |
649
+ | `recommendation.ts` | Buy/track decision logic |
650
+ | `providers/ebay.ts` | Live browse-market snapshot |
651
+ | `providers/ebay-sold.ts` | Sold-data enrichment (temporary external provider) |
652
+ | `providers/terapeak.ts` | Terapeak / eBay Research metrics |
653
+ | `providers/ebay-research.ts` | Authenticated eBay Research fetcher |
654
+ | `providers/social.ts` | Twitter/X, YouTube, Reddit signals |
655
+ | `providers/chart.ts` | Chart-signal stub |
656
+ | `providers/research.ts` | Historical comeback research (Perplexity) |
657
+
658
+ **Flow:** Admin calls `/validation/run` → server resolves runner user → orchestrator queries all providers → merges signals → returns writes + decision + debug metadata.
659
+
660
+ **Merge precedence:** Terapeak > sold provider > browse provider for overlapping fields. Optional providers (social, research) only write when data resolves — never blank existing values.
661
+
662
+ **Known limitations:**
663
+ - Sold-data provider is temporary (external API via `SOLD_ITEMS_API_URL`)
664
+ - Social signals are supportive only — not authoritative buy triggers
665
+ - Chart provider is a stub
666
+ - eBay Research requires valid Playwright session
667
+
668
+ **eBay Research session management:**
669
+ - Bootstrap: `pnpm run research:bootstrap`
670
+ - Inspect: `pnpm run research:inspect-session`
671
+ - Browser check: `pnpm run research:check-browser`
672
+ - Session source precedence: KV → env vars → local files → fallback
673
+ - QStash alerts: 24h before, 6h before, and at expiry → Telegram
674
+
675
+ ---
676
+
828
677
  ## Testing & validation
829
678
 
830
679
  ```bash
831
- # Build and type check
832
- pnpm run build
833
- pnpm run typecheck
680
+ # Build and test
681
+ pnpm run build && pnpm run typecheck && pnpm test
834
682
 
835
- # Run the test suite
836
- pnpm test
837
-
838
- # Check connectivity and token status
683
+ # Connectivity check
839
684
  pnpm run diagnose
840
685
 
841
- # Verify hosted server health
686
+ # Hosted health
842
687
  curl https://your-server.com/health
843
688
 
844
- # Verify a session token
845
- curl -H "Authorization: Bearer <session-token>" https://your-server.com/whoami
689
+ # Session check
690
+ curl -H "Authorization: Bearer <token>" https://your-server.com/whoami
846
691
 
847
- # Verify validation runner health for sandbox
692
+ # Validation runner health
848
693
  curl https://your-server.com/sandbox/validation/health \
849
694
  -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
850
695
 
851
- # Run a hosted validation job
852
- curl -X POST https://your-server.com/sandbox/validation/run \
853
- -H "Content-Type: application/json" \
854
- -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY" \
855
- -d '{"validationId":"example-123","runType":"manual","cadence":"Daily","timestamp":"2026-03-31T00:00:00.000Z","item":{"recordId":"1","name":"Example Item","variation":[],"itemType":[],"releaseType":[],"releaseDate":null,"releasePeriod":[],"availability":[],"wholesalePrice":null,"supplierNames":[],"canonicalArtists":[],"relatedAlbums":[]},"validation":{"validationType":"default","buyDecision":"Hold","automationStatus":"Manual","autoCheckEnabled":false,"dDay":null,"artistTier":"unknown","initialBudget":null,"reserveBudget":null,"currentMetrics":{"avgWatchersPerListing":null,"preOrderListingsCount":null,"twitterTrending":false,"youtubeViews24hMillions":null,"redditPostsCount7d":null,"marketPriceUsd":null,"avgShippingCostUsd":null,"competitionLevel":null,"marketPriceTrend":"Stable","day1Sold":null,"day2Sold":null,"day3Sold":null,"day4Sold":null,"day5Sold":null,"daysTracked":null}}}'
696
+ # Privileged MCP admin-key bypass check
697
+ curl https://your-server.com/sandbox/mcp \
698
+ -H "Authorization: Bearer YOUR_ADMIN_API_KEY" \
699
+ -H "Accept: application/json, text/event-stream"
856
700
 
857
- # Test MCP endpoint returns auth challenge when no token is provided
701
+ # MCP auth challenge test
858
702
  curl -X POST https://your-server.com/sandbox/mcp \
859
703
  -H "Content-Type: application/json" \
860
704
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
861
- # → should return 401 with authorization_url
705
+ # → 401 with authorization_url
862
706
  ```
863
707
 
864
708
  ---
@@ -867,59 +711,37 @@ curl -X POST https://your-server.com/sandbox/mcp \
867
711
 
868
712
  ### Hosted MCP returns 406
869
713
 
870
- Include the correct `Accept` header in your MCP client:
871
- ```
872
- Accept: application/json, text/event-stream
873
- ```
714
+ Include `Accept: application/json, text/event-stream` header.
874
715
 
875
716
  ### OAuth callback: "Invalid or expired OAuth state"
876
717
 
877
- OAuth state records expire in 15 minutes. If you see this error, restart the browser OAuth flow.
878
-
879
- ### Token verification fails on existing refresh token
718
+ OAuth state expires in 15 minutes. Restart the browser flow.
880
719
 
881
- Refresh tokens expire after ~18 months or can be revoked by eBay (password changes, etc.). Run the setup wizard again to obtain a new one:
882
- ```bash
883
- pnpm run setup
884
- ```
720
+ ### Token verification fails on refresh token
885
721
 
886
- In hosted mode, start a new browser OAuth flow at `/sandbox/oauth/start` or `/production/oauth/start`.
722
+ Refresh tokens expire after ~18 months or can be revoked. Run `pnpm run setup` or start new browser OAuth at `/oauth/start`.
887
723
 
888
- ### Session token no longer works in hosted mode
724
+ ### Session token no longer works
889
725
 
890
- Check whether the session was revoked or expired:
891
726
  ```bash
727
+ # Check status:
892
728
  curl -H "Authorization: Bearer <token>" https://your-server.com/whoami
893
- ```
894
729
 
895
- Revoke exposed session tokens via the admin endpoint:
896
- ```bash
730
+ # Revoke if exposed:
897
731
  curl -X POST https://your-server.com/admin/session/<token>/revoke \
898
732
  -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
899
733
  ```
900
734
 
901
735
  ### Validation health is degraded
902
736
 
903
- Start with the environment-scoped health endpoint:
904
-
905
737
  ```bash
906
738
  curl https://your-server.com/sandbox/validation/health \
907
739
  -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
908
740
  ```
909
741
 
910
- Common causes:
911
-
912
- - `VALIDATION_RUNNER_USER_ID` or the env-specific override is missing
913
- - the validation runner user has no stored refresh-token-backed credentials in the hosted token store
914
- - the refresh token is expired or revoked upstream
915
- - `SOLD_ITEMS_API_URL` or `SOLD_ITEMS_API_KEY` is missing, causing sold enrichment to degrade
916
- - one or more social-provider credentials are absent, which causes the related supportive signal to degrade gracefully instead of failing the entire run
742
+ Check: `VALIDATION_RUNNER_USER_ID` set, runner has stored tokens, tokens not expired, `SOLD_ITEMS_API_URL` configured.
917
743
 
918
- ### eBay Research debug shows `authState = missing` or `sessionStrategy = none`
919
-
920
- This means the first-party research provider could not load a validated authenticated session and validation is intentionally falling back to browse and/or the temporary sold provider.
921
-
922
- Run this checklist:
744
+ ### eBay Research shows `authState = missing`
923
745
 
924
746
  ```bash
925
747
  pnpm run playwright:install
@@ -928,28 +750,23 @@ pnpm run research:check-browser
928
750
  pnpm run research:bootstrap
929
751
  ```
930
752
 
931
- Expected post-bootstrap debug characteristics from [`src/validation/providers/ebay-research.ts`](src/validation/providers/ebay-research.ts):
753
+ Expected after bootstrap: `authState = loaded`, `sessionStrategy = storage_state`, `sessionSource = kv`, `authValidationSucceeded = true`.
932
754
 
933
- - `authState = loaded`
934
- - `sessionStrategy = storage_state`
935
- - `sessionSource = kv`
936
- - `kvLoadAttempted = true`
937
- - `kvLoadSucceeded = true`
938
- - `authValidationAttempted = true`
939
- - `authValidationSucceeded = true`
755
+ ### Stale MCP sessions after deployment
940
756
 
941
- If the provider still reports `missing`, verify that your hosted deployment can reach the configured KV backend, that Chromium is available in the runtime image, and that the stored eBay session has not expired. Refresh the session by rerunning `pnpm run research:bootstrap`.
757
+ After deploying new code, agents' MCP sessions may still point to old container code. **Agents must restart their MCP session** (`/reset` or reopen chat) to negotiate a fresh connection.
942
758
 
943
- If `authDebug.tokenEndpoint` or the captured upstream response looks wrong, verify the environment-specific OAuth configuration and token-base resolution.
759
+ ---
944
760
 
945
- ### Security checklist
761
+ ## Security checklist
946
762
 
947
- - Do not commit `.env` or session tokens to version control
948
- - Protect `/oauth/start` and `/admin/*` with `OAUTH_START_KEY` and `ADMIN_API_KEY`
949
- - Keep `/oauth/callback` publicly reachable (eBay redirects to it after login)
950
- - Keep `/health` reachable if Render uses it for health checks
951
- - For production-grade isolation, optionally place `/`, `/oauth/start`, and `/admin/*` behind Cloudflare Access
952
- - Rotate exposed eBay client secrets and update your secret file
763
+ - Do not commit `.env` or session tokens
764
+ - Protect `/oauth/start` with `OAUTH_START_KEY`, `/admin/*` and `/validation/*` with `ADMIN_API_KEY`
765
+ - Keep `ADMIN_API_KEY` long, random, server-side only, and separate from user session tokens; it also works as a privileged MCP Bearer-token bypass
766
+ - Keep `/oauth/callback` publicly reachable (eBay redirects here)
767
+ - Keep `/health` reachable for deployment health checks
768
+ - Rotate exposed eBay credentials and update secret file
769
+ - For production isolation, consider Cloudflare Access on `/`, `/oauth/start`, `/admin/*`
953
770
 
954
771
  ---
955
772