duckpond-mcp-server 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -185,11 +185,60 @@ Manually detach a user's database from the cache to free resources.
185
185
  - `DUCKPOND_THREADS` - Number of threads (default: `4`)
186
186
  - `DUCKPOND_CACHE_TYPE` - Cache type: `disk`, `memory`, `noop` (default: `disk`)
187
187
 
188
+ #### Storage Configuration
189
+
190
+ By default, DuckPond stores databases locally. For cloud deployments, configure R2 or S3.
191
+
192
+ **Local Storage (Default)**
193
+
194
+ ```bash
195
+ # Databases stored in ~/.duckpond/data by default
196
+ # Customize with:
197
+ export DUCKPOND_DATA_DIR=/path/to/data
198
+
199
+ npx duckpond-mcp-server
200
+ ```
201
+
202
+ **Cloudflare R2**
203
+
204
+ ```bash
205
+ export DUCKPOND_R2_ACCOUNT_ID=your-account-id
206
+ export DUCKPOND_R2_ACCESS_KEY_ID=your-access-key
207
+ export DUCKPOND_R2_SECRET_ACCESS_KEY=your-secret-key
208
+ export DUCKPOND_R2_BUCKET=your-bucket
209
+
210
+ npx duckpond-mcp-server
211
+ ```
212
+
213
+ **AWS S3**
214
+
215
+ ```bash
216
+ export DUCKPOND_S3_REGION=us-east-1
217
+ export DUCKPOND_S3_ACCESS_KEY_ID=your-access-key
218
+ export DUCKPOND_S3_SECRET_ACCESS_KEY=your-secret-key
219
+ export DUCKPOND_S3_BUCKET=your-bucket
220
+
221
+ npx duckpond-mcp-server
222
+ ```
223
+
224
+ **S3-Compatible (MinIO, etc.)**
225
+
226
+ ```bash
227
+ export DUCKPOND_S3_REGION=us-east-1
228
+ export DUCKPOND_S3_ACCESS_KEY_ID=minioadmin
229
+ export DUCKPOND_S3_SECRET_ACCESS_KEY=minioadmin
230
+ export DUCKPOND_S3_BUCKET=duckpond
231
+ export DUCKPOND_S3_ENDPOINT=http://localhost:9000
232
+
233
+ npx duckpond-mcp-server
234
+ ```
235
+
188
236
  #### Multi-Tenant Settings
189
237
 
190
238
  - `DUCKPOND_MAX_ACTIVE_USERS` - LRU cache size (default: `10`)
191
239
  - `DUCKPOND_EVICTION_TIMEOUT` - Idle timeout in ms (default: `300000`)
192
- - `DUCKPOND_STRATEGY` - Storage strategy: `parquet`, `duckdb`, `hybrid` (default: `parquet`)
240
+ - `DUCKPOND_STRATEGY` - Storage strategy: `parquet`, `duckdb`, `hybrid` (default: `duckdb`)
241
+ - `DUCKPOND_DATA_DIR` - Local data directory (default: `~/.duckpond/data`)
193
242
 
194
243
  #### Cloudflare R2 Configuration
195
244
 
@@ -272,67 +321,89 @@ npx duckpond-mcp-server --transport http
272
321
  ### DuckDB UI
273
322
 
274
323
  - `GET /ui` - UI status and available users
275
- - `GET /ui/:userId` - Start UI for specific user
276
- - `GET /ui/*` - Proxy to DuckDB UI server
324
+ - `GET /ui/:userId` - Start UI for specific user (returns URL for direct access)
277
325
 
278
326
  ## DuckDB UI
279
327
 
280
- The MCP server includes built-in support for [DuckDB UI](https://duckdb.org/docs/stable/core_extensions/ui.html), allowing you to visually inspect and debug any user's database through a web browser.
328
+ The MCP server includes built-in support for [DuckDB UI](https://duckdb.org/docs/stable/core_extensions/ui.html), allowing you to visually inspect and debug your database through a web browser.
281
329
 
282
- ### HTTP Mode (default)
330
+ ### How It Works
283
331
 
284
- When running in HTTP mode, the UI is automatically available:
332
+ With `DUCKPOND_DEFAULT_USER` set, the UI **auto-starts** when the server starts. Just open `http://localhost:4213` in your browser.
285
333
 
286
- ```bash
287
- # Start server
288
- npx duckpond-mcp-server --transport http --port 3000
334
+ The UI runs on port 4213 because DuckDB UI requires specific browser features (SharedArrayBuffer) that work best with direct access.
289
335
 
290
- # Open UI for user "claude"
291
- # Browser: http://localhost:3000/ui/claude
292
- ```
293
-
294
- ### stdio Mode
295
-
296
- For stdio mode (Claude Desktop), enable the UI server with the `--ui` flag:
297
-
298
- ```bash
299
- # Start with UI enabled
300
- npx duckpond-mcp-server --transport stdio --ui --ui-port 4000
301
-
302
- # MCP communicates via stdin/stdout
303
- # UI available at: http://localhost:4000/ui/claude
304
- ```
305
-
306
- ### Claude Desktop with UI
307
-
308
- Add UI support to your Claude Desktop config:
336
+ ### Claude Desktop Config (Recommended)
309
337
 
310
338
  ```json
311
339
  {
312
340
  "mcpServers": {
313
341
  "duckpond": {
314
342
  "command": "npx",
315
- "args": ["-y", "duckpond-mcp-server", "--ui", "--ui-port", "4000"],
343
+ "args": ["-y", "duckpond-mcp-server", "--ui"],
316
344
  "env": {
317
- "DUCKPOND_DEFAULT_USER": "claude"
345
+ "DUCKPOND_DEFAULT_USER": "claude",
346
+ "DUCKPOND_DATA_DIR": "${HOME}/.duckpond/data"
318
347
  }
319
348
  }
320
349
  }
321
350
  }
322
351
  ```
323
352
 
324
- Then browse to `http://localhost:4000/ui/claude` to inspect the database.
353
+ The UI automatically starts for the default user. Open `http://localhost:4213` in your browser.
354
+
355
+ ### HTTP Mode
356
+
357
+ ```bash
358
+ # Start server
359
+ npx duckpond-mcp-server --transport http --port 3000
360
+
361
+ # Start UI for user "claude"
362
+ curl http://localhost:3000/ui/claude
363
+
364
+ # Access UI directly
365
+ # Browser: http://localhost:4213
366
+ ```
367
+
368
+ ### stdio Mode without Default User
369
+
370
+ If no `DUCKPOND_DEFAULT_USER` is set, a management server starts for manual user selection:
371
+
372
+ ```bash
373
+ # Start with UI management server
374
+ npx duckpond-mcp-server --ui --ui-port 4000
375
+
376
+ # Start UI for a user
377
+ curl http://localhost:4000/ui/claude
378
+
379
+ # Access UI directly
380
+ # Browser: http://localhost:4213
381
+ ```
325
382
 
326
383
  ### Docker
327
384
 
328
385
  ```bash
329
- # Run with port mapping
330
- docker run -p 3000:3000 duckpond-mcp-server
386
+ # Using docker-compose (recommended)
387
+ docker compose up -d
388
+
389
+ # Start UI for a user
390
+ curl http://localhost:3000/ui/claude
331
391
 
332
- # Access UI
333
- # Browser: http://localhost:3000/ui/claude
392
+ # Access UI directly
393
+ # Browser: http://localhost:4213
334
394
  ```
335
395
 
396
+ ```bash
397
+ # Simple docker run
398
+ docker run -p 3000:3000 -p 4213:4213 duckpond-mcp-server
399
+
400
+ # Start UI for a user, then access directly
401
+ curl http://localhost:3000/ui/claude
402
+ # Browser: http://localhost:4213
403
+ ```
404
+
405
+ **Why direct port access?** DuckDB UI uses SharedArrayBuffer which requires specific CORS headers. Direct access to port 4213 ensures full compatibility with the UI's WebAssembly requirements.
406
+
336
407
  ### UI Features
337
408
 
338
409
  - **Database Explorer** - Browse schemas, tables, and columns
@@ -340,20 +411,20 @@ docker run -p 3000:3000 duckpond-mcp-server
340
411
  - **Table Summaries** - Row counts, data profiles, previews
341
412
  - **Column Explorer** - Detailed column statistics and insights
342
413
 
343
- ### Switching Users
414
+ ### Switching Users (HTTP Mode)
344
415
 
345
- Navigate to `/ui/:differentUserId` to switch between users. Only one user's UI is active at a time - switching automatically stops the previous UI and starts for the new user.
416
+ In HTTP mode, navigate to `/ui/:differentUserId` to switch between users. Only one user's UI is active at a time - switching automatically stops the previous UI and starts for the new user.
346
417
 
347
418
  ### Environment Variables
348
419
 
349
- - `DUCKPOND_UI_ENABLED` - Enable UI in stdio mode (default: `false`)
350
- - `DUCKPOND_UI_PORT` - UI server port for stdio mode (default: `4000`)
420
+ - `DUCKPOND_DEFAULT_USER` - Default user ID; when set, UI auto-starts for this user
421
+ - `DUCKPOND_UI_ENABLED` - Enable UI (default: `false`, or use `--ui` flag)
351
422
 
352
423
  ### CLI Flags
353
424
 
354
- - `--ui` - Enable UI server in stdio mode
355
- - `--ui-port <port>` - UI server port (default: `4000`)
356
- - `--ui-internal-port <port>` - Internal DuckDB UI port (default: `4213`)
425
+ - `--ui` - Enable DuckDB UI (auto-starts for `DUCKPOND_DEFAULT_USER`)
426
+ - `--ui-port <port>` - Management server port, only used when no default user (default: `4000`)
427
+ - `--ui-internal-port <port>` - DuckDB UI port (default: `4213`)
357
428
 
358
429
  ## Development
359
430
 
@@ -0,0 +1,2 @@
1
+ import{b as a}from"./chunk-A3S6D44B.js";import{serve as l}from"@hono/node-server";import{Hono as U}from"hono";var n=a.core;async function h(i){let{port:s,duckpond:o}=i,c=o.getUIPort(),u=new U;u.get("/ui",r=>{let t=o.getCurrentUIUser(),e=o.listUsers();return r.json({message:t?`UI active for user: ${t}. Access directly at http://localhost:${c}`:"No UI active. Visit /ui/:userId to start DuckDB UI for a user.",currentUser:t,uiUrl:t?`http://localhost:${c}`:null,availableUsers:e.success?e.data.users:[]})}),u.get("/ui/:userId",async r=>{let t=r.req.param("userId");n(`[UI Server] Starting UI for user: ${t}`);let e=await o.startUI(t);return e.success?r.json({success:!0,message:`UI started for user: ${t}`,uiUrl:`http://localhost:${c}`,hint:"Access the DuckDB UI directly at the uiUrl above"}):r.json({error:"Failed to start UI",message:e.error.message,details:e.error.details},500)}),u.get("/",r=>{let t=o.getCurrentUIUser();return r.json({name:"DuckPond UI Server",description:"Lightweight HTTP server for DuckDB UI access in stdio mode",currentUIUser:t,uiUrl:t?`http://localhost:${c}`:null,endpoints:{startUI:`http://localhost:${s}/ui/:userId`,status:`http://localhost:${s}/ui`}})}),l({fetch:u.fetch,port:s}),n(`\u2713 UI server running at http://localhost:${s}/ui`),n(` Visit http://localhost:${s}/ui/:userId to start DuckDB UI`),n(` Then access DuckDB UI directly at http://localhost:${c}`)}export{h as a};
2
+ //# sourceMappingURL=chunk-E5JCTMWX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui-server.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\"\nimport { Hono } from \"hono\"\n\nimport type { DuckPondServer } from \"./server-core\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.core\n\nexport type UIServerOptions = {\n port: number\n duckpond: DuckPondServer\n}\n\n/**\n * Start a lightweight HTTP server for UI access in stdio mode\n * This allows starting DuckDB UI even when MCP is running over stdio\n */\nexport async function startUIServer(options: UIServerOptions): Promise<void> {\n const { port, duckpond } = options\n const uiInternalPort = duckpond.getUIPort()\n\n const app = new Hono()\n\n // GET /ui - Info endpoint\n app.get(\"/ui\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n const listResult = duckpond.listUsers()\n return c.json({\n message: currentUser\n ? `UI active for user: ${currentUser}. Access directly at http://localhost:${uiInternalPort}`\n : \"No UI active. Visit /ui/:userId to start DuckDB UI for a user.\",\n currentUser,\n uiUrl: currentUser ? `http://localhost:${uiInternalPort}` : null,\n availableUsers: listResult.success ? listResult.data.users : [],\n })\n })\n\n // GET /ui/:userId - Start UI for a specific user\n app.get(\"/ui/:userId\", async (c) => {\n const userId = c.req.param(\"userId\")\n\n log(`[UI Server] Starting UI for user: ${userId}`)\n const result = await duckpond.startUI(userId)\n\n if (!result.success) {\n return c.json(\n {\n error: \"Failed to start UI\",\n message: result.error.message,\n details: result.error.details,\n },\n 500,\n )\n }\n\n return c.json({\n success: true,\n message: `UI started for user: ${userId}`,\n uiUrl: `http://localhost:${uiInternalPort}`,\n hint: \"Access the DuckDB UI directly at the uiUrl above\",\n })\n })\n\n // Root endpoint with info\n app.get(\"/\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n return c.json({\n name: \"DuckPond UI Server\",\n description: \"Lightweight HTTP server for DuckDB UI access in stdio mode\",\n currentUIUser: currentUser,\n uiUrl: currentUser ? `http://localhost:${uiInternalPort}` : null,\n endpoints: {\n startUI: `http://localhost:${port}/ui/:userId`,\n status: `http://localhost:${port}/ui`,\n },\n })\n })\n\n // Start the server\n serve({\n fetch: app.fetch,\n port,\n })\n\n log(`✓ UI server running at http://localhost:${port}/ui`)\n log(` Visit http://localhost:${port}/ui/:userId to start DuckDB UI`)\n log(` Then access DuckDB UI directly at http://localhost:${uiInternalPort}`)\n}\n"],"mappings":"wCAAA,OAAS,SAAAA,MAAa,oBACtB,OAAS,QAAAC,MAAY,OAKrB,IAAMC,EAAMC,EAAQ,KAWpB,eAAsBC,EAAcC,EAAyC,CAC3E,GAAM,CAAE,KAAAC,EAAM,SAAAC,CAAS,EAAIF,EACrBG,EAAiBD,EAAS,UAAU,EAEpCE,EAAM,IAAIC,EAGhBD,EAAI,IAAI,MAAQE,GAAM,CACpB,IAAMC,EAAcL,EAAS,iBAAiB,EACxCM,EAAaN,EAAS,UAAU,EACtC,OAAOI,EAAE,KAAK,CACZ,QAASC,EACL,uBAAuBA,CAAW,yCAAyCJ,CAAc,GACzF,iEACJ,YAAAI,EACA,MAAOA,EAAc,oBAAoBJ,CAAc,GAAK,KAC5D,eAAgBK,EAAW,QAAUA,EAAW,KAAK,MAAQ,CAAC,CAChE,CAAC,CACH,CAAC,EAGDJ,EAAI,IAAI,cAAe,MAAOE,GAAM,CAClC,IAAMG,EAASH,EAAE,IAAI,MAAM,QAAQ,EAEnCT,EAAI,qCAAqCY,CAAM,EAAE,EACjD,IAAMC,EAAS,MAAMR,EAAS,QAAQO,CAAM,EAE5C,OAAKC,EAAO,QAWLJ,EAAE,KAAK,CACZ,QAAS,GACT,QAAS,wBAAwBG,CAAM,GACvC,MAAO,oBAAoBN,CAAc,GACzC,KAAM,kDACR,CAAC,EAfQG,EAAE,KACP,CACE,MAAO,qBACP,QAASI,EAAO,MAAM,QACtB,QAASA,EAAO,MAAM,OACxB,EACA,GACF,CASJ,CAAC,EAGDN,EAAI,IAAI,IAAME,GAAM,CAClB,IAAMC,EAAcL,EAAS,iBAAiB,EAC9C,OAAOI,EAAE,KAAK,CACZ,KAAM,qBACN,YAAa,6DACb,cAAeC,EACf,MAAOA,EAAc,oBAAoBJ,CAAc,GAAK,KAC5D,UAAW,CACT,QAAS,oBAAoBF,CAAI,cACjC,OAAQ,oBAAoBA,CAAI,KAClC,CACF,CAAC,CACH,CAAC,EAGDU,EAAM,CACJ,MAAOP,EAAI,MACX,KAAAH,CACF,CAAC,EAEDJ,EAAI,gDAA2CI,CAAI,KAAK,EACxDJ,EAAI,4BAA4BI,CAAI,gCAAgC,EACpEJ,EAAI,wDAAwDM,CAAc,EAAE,CAC9E","names":["serve","Hono","log","loggers","startUIServer","options","port","duckpond","uiInternalPort","app","Hono","c","currentUser","listResult","userId","result","serve"]}
@@ -0,0 +1,2 @@
1
+ import{a as n,b as a}from"./chunk-A3S6D44B.js";import{DuckPond as c}from"duckpond";var r=a.core,u=class{constructor(t){this.config=t;n(this,"pond",null);n(this,"initialized",!1);n(this,"currentUIUserId",null);n(this,"uiInternalPort",4213);n(this,"uiKeepaliveTimer",null);n(this,"UI_KEEPALIVE_INTERVAL",3e4);r("DuckPondServer created")}async init(){if(this.initialized)return r("Already initialized"),{success:!0,data:void 0};r("Initializing DuckPond...");let t=Date.now();this.pond=new c(this.config);let e=await this.pond.init();return this.handleEither(e,Date.now()-t)}async query(t,e){if(!this.pond)return this.notInitializedError();r(`Query for user ${t}: ${e.substring(0,100)}...`);let s=Date.now(),i=await this.pond.query(t,e);return this.handleEither(i,Date.now()-s)}async execute(t,e){if(!this.pond)return this.notInitializedError();r(`Execute for user ${t}: ${e.substring(0,100)}...`);let s=Date.now(),i=await this.pond.execute(t,e);return this.handleEither(i,Date.now()-s)}async getUserStats(t){if(!this.pond)return this.notInitializedError();r(`Getting stats for user ${t}`);let e=Date.now(),s=await this.pond.getUserStats(t);return this.handleEither(s,Date.now()-e)}isAttached(t){if(!this.pond)return this.notInitializedError();let e=this.pond.isAttached(t);return r(`User ${t} attached: ${e}`),{success:!0,data:e}}async detachUser(t){if(!this.pond)return this.notInitializedError();r(`Detaching user ${t}`);let e=Date.now(),s=await this.pond.detachUser(t);return this.handleEither(s,Date.now()-e)}listUsers(){if(!this.pond)return this.notInitializedError();r("Listing cached users");let t=this.pond.listUsers();return{success:!0,data:{users:t.users.toArray(),count:t.count,maxActiveUsers:t.maxActiveUsers,utilizationPercent:t.utilizationPercent}}}async close(){if(!this.pond)return{success:!0,data:void 0};r("Closing DuckPond...");let t=Date.now();this.currentUIUserId&&await this.stopUI();let e=await this.pond.close();return this.initialized=!1,this.pond=null,this.handleEither(e,Date.now()-t)}async startUI(t){if(!this.pond)return this.notInitializedError();r(`Starting UI for user ${t}`);let e=Date.now();if(this.currentUIUserId&&this.currentUIUserId!==t&&(r(`Stopping UI for previous user ${this.currentUIUserId}`),await this.stopUI()),this.currentUIUserId===t)return r(`UI already running for user ${t}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-e};let s=await this.execute(t,"INSTALL ui; LOAD ui;");if(!s.success)return r(`Failed to install UI extension: ${s.error.message}`),s;let i=await this.execute(t,`SET ui_local_port = ${this.uiInternalPort}`);if(!i.success)return r(`Failed to set UI port: ${i.error.message}`),i;let o=await this.execute(t,"CALL start_ui_server()");return o.success?(this.currentUIUserId=t,this.startUIKeepalive(t),r(`UI started for user ${t} on port ${this.uiInternalPort}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-e}):(r(`Failed to start UI server: ${o.error.message}`),o)}startUIKeepalive(t){this.stopUIKeepalive(),r(`Starting UI keepalive for user ${t}`),this.uiKeepaliveTimer=setInterval(async()=>{this.pond&&this.currentUIUserId===t&&(r(`UI keepalive ping for user ${t}`),await this.pond.query(t,"SELECT 1"))},this.UI_KEEPALIVE_INTERVAL)}stopUIKeepalive(){this.uiKeepaliveTimer&&(r("Stopping UI keepalive"),clearInterval(this.uiKeepaliveTimer),this.uiKeepaliveTimer=null)}async stopUI(){if(this.stopUIKeepalive(),!this.currentUIUserId)return r("No UI running to stop"),{success:!0,data:void 0};if(!this.pond)return this.currentUIUserId=null,{success:!0,data:void 0};r(`Stopping UI for user ${this.currentUIUserId}`);let t=Date.now(),e=await this.execute(this.currentUIUserId,"CALL stop_ui_server()"),s=this.currentUIUserId;return this.currentUIUserId=null,e.success?(r(`UI stopped for user ${s}`),{success:!0,data:void 0,executionTime:Date.now()-t}):(r(`Failed to stop UI: ${e.error.message}`),e)}getCurrentUIUser(){return this.currentUIUserId}setUIPort(t){this.uiInternalPort=t}getUIPort(){return this.uiInternalPort}handleEither(t,e){return t.fold(s=>({success:!1,error:{code:this.mapErrorCode(s.code),message:s.message,details:{originalCode:s.code,context:s.context,cause:s.cause?.message}}}),s=>({success:!0,data:s,executionTime:e}))}mapErrorCode(t){return{CONNECTION_FAILED:"SERVICE_UNAVAILABLE",CONNECTION_TIMEOUT:"TIMEOUT",R2_CONNECTION_ERROR:"SERVICE_UNAVAILABLE",S3_CONNECTION_ERROR:"SERVICE_UNAVAILABLE",USER_NOT_FOUND:"NOT_FOUND",USER_ALREADY_EXISTS:"ALREADY_EXISTS",USER_NOT_ATTACHED:"NOT_FOUND",QUERY_EXECUTION_ERROR:"INVALID_REQUEST",QUERY_TIMEOUT:"TIMEOUT",INVALID_SQL:"INVALID_REQUEST",MEMORY_LIMIT_EXCEEDED:"RESOURCE_EXHAUSTED",STORAGE_ERROR:"SERVICE_UNAVAILABLE",STORAGE_QUOTA_EXCEEDED:"RESOURCE_EXHAUSTED",INVALID_CONFIG:"INVALID_ARGUMENT",NOT_INITIALIZED:"FAILED_PRECONDITION",UNKNOWN_ERROR:"INTERNAL_ERROR"}[t]||"INTERNAL_ERROR"}notInitializedError(){return{success:!1,error:{code:"FAILED_PRECONDITION",message:"DuckPond not initialized. Call init() first."}}}};export{u as a};
2
+ //# sourceMappingURL=chunk-SRB6JVEI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server-core.ts"],"sourcesContent":["import type { Either } from \"duckpond\"\nimport { DuckPond, type DuckPondConfig, type ErrorCode, type ListUsersResult, type UserStats } from \"duckpond\"\n\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.core\n\n/**\n * Result type for MCP tool responses\n */\nexport type MCPResult<T> =\n | {\n success: true\n data: T\n executionTime?: number\n }\n | {\n success: false\n error: {\n code: string\n message: string\n details?: {\n originalCode?: ErrorCode\n context?: Record<string, unknown>\n cause?: string\n }\n }\n }\n\n/**\n * Configuration for DuckPond MCP Server\n * Extends DuckPondConfig with server-specific options\n */\nexport type DuckPondServerConfig = DuckPondConfig & {\n /** Directory for persistent database storage (default: ~/.duckpond/data) */\n dataDir?: string\n}\n\n/**\n * Core DuckPond MCP Server\n *\n * Wraps DuckPond library with MCP-compatible result types\n */\nexport class DuckPondServer {\n private pond: DuckPond | null = null\n private initialized = false\n private currentUIUserId: string | null = null\n private uiInternalPort: number = 4213\n private uiKeepaliveTimer: ReturnType<typeof setInterval> | null = null\n private readonly UI_KEEPALIVE_INTERVAL = 30000 // 30 seconds\n\n constructor(private config: DuckPondServerConfig) {\n log(\"DuckPondServer created\")\n }\n\n /**\n * Initialize the DuckPond instance\n */\n async init(): Promise<MCPResult<void>> {\n if (this.initialized) {\n log(\"Already initialized\")\n return { success: true, data: undefined }\n }\n\n log(\"Initializing DuckPond...\")\n const startTime = Date.now()\n\n this.pond = new DuckPond(this.config)\n const result = await this.pond.init()\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute a SQL query for a user\n */\n async query<T = unknown>(userId: string, sql: string): Promise<MCPResult<T[]>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Query for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.query<T>(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute DDL/DML without returning results\n */\n async execute(userId: string, sql: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Execute for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.execute(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Get statistics about a user's database\n */\n async getUserStats(userId: string): Promise<MCPResult<UserStats>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Getting stats for user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.getUserStats(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Check if a user is currently cached\n */\n isAttached(userId: string): MCPResult<boolean> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n const attached = this.pond.isAttached(userId)\n log(`User ${userId} attached: ${attached}`)\n\n return {\n success: true,\n data: attached,\n }\n }\n\n /**\n * Manually detach a user from the cache\n */\n async detachUser(userId: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Detaching user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.detachUser(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * List all currently cached users\n */\n listUsers(): MCPResult<{ users: string[]; count: number; maxActiveUsers: number; utilizationPercent: number }> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(\"Listing cached users\")\n const result = this.pond.listUsers()\n\n return {\n success: true,\n data: {\n users: result.users.toArray(), // Convert List<string> to string[]\n count: result.count,\n maxActiveUsers: result.maxActiveUsers,\n utilizationPercent: result.utilizationPercent,\n },\n }\n }\n\n /**\n * Close the DuckPond instance\n */\n async close(): Promise<MCPResult<void>> {\n if (!this.pond) {\n return { success: true, data: undefined }\n }\n\n log(\"Closing DuckPond...\")\n const startTime = Date.now()\n\n // Stop UI server if running\n if (this.currentUIUserId) {\n await this.stopUI()\n }\n\n const result = await this.pond.close()\n this.initialized = false\n this.pond = null\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Start DuckDB UI for a specific user\n * Only one user's UI can be active at a time\n */\n async startUI(userId: string): Promise<MCPResult<{ port: number }>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Starting UI for user ${userId}`)\n const startTime = Date.now()\n\n // Stop existing UI if different user\n if (this.currentUIUserId && this.currentUIUserId !== userId) {\n log(`Stopping UI for previous user ${this.currentUIUserId}`)\n await this.stopUI()\n }\n\n // If already running for this user, just return success\n if (this.currentUIUserId === userId) {\n log(`UI already running for user ${userId}`)\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n // Install and load UI extension\n const installResult = await this.execute(userId, \"INSTALL ui; LOAD ui;\")\n if (!installResult.success) {\n log(`Failed to install UI extension: ${installResult.error.message}`)\n return installResult as MCPResult<{ port: number }>\n }\n\n // Set the UI port\n const portResult = await this.execute(userId, `SET ui_local_port = ${this.uiInternalPort}`)\n if (!portResult.success) {\n log(`Failed to set UI port: ${portResult.error.message}`)\n return portResult as MCPResult<{ port: number }>\n }\n\n // Start the UI server (without opening browser)\n const startResult = await this.execute(userId, \"CALL start_ui_server()\")\n if (!startResult.success) {\n log(`Failed to start UI server: ${startResult.error.message}`)\n return startResult as MCPResult<{ port: number }>\n }\n\n this.currentUIUserId = userId\n this.startUIKeepalive(userId)\n log(`UI started for user ${userId} on port ${this.uiInternalPort}`)\n\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n /**\n * Start keepalive timer to prevent UI user from being evicted\n */\n private startUIKeepalive(userId: string): void {\n this.stopUIKeepalive()\n log(`Starting UI keepalive for user ${userId}`)\n this.uiKeepaliveTimer = setInterval(async () => {\n if (this.pond && this.currentUIUserId === userId) {\n log(`UI keepalive ping for user ${userId}`)\n await this.pond.query(userId, \"SELECT 1\")\n }\n }, this.UI_KEEPALIVE_INTERVAL)\n }\n\n /**\n * Stop the keepalive timer\n */\n private stopUIKeepalive(): void {\n if (this.uiKeepaliveTimer) {\n log(\"Stopping UI keepalive\")\n clearInterval(this.uiKeepaliveTimer)\n this.uiKeepaliveTimer = null\n }\n }\n\n /**\n * Stop the currently running DuckDB UI\n */\n async stopUI(): Promise<MCPResult<void>> {\n this.stopUIKeepalive()\n\n if (!this.currentUIUserId) {\n log(\"No UI running to stop\")\n return { success: true, data: undefined }\n }\n\n if (!this.pond) {\n this.currentUIUserId = null\n return { success: true, data: undefined }\n }\n\n log(`Stopping UI for user ${this.currentUIUserId}`)\n const startTime = Date.now()\n\n const result = await this.execute(this.currentUIUserId, \"CALL stop_ui_server()\")\n const previousUser = this.currentUIUserId\n this.currentUIUserId = null\n\n if (result.success) {\n log(`UI stopped for user ${previousUser}`)\n return {\n success: true,\n data: undefined,\n executionTime: Date.now() - startTime,\n }\n }\n\n log(`Failed to stop UI: ${result.error.message}`)\n return result\n }\n\n /**\n * Get the user ID for the currently running UI\n */\n getCurrentUIUser(): string | null {\n return this.currentUIUserId\n }\n\n /**\n * Set the internal port for DuckDB UI\n */\n setUIPort(port: number): void {\n this.uiInternalPort = port\n }\n\n /**\n * Get the internal port for DuckDB UI\n */\n getUIPort(): number {\n return this.uiInternalPort\n }\n\n /**\n * Convert Either<Error, T> to MCPResult<T>\n */\n private handleEither<T>(\n result: Either<{ code: ErrorCode; message: string; cause?: Error; context?: Record<string, unknown> }, T>,\n executionTime: number,\n ): MCPResult<T> {\n return result.fold(\n (error) => ({\n success: false,\n error: {\n code: this.mapErrorCode(error.code),\n message: error.message,\n details: {\n originalCode: error.code,\n context: error.context,\n cause: error.cause?.message,\n },\n },\n }),\n (data) => ({\n success: true,\n data,\n executionTime,\n }),\n )\n }\n\n /**\n * Map DuckPond ErrorCode to MCP error code\n */\n private mapErrorCode(code: ErrorCode): string {\n const mapping: Record<ErrorCode, string> = {\n CONNECTION_FAILED: \"SERVICE_UNAVAILABLE\",\n CONNECTION_TIMEOUT: \"TIMEOUT\",\n R2_CONNECTION_ERROR: \"SERVICE_UNAVAILABLE\",\n S3_CONNECTION_ERROR: \"SERVICE_UNAVAILABLE\",\n USER_NOT_FOUND: \"NOT_FOUND\",\n USER_ALREADY_EXISTS: \"ALREADY_EXISTS\",\n USER_NOT_ATTACHED: \"NOT_FOUND\",\n QUERY_EXECUTION_ERROR: \"INVALID_REQUEST\",\n QUERY_TIMEOUT: \"TIMEOUT\",\n INVALID_SQL: \"INVALID_REQUEST\",\n MEMORY_LIMIT_EXCEEDED: \"RESOURCE_EXHAUSTED\",\n STORAGE_ERROR: \"SERVICE_UNAVAILABLE\",\n STORAGE_QUOTA_EXCEEDED: \"RESOURCE_EXHAUSTED\",\n INVALID_CONFIG: \"INVALID_ARGUMENT\",\n NOT_INITIALIZED: \"FAILED_PRECONDITION\",\n UNKNOWN_ERROR: \"INTERNAL_ERROR\",\n }\n\n return mapping[code] || \"INTERNAL_ERROR\"\n }\n\n /**\n * Helper for not initialized error\n */\n private notInitializedError<T>(): MCPResult<T> {\n return {\n success: false,\n error: {\n code: \"FAILED_PRECONDITION\",\n message: \"DuckPond not initialized. Call init() first.\",\n },\n }\n }\n}\n"],"mappings":"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","names":["DuckPond","log","loggers","DuckPondServer","config","__publicField","startTime","DuckPond","result","userId","sql","attached","installResult","portResult","startResult","previousUser","port","executionTime","error","data","code"]}
@@ -0,0 +1,48 @@
1
+ import{a as T}from"./chunk-SRB6JVEI.js";import{a as E}from"./chunk-E5JCTMWX.js";import{b as m,c as U,d as $,e as C,f as O,g as P,h as A}from"./chunk-MPMUZFRC.js";import{b as x}from"./chunk-A3S6D44B.js";import{webcrypto as z}from"crypto";import{FastMCP as D}from"@jordanburke/fastmcp";import{createHash as q,randomBytes as _}from"crypto";import{createRequire as N}from"module";import*as S from"jsonwebtoken";import{URL as W}from"url";globalThis.crypto||(globalThis.crypto=z);var J=N(import.meta.url),b=J("../package.json"),c=x.fastmcp,I=process.env.DUCKPOND_JWT_SECRET||_(32).toString("hex"),v=process.env.DUCKPOND_JWT_EXPIRES_IN?parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN,10):365*24*60*60,y=new Map,f=new Map;function F(r){c("\u{1F680} Initializing FastMCP server...");let n=new T(r.config),d={name:"duckpond",version:b.version,health:{enabled:!0,path:"/health",status:200,message:JSON.stringify({status:"healthy",service:"duckpond-mcp-server",version:b.version,timestamp:new Date().toISOString()})}},o=r.oauth?.enabled||r.basicAuth?new D({...d,oauth:{enabled:!0,authorizationServer:{issuer:r.oauth?.issuer||`http://localhost:${r.port||3e3}`,authorizationEndpoint:`${r.oauth?.issuer||`http://localhost:${r.port||3e3}`}/oauth/authorize`,tokenEndpoint:`${r.oauth?.issuer||`http://localhost:${r.port||3e3}`}/oauth/token`,jwksUri:`${r.oauth?.issuer||`http://localhost:${r.port||3e3}`}/oauth/jwks`,registrationEndpoint:`${r.oauth?.issuer||`http://localhost:${r.port||3e3}`}/oauth/register`,responseTypesSupported:["code"],grantTypesSupported:["authorization_code"],tokenEndpointAuthMethodsSupported:["client_secret_post","client_secret_basic"],codeChallengeMethodsSupported:["S256","plain"]},protectedResource:{resource:process.env.DUCKPOND_OAUTH_RESOURCE||r.oauth?.resource||`${r.oauth?.issuer||`http://localhost:${r.port||3e3}`}/mcp`,authorizationServers:[r.oauth?.issuer||`http://localhost:${r.port||3e3}`]}},authenticate:s=>{let e=s.headers?.authorization,t=r.oauth?.issuer||`http://localhost:${r.port||3e3}`;if(!e)throw r.oauth?.enabled?new Response(JSON.stringify({error:"unauthorized",error_description:"Authorization required. Please authenticate via OAuth."}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer realm="MCP", authorization_uri="${t}/oauth/authorize", resource="${t}/.well-known/oauth-protected-resource"`}}):new Response(JSON.stringify({error:"unauthorized",error_description:"Authorization required."}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json"}});if(r.basicAuth&&e.startsWith("Basic ")){let u=Buffer.from(e.slice(6),"base64").toString("utf-8"),[i,l]=u.split(":");if(i===r.basicAuth.username&&l===r.basicAuth.password)return Promise.resolve({userId:r.basicAuth.userId||i,email:r.basicAuth.email||`${i}@example.com`,scope:"read write"});throw new Response(JSON.stringify({error:"unauthorized",error_description:"Invalid username or password"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Basic realm="MCP"'}})}if(r.oauth?.enabled&&e.startsWith("Bearer ")){let u=e.slice(7);try{let i=S.verify(u,I);if(!i.sub||!i.iat||!i.exp)throw new Response(JSON.stringify({error:"invalid_token",error_description:"Invalid token structure"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Invalid token structure"'}});let l=r.oauth?.resource||`${t}/mcp`;if(i.aud&&i.aud!==l)throw new Response(JSON.stringify({error:"invalid_token",error_description:"Token audience mismatch"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Token audience mismatch"'}});return Promise.resolve({userId:i.sub,email:i.email||"",scope:i.scope||"read write"})}catch(i){throw i instanceof Response?i:new Response(JSON.stringify({error:"invalid_token",error_description:"Invalid or expired token"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Invalid or expired token"'}})}}throw new Response(JSON.stringify({error:"unauthorized",error_description:"Invalid authorization header format"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer realm="MCP", authorization_uri="${t}/oauth/authorize", resource="${t}/.well-known/oauth-protected-resource"`}})}}):new D(d);o.addTool({name:"query",description:"Execute a SQL query for a specific user and return results",parameters:U,execute:async s=>{try{let e=m(s.userId),t=await n.query(e,s.sql);return t.success?JSON.stringify({rows:t.data,rowCount:t.data.length,executionTime:t.executionTime},null,2):`ERROR: ${t.error.message}`}catch(e){c("Error in query tool:",e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"execute",description:"Execute SQL statement (DDL/DML) for a specific user without returning results",parameters:$,execute:async s=>{try{let e=m(s.userId),t=await n.execute(e,s.sql);return t.success?JSON.stringify({success:!0,message:"Statement executed successfully",executionTime:t.executionTime},null,2):`ERROR: ${t.error.message}`}catch(e){c("Error in execute tool:",e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"getUserStats",description:"Get statistics about a user's database (memory usage, query count, etc.)",parameters:C,execute:async s=>{try{let e=m(s.userId),t=await n.getUserStats(e);return t.success?JSON.stringify({...t.data,lastAccess:t.data.lastAccess.toISOString()},null,2):`ERROR: ${t.error.message}`}catch(e){c("Error in getUserStats tool:",e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"isAttached",description:"Check if a user's database is currently cached in memory",parameters:O,execute:async s=>{try{let e=m(s.userId),t=n.isAttached(e);return t.success?JSON.stringify({attached:t.data,userId:e},null,2):`ERROR: ${t.error.message}`}catch(e){c("Error in isAttached tool:",e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"detachUser",description:"Manually detach a user's database from the cache to free resources",parameters:P,execute:async s=>{try{let e=m(s.userId),t=await n.detachUser(e);return t.success?JSON.stringify({success:!0,message:`User ${e} detached successfully`},null,2):`ERROR: ${t.error.message}`}catch(e){c("Error in detachUser tool:",e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"listUsers",description:"List all currently cached users and cache statistics",parameters:A,execute:async()=>{try{let s=n.listUsers();return s.success?JSON.stringify(s.data,null,2):`ERROR: ${s.error.message}`}catch(s){c("Error in listUsers tool:",s);let e=s instanceof Error?s.message:String(s);return`ERROR: ${JSON.stringify({error:e},null,2)}`}}}),r.oauth?.enabled&&B(o,r);let a=o.getApp();return a.get("/",s=>{let e=r.oauth?.issuer||`http://localhost:${r.port||3e3}`,t={name:"DuckPond MCP Server",version:b.version,description:"Model Context Protocol server for multi-tenant DuckDB with R2/S3 storage",service:"duckpond-mcp-server",capabilities:{tools:["query","execute","getUserStats","isAttached","detachUser","listUsers"],transports:["stdio","http"],authentication:{oauth:r.oauth?.enabled||!1,basicAuth:!!r.basicAuth}},endpoints:{mcp:`${e}${r.endpoint||"/mcp"}`,health:`${e}/health`,ui:`${e}/ui/:userId`,...r.oauth?.enabled&&{oauth:{authorization:`${e}/oauth/authorize`,token:`${e}/oauth/token`,jwks:`${e}/oauth/jwks`,register:`${e}/oauth/register`}}},timestamp:new Date().toISOString()};return s.json(t)}),L(a,n,r),c("\u2713 FastMCP server created"),{server:o,duckpond:n}}function B(r,n){let d=r.getApp();setInterval(()=>{let o=Date.now();for(let[a,s]of y.entries())o-s.createdAt>6e5&&y.delete(a);for(let[a,s]of f.entries())o-s.createdAt>2592e6&&f.delete(a)},6e4),d.get("/oauth/authorize",o=>{let a=o.req.query(),s=a.response_type,e=a.redirect_uri,t=a.state,u=a.code_challenge,i=a.code_challenge_method,l=a.client_id;if(s!=="code")return o.json({error:"unsupported_response_type",error_description:"Only 'code' response type is supported"},400);if(!e)return o.json({error:"invalid_request",error_description:"redirect_uri is required"},400);if(u&&(!i||!["S256","plain"].includes(i)))return o.json({error:"invalid_request",error_description:"Invalid code_challenge_method. Only 'S256' and 'plain' are supported"},400);let h=`
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>OAuth Login - DuckPond MCP Server</title>
6
+ <style>
7
+ body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }
8
+ .form-group { margin-bottom: 15px; }
9
+ label { display: block; margin-bottom: 5px; font-weight: bold; }
10
+ input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
11
+ button { width: 100%; padding: 12px; background: #007cba; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }
12
+ button:hover { background: #005a87; }
13
+ .app-info { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 20px; }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="app-info">
18
+ <h3>\u{1F510} OAuth Authorization</h3>
19
+ <p><strong>Application:</strong> ${l||"MCP Client"}</p>
20
+ <p><strong>Permissions:</strong> Read and write access to DuckDB databases</p>
21
+ </div>
22
+
23
+ <form method="POST" action="/oauth/authorize">
24
+ <input type="hidden" name="response_type" value="${s}">
25
+ <input type="hidden" name="redirect_uri" value="${e}">
26
+ <input type="hidden" name="state" value="${t||""}">
27
+ <input type="hidden" name="code_challenge" value="${u||""}">
28
+ <input type="hidden" name="code_challenge_method" value="${i||""}">
29
+ <input type="hidden" name="client_id" value="${l||""}">
30
+
31
+ <div class="form-group">
32
+ <label for="username">Username:</label>
33
+ <input type="text" id="username" name="username" required>
34
+ </div>
35
+
36
+ <div class="form-group">
37
+ <label for="password">Password:</label>
38
+ <input type="password" id="password" name="password" required>
39
+ </div>
40
+
41
+ <button type="submit">Authorize Application</button>
42
+ </form>
43
+ </body>
44
+ </html>`;return o.html(h)}),d.post("/oauth/authorize",async o=>{try{let a=await o.req.text(),s=new URLSearchParams(a),e=s.get("username"),t=s.get("password"),u=s.get("redirect_uri"),i=s.get("state"),l=s.get("code_challenge"),h=s.get("code_challenge_method");if(e!==n.oauth?.username||t!==n.oauth?.password)return o.html(`
45
+ <!DOCTYPE html>
46
+ <html><head><title>Login Failed</title><style>body{font-family:Arial;max-width:400px;margin:100px auto;padding:20px;}.error{color:red;background:#fee;padding:10px;border-radius:4px;margin-bottom:15px;}</style></head>
47
+ <body><div class="error">\u274C Invalid username or password</div><a href="javascript:history.back()">\u2190 Try Again</a></body></html>`,401);let w=_(16).toString("hex");y.set(w,{createdAt:Date.now(),redirectUri:u||"",codeChallenge:l||void 0,codeChallengeMethod:h||void 0,userId:n.oauth?.userId||e||"oauth-user"});let g=new W(u||"");return g.searchParams.set("code",w),i&&g.searchParams.set("state",i),o.redirect(g.toString(),302)}catch{return o.json({error:"invalid_request",error_description:"Failed to process authorization request"},400)}}),d.post("/oauth/token",async o=>{let a=await o.req.text(),s=new URLSearchParams(a),e=s.get("grant_type"),t=s.get("code"),u=s.get("redirect_uri"),i=s.get("code_verifier"),l=s.get("refresh_token");if(e==="refresh_token"){if(!l)return o.json({error:"invalid_request",error_description:"refresh_token is required for refresh_token grant type"},400);let p=f.get(l);if(!p)return o.json({error:"invalid_grant",error_description:"Invalid or expired refresh token"},400);f.delete(l);let M={sub:p.userId,email:p.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+v,iss:n.oauth?.issuer||`http://localhost:${n.port||3e3}`,aud:n.oauth?.resource||`${n.oauth?.issuer||`http://localhost:${n.port||3e3}`}/mcp`},R=_(32).toString("hex");f.set(R,{createdAt:Date.now(),userId:p.userId,email:p.email});let j=S.sign(M,I);return o.json({access_token:j,token_type:"Bearer",expires_in:v,scope:"read write",refresh_token:R})}if(e!=="authorization_code")return o.json({error:"unsupported_grant_type",error_description:"Only 'authorization_code' and 'refresh_token' grant types are supported"},400);let h=y.get(t||"");if(!h)return o.json({error:"invalid_grant",error_description:"Invalid or expired authorization code"},400);if(h.redirectUri&&h.redirectUri!==u)return o.json({error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(h.codeChallenge){if(!i)return o.json({error:"invalid_grant",error_description:"code_verifier is required when code_challenge was used"},400);let p;if(h.codeChallengeMethod==="S256"?p=q("sha256").update(i).digest().toString("base64url"):p=i,p!==h.codeChallenge)return o.json({error:"invalid_grant",error_description:"Invalid code_verifier"},400)}y.delete(t);let w={sub:h.userId,email:n.oauth?.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+v,iss:n.oauth?.issuer||`http://localhost:${n.port||3e3}`,aud:n.oauth?.resource||`${n.oauth?.issuer||`http://localhost:${n.port||3e3}`}/mcp`},g=_(32).toString("hex");f.set(g,{createdAt:Date.now(),userId:h.userId,email:n.oauth?.email});let k=S.sign(w,I);return o.json({access_token:k,token_type:"Bearer",expires_in:v,scope:"read write",refresh_token:g})}),d.get("/oauth/jwks",o=>o.json({keys:[{kty:"oct",use:"sig",kid:"duckpond-hmac-key",alg:"HS256"}]})),d.post("/oauth/register",async o=>{try{let a={};try{let u=await o.req.text();if(u&&u!=="[object Object]")try{a=JSON.parse(u)}catch{a=Object.fromEntries(new URLSearchParams(u))}}catch(u){c("Error parsing request body:",u)}let s=`client-${_(8).toString("hex")}`,e=_(16).toString("hex"),t={client_id:s,client_secret:e,client_id_issued_at:Math.floor(Date.now()/1e3),client_secret_expires_at:0,grant_types:a.grant_types||["authorization_code"],response_types:a.response_types||["code"],redirect_uris:a.redirect_uris||[],token_endpoint_auth_method:a.token_endpoint_auth_method||"client_secret_post"};return a.client_name&&(t.client_name=a.client_name),a.scope&&(t.scope=a.scope),o.json(t,201)}catch(a){return o.json({error:"invalid_client_metadata",error_description:"Invalid client registration request: "+(a instanceof Error?a.message:String(a))},400)}}),c("\u2713 OAuth flow endpoints added")}function L(r,n,d){let o=d.oauth?.issuer||`http://localhost:${d.port||3e3}`,a=n.getUIPort();r.get("/ui",s=>{let e=n.getCurrentUIUser(),t=n.listUsers();return s.json({message:e?`UI active for user: ${e}. Access directly at http://localhost:${a}`:"No UI active. Visit /ui/:userId to start DuckDB UI for a user.",currentUser:e,uiUrl:e?`http://localhost:${a}`:null,availableUsers:t.success?t.data.users:[],endpoints:{startUI:`${o}/ui/:userId`}})}),r.get("/ui/:userId",async s=>{let e=s.req.param("userId");c(`Starting UI for user: ${e}`);let t=await n.startUI(e);return t.success?s.json({success:!0,message:`UI started for user: ${e}`,uiUrl:`http://localhost:${a}`,hint:"Access the DuckDB UI directly at the uiUrl above"}):s.json({error:"Failed to start UI",message:t.error.message,details:t.error.details},500)}),c("\u2713 UI endpoints added")}async function re(r,n){let{server:d,duckpond:o}=F(r),a=await o.init();if(!a.success)throw new Error(`Failed to initialize DuckPond: ${a.error.message}`);if(c("DuckPond initialized successfully"),r.ui?.internalPort&&o.setUIPort(r.ui.internalPort),n==="stdio"){if(await d.start({transportType:"stdio"}),c("\u2713 FastMCP server running with stdio transport"),r.ui?.enabled)if(r.ui.autoStartUser){c(`Auto-starting UI for user: ${r.ui.autoStartUser}`);let s=await o.startUI(r.ui.autoStartUser);s.success?console.error(`\u{1F5A5}\uFE0F DuckDB UI running at http://localhost:${o.getUIPort()}`):c(`Failed to auto-start UI: ${s.error.message}`)}else await E({port:r.ui.port,duckpond:o})}else await d.start({transportType:"httpStream",httpStream:{port:r.port||3e3,endpoint:r.endpoint||"/mcp"}}),c(`\u2713 FastMCP server running on http://0.0.0.0:${r.port||3e3}${r.endpoint||"/mcp"}`),c("\u{1F50C} Connect with StreamableHTTPClientTransport");process.on("SIGINT",async()=>{c("Received SIGINT, closing server..."),await o.close(),process.exit(0)}),process.on("SIGTERM",async()=>{c("Received SIGTERM, closing server..."),await o.close(),process.exit(0)})}export{F as a,re as b};
48
+ //# sourceMappingURL=chunk-XTQYREFT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { FastMCP } from \"@jordanburke/fastmcp\"\nimport { createHash, randomBytes } from \"crypto\"\nimport { createRequire } from \"module\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport * as jwt from \"jsonwebtoken\"\nimport { URL } from \"url\"\nimport { z } from \"zod\"\n\nimport { DuckPondServer, type DuckPondServerConfig } from \"./server-core\"\nimport {\n detachUserSchema,\n executeSchema,\n getDefaultUserId,\n getUserStatsSchema,\n isAttachedSchema,\n listUsersSchema,\n querySchema,\n resolveUserId,\n} from \"./tools\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.fastmcp\n\nexport type OAuthConfig = {\n enabled: boolean\n username: string\n password: string\n userId: string\n email?: string\n issuer?: string\n resource?: string\n}\n\nexport type FastMCPServerOptions = {\n config: DuckPondServerConfig\n port?: number\n endpoint?: string\n oauth?: OAuthConfig\n basicAuth?: {\n username: string\n password: string\n userId?: string\n email?: string\n }\n ui?: {\n enabled: boolean\n port: number\n internalPort?: number\n autoStartUser?: string\n }\n}\n\n// JWT secret for token signing/validation\nconst JWT_SECRET = process.env.DUCKPOND_JWT_SECRET || randomBytes(32).toString(\"hex\")\n\n// JWT token expiration configuration (default: 1 year)\nconst JWT_EXPIRES_IN = process.env.DUCKPOND_JWT_EXPIRES_IN\n ? parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN, 10)\n : 365 * 24 * 60 * 60 // 1 year in seconds\n\n// In-memory stores for OAuth flow\nconst authorizationCodes = new Map<\n string,\n {\n createdAt: number\n redirectUri?: string\n codeChallenge?: string\n codeChallengeMethod?: string\n userId: string\n }\n>()\n\nconst refreshTokens = new Map<\n string,\n {\n createdAt: number\n userId: string\n email?: string\n }\n>()\n\n// AuthSession type for FastMCP authentication\ntype AuthSession = {\n userId: string\n email: string\n scope: string\n [key: string]: unknown // Allow additional properties\n}\n\ntype OAuthClientRegistrationRequest = {\n grant_types?: string[]\n response_types?: string[]\n redirect_uris?: string[]\n token_endpoint_auth_method?: string\n client_name?: string\n scope?: string\n}\n\ntype OAuthClientRegistrationResponse = {\n client_id: string\n client_secret: string\n client_id_issued_at: number\n client_secret_expires_at: number\n grant_types: string[]\n response_types: string[]\n redirect_uris: string[]\n token_endpoint_auth_method: string\n client_name?: string\n scope?: string\n}\n\nexport function createFastMCPServer(options: FastMCPServerOptions): {\n server: FastMCP\n duckpond: DuckPondServer\n} {\n log(\"🚀 Initializing FastMCP server...\")\n\n // Create DuckPond server instance\n const duckpond = new DuckPondServer(options.config)\n\n // Build server configuration\n const baseConfig = {\n name: \"duckpond\",\n version: packageJson.version as `${number}.${number}.${number}`,\n health: {\n enabled: true,\n path: \"/health\",\n status: 200,\n message: JSON.stringify({\n status: \"healthy\",\n service: \"duckpond-mcp-server\",\n version: packageJson.version,\n timestamp: new Date().toISOString(),\n }),\n },\n }\n\n // Create server with authentication (OAuth, Basic Auth, or none)\n const server =\n options.oauth?.enabled || options.basicAuth\n ? new FastMCP<AuthSession>({\n ...baseConfig,\n oauth: {\n enabled: true,\n authorizationServer: {\n issuer: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n authorizationEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/authorize`,\n tokenEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/token`,\n jwksUri: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/jwks`,\n registrationEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/register`,\n responseTypesSupported: [\"code\"],\n grantTypesSupported: [\"authorization_code\"],\n tokenEndpointAuthMethodsSupported: [\"client_secret_post\", \"client_secret_basic\"],\n codeChallengeMethodsSupported: [\"S256\", \"plain\"],\n },\n protectedResource: {\n resource:\n process.env.DUCKPOND_OAUTH_RESOURCE ||\n options.oauth?.resource ||\n `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n authorizationServers: [options.oauth?.issuer || `http://localhost:${options.port || 3000}`],\n },\n },\n authenticate: (request) => {\n const authHeader = request.headers?.authorization\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n\n // For OAuth-enabled servers, require authentication\n if (!authHeader) {\n if (options.oauth?.enabled) {\n // Return HTTP 401 with WWW-Authenticate header for proper OAuth discovery\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Authorization required. Please authenticate via OAuth.\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", authorization_uri=\"${baseUrl}/oauth/authorize\", resource=\"${baseUrl}/.well-known/oauth-protected-resource\"`,\n },\n },\n )\n }\n\n // For non-OAuth servers, also require some form of auth\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Authorization required.\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n },\n )\n }\n\n // Handle Basic Authentication\n if (options.basicAuth && authHeader.startsWith(\"Basic \")) {\n const credentials = Buffer.from(authHeader.slice(6), \"base64\").toString(\"utf-8\")\n const [username, password] = credentials.split(\":\")\n\n if (username === options.basicAuth.username && password === options.basicAuth.password) {\n return Promise.resolve({\n userId: options.basicAuth.userId || username,\n email: options.basicAuth.email || `${username}@example.com`,\n scope: \"read write\",\n })\n } else {\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Invalid username or password\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Basic realm=\"MCP\"`,\n },\n },\n )\n }\n }\n\n // Handle Bearer Token (OAuth) - Validate JWT\n if (options.oauth?.enabled && authHeader.startsWith(\"Bearer \")) {\n const token = authHeader.slice(7) // Remove 'Bearer ' prefix\n\n try {\n // Verify JWT token\n const decoded = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload\n\n if (!decoded.sub || !decoded.iat || !decoded.exp) {\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Invalid token structure\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Invalid token structure\"`,\n },\n },\n )\n }\n\n // Validate audience\n const expectedAudience = options.oauth?.resource || `${baseUrl}/mcp`\n if (decoded.aud && decoded.aud !== expectedAudience) {\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Token audience mismatch\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Token audience mismatch\"`,\n },\n },\n )\n }\n\n // Return user info from JWT claims\n return Promise.resolve({\n userId: decoded.sub,\n email: (decoded.email as string) || \"\",\n scope: (decoded.scope as string) || \"read write\",\n })\n } catch (error) {\n if (error instanceof Response) {\n throw error // Re-throw our custom Response errors\n }\n\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Invalid or expired token\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Invalid or expired token\"`,\n },\n },\n )\n }\n }\n\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Invalid authorization header format\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", authorization_uri=\"${baseUrl}/oauth/authorize\", resource=\"${baseUrl}/.well-known/oauth-protected-resource\"`,\n },\n },\n )\n },\n })\n : new FastMCP(baseConfig)\n\n // Add query tool\n server.addTool({\n name: \"query\",\n description: \"Execute a SQL query for a specific user and return results\",\n parameters: querySchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.query(userId, args.sql)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n rows: result.data,\n rowCount: result.data.length,\n executionTime: result.executionTime,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in query tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add execute tool\n server.addTool({\n name: \"execute\",\n description: \"Execute SQL statement (DDL/DML) for a specific user without returning results\",\n parameters: executeSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.execute(userId, args.sql)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n success: true,\n message: \"Statement executed successfully\",\n executionTime: result.executionTime,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in execute tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add getUserStats tool\n server.addTool({\n name: \"getUserStats\",\n description: \"Get statistics about a user's database (memory usage, query count, etc.)\",\n parameters: getUserStatsSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.getUserStats(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n ...result.data,\n lastAccess: result.data.lastAccess.toISOString(),\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in getUserStats tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add isAttached tool\n server.addTool({\n name: \"isAttached\",\n description: \"Check if a user's database is currently cached in memory\",\n parameters: isAttachedSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = duckpond.isAttached(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n attached: result.data,\n userId,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in isAttached tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add detachUser tool\n server.addTool({\n name: \"detachUser\",\n description: \"Manually detach a user's database from the cache to free resources\",\n parameters: detachUserSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.detachUser(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n success: true,\n message: `User ${userId} detached successfully`,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in detachUser tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add listUsers tool\n server.addTool({\n name: \"listUsers\",\n description: \"List all currently cached users and cache statistics\",\n parameters: listUsersSchema,\n execute: async () => {\n try {\n const result = duckpond.listUsers()\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(result.data, null, 2)\n } catch (error) {\n log(\"Error in listUsers tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add OAuth flow endpoints if OAuth is enabled\n if (options.oauth?.enabled) {\n setupOAuthEndpoints(server, options)\n }\n\n // Add root info endpoint using Hono\n const app = server.getApp()\n app.get(\"/\", (c) => {\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n\n const serverInfo = {\n name: \"DuckPond MCP Server\",\n version: packageJson.version,\n description: \"Model Context Protocol server for multi-tenant DuckDB with R2/S3 storage\",\n service: \"duckpond-mcp-server\",\n capabilities: {\n tools: [\"query\", \"execute\", \"getUserStats\", \"isAttached\", \"detachUser\", \"listUsers\"],\n transports: [\"stdio\", \"http\"],\n authentication: {\n oauth: options.oauth?.enabled || false,\n basicAuth: !!options.basicAuth,\n },\n },\n endpoints: {\n mcp: `${baseUrl}${options.endpoint || \"/mcp\"}`,\n health: `${baseUrl}/health`,\n ui: `${baseUrl}/ui/:userId`,\n ...(options.oauth?.enabled && {\n oauth: {\n authorization: `${baseUrl}/oauth/authorize`,\n token: `${baseUrl}/oauth/token`,\n jwks: `${baseUrl}/oauth/jwks`,\n register: `${baseUrl}/oauth/register`,\n },\n }),\n },\n timestamp: new Date().toISOString(),\n }\n\n return c.json(serverInfo)\n })\n\n // Add UI endpoints for DuckDB UI access\n setupUIEndpoints(app, duckpond, options)\n\n log(\"✓ FastMCP server created\")\n\n return { server, duckpond }\n}\n\nfunction setupOAuthEndpoints(server: FastMCP, options: FastMCPServerOptions): void {\n const app = server.getApp()\n\n // Clean up old codes and refresh tokens every minute\n setInterval(() => {\n const now = Date.now()\n // Clean authorization codes (10 minutes)\n for (const [code, data] of authorizationCodes.entries()) {\n if (now - data.createdAt > 600000) {\n authorizationCodes.delete(code)\n }\n }\n // Clean refresh tokens (30 days)\n for (const [token, data] of refreshTokens.entries()) {\n if (now - data.createdAt > 2592000000) {\n refreshTokens.delete(token)\n }\n }\n }, 60000)\n\n // OAuth Authorization Endpoint - Login Form\n app.get(\"/oauth/authorize\", (c) => {\n const params = c.req.query()\n const responseType = params.response_type\n const redirectUri = params.redirect_uri\n const state = params.state\n const codeChallenge = params.code_challenge\n const codeChallengeMethod = params.code_challenge_method\n const clientId = params.client_id\n\n if (responseType !== \"code\") {\n return c.json(\n {\n error: \"unsupported_response_type\",\n error_description: \"Only 'code' response type is supported\",\n },\n 400,\n )\n }\n\n if (!redirectUri) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"redirect_uri is required\",\n },\n 400,\n )\n }\n\n // Validate PKCE parameters if present\n if (codeChallenge) {\n if (!codeChallengeMethod || ![\"S256\", \"plain\"].includes(codeChallengeMethod)) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"Invalid code_challenge_method. Only 'S256' and 'plain' are supported\",\n },\n 400,\n )\n }\n }\n\n // Serve login form\n const loginForm = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>OAuth Login - DuckPond MCP Server</title>\n <style>\n body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }\n .form-group { margin-bottom: 15px; }\n label { display: block; margin-bottom: 5px; font-weight: bold; }\n input[type=\"text\"], input[type=\"password\"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }\n button { width: 100%; padding: 12px; background: #007cba; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }\n button:hover { background: #005a87; }\n .app-info { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 20px; }\n </style>\n</head>\n<body>\n <div class=\"app-info\">\n <h3>🔐 OAuth Authorization</h3>\n <p><strong>Application:</strong> ${clientId || \"MCP Client\"}</p>\n <p><strong>Permissions:</strong> Read and write access to DuckDB databases</p>\n </div>\n\n <form method=\"POST\" action=\"/oauth/authorize\">\n <input type=\"hidden\" name=\"response_type\" value=\"${responseType}\">\n <input type=\"hidden\" name=\"redirect_uri\" value=\"${redirectUri}\">\n <input type=\"hidden\" name=\"state\" value=\"${state || \"\"}\">\n <input type=\"hidden\" name=\"code_challenge\" value=\"${codeChallenge || \"\"}\">\n <input type=\"hidden\" name=\"code_challenge_method\" value=\"${codeChallengeMethod || \"\"}\">\n <input type=\"hidden\" name=\"client_id\" value=\"${clientId || \"\"}\">\n\n <div class=\"form-group\">\n <label for=\"username\">Username:</label>\n <input type=\"text\" id=\"username\" name=\"username\" required>\n </div>\n\n <div class=\"form-group\">\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required>\n </div>\n\n <button type=\"submit\">Authorize Application</button>\n </form>\n</body>\n</html>`\n\n return c.html(loginForm)\n })\n\n // OAuth Authorization POST - Process Login\n app.post(\"/oauth/authorize\", async (c) => {\n try {\n const body = await c.req.text()\n const params = new URLSearchParams(body)\n\n const username = params.get(\"username\")\n const password = params.get(\"password\")\n const redirectUri = params.get(\"redirect_uri\")\n const state = params.get(\"state\")\n const codeChallenge = params.get(\"code_challenge\")\n const codeChallengeMethod = params.get(\"code_challenge_method\")\n\n // Validate credentials\n if (username !== options.oauth?.username || password !== options.oauth?.password) {\n const errorForm = `\n<!DOCTYPE html>\n<html><head><title>Login Failed</title><style>body{font-family:Arial;max-width:400px;margin:100px auto;padding:20px;}.error{color:red;background:#fee;padding:10px;border-radius:4px;margin-bottom:15px;}</style></head>\n<body><div class=\"error\">❌ Invalid username or password</div><a href=\"javascript:history.back()\">← Try Again</a></body></html>`\n return c.html(errorForm, 401)\n }\n\n // Generate authorization code\n const code = randomBytes(16).toString(\"hex\")\n authorizationCodes.set(code, {\n createdAt: Date.now(),\n redirectUri: redirectUri || \"\",\n codeChallenge: codeChallenge || undefined,\n codeChallengeMethod: codeChallengeMethod || undefined,\n userId: options.oauth?.userId || username || \"oauth-user\",\n })\n\n // Redirect with authorization code\n const redirectUrl = new URL(redirectUri || \"\")\n redirectUrl.searchParams.set(\"code\", code)\n if (state) {\n redirectUrl.searchParams.set(\"state\", state)\n }\n\n return c.redirect(redirectUrl.toString(), 302)\n } catch {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"Failed to process authorization request\",\n },\n 400,\n )\n }\n })\n\n // OAuth Token Endpoint\n app.post(\"/oauth/token\", async (c) => {\n const body = await c.req.text()\n const params = new URLSearchParams(body)\n const grantType = params.get(\"grant_type\")\n const code = params.get(\"code\")\n const redirectUri = params.get(\"redirect_uri\")\n const codeVerifier = params.get(\"code_verifier\")\n const refreshTokenParam = params.get(\"refresh_token\")\n\n if (grantType === \"refresh_token\") {\n // Handle refresh token flow\n if (!refreshTokenParam) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"refresh_token is required for refresh_token grant type\",\n },\n 400,\n )\n }\n\n const tokenData = refreshTokens.get(refreshTokenParam)\n if (!tokenData) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid or expired refresh token\",\n },\n 400,\n )\n }\n\n // Remove old refresh token (token rotation)\n refreshTokens.delete(refreshTokenParam)\n\n // Generate new JWT access token\n const accessTokenPayload = {\n sub: tokenData.userId,\n email: tokenData.email || \"\",\n scope: \"read write\",\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + JWT_EXPIRES_IN,\n iss: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n aud: options.oauth?.resource || `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n }\n\n // Generate new refresh token\n const newRefreshToken = randomBytes(32).toString(\"hex\")\n refreshTokens.set(newRefreshToken, {\n createdAt: Date.now(),\n userId: tokenData.userId,\n email: tokenData.email,\n })\n\n const accessToken = jwt.sign(accessTokenPayload, JWT_SECRET)\n\n return c.json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: JWT_EXPIRES_IN,\n scope: \"read write\",\n refresh_token: newRefreshToken,\n })\n }\n\n if (grantType !== \"authorization_code\") {\n return c.json(\n {\n error: \"unsupported_grant_type\",\n error_description: \"Only 'authorization_code' and 'refresh_token' grant types are supported\",\n },\n 400,\n )\n }\n\n const codeData = authorizationCodes.get(code || \"\")\n if (!codeData) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid or expired authorization code\",\n },\n 400,\n )\n }\n\n // Validate redirect_uri matches\n if (codeData.redirectUri && codeData.redirectUri !== redirectUri) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"redirect_uri mismatch\",\n },\n 400,\n )\n }\n\n // Validate PKCE if code_challenge was provided\n if (codeData.codeChallenge) {\n if (!codeVerifier) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"code_verifier is required when code_challenge was used\",\n },\n 400,\n )\n }\n\n let expectedChallenge: string\n if (codeData.codeChallengeMethod === \"S256\") {\n expectedChallenge = createHash(\"sha256\").update(codeVerifier).digest().toString(\"base64url\")\n } else {\n // 'plain' method\n expectedChallenge = codeVerifier\n }\n\n if (expectedChallenge !== codeData.codeChallenge) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid code_verifier\",\n },\n 400,\n )\n }\n }\n\n // Remove used code\n authorizationCodes.delete(code!)\n\n // Generate JWT access token\n const accessTokenPayload = {\n sub: codeData.userId,\n email: options.oauth?.email || \"\",\n scope: \"read write\",\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + JWT_EXPIRES_IN,\n iss: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n aud: options.oauth?.resource || `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n }\n\n // Generate refresh token\n const refreshToken = randomBytes(32).toString(\"hex\")\n refreshTokens.set(refreshToken, {\n createdAt: Date.now(),\n userId: codeData.userId,\n email: options.oauth?.email,\n })\n\n const accessToken = jwt.sign(accessTokenPayload, JWT_SECRET)\n\n return c.json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: JWT_EXPIRES_IN,\n scope: \"read write\",\n refresh_token: refreshToken,\n })\n })\n\n // JWKS Endpoint\n app.get(\"/oauth/jwks\", (c) => {\n return c.json({\n keys: [\n {\n kty: \"oct\", // Octet sequence for symmetric keys\n use: \"sig\",\n kid: \"duckpond-hmac-key\",\n alg: \"HS256\",\n },\n ],\n })\n })\n\n // Dynamic Client Registration\n app.post(\"/oauth/register\", async (c) => {\n try {\n let registrationRequest: OAuthClientRegistrationRequest = {}\n\n try {\n const body = await c.req.text()\n if (body && body !== \"[object Object]\") {\n try {\n registrationRequest = JSON.parse(body) as OAuthClientRegistrationRequest\n } catch {\n const formData = Object.fromEntries(new URLSearchParams(body))\n registrationRequest = formData as OAuthClientRegistrationRequest\n }\n }\n } catch (parseError) {\n log(\"Error parsing request body:\", parseError)\n }\n\n const clientId = `client-${randomBytes(8).toString(\"hex\")}`\n const clientSecret = randomBytes(16).toString(\"hex\")\n\n const response: OAuthClientRegistrationResponse = {\n client_id: clientId,\n client_secret: clientSecret,\n client_id_issued_at: Math.floor(Date.now() / 1000),\n client_secret_expires_at: 0, // Never expires\n grant_types: registrationRequest.grant_types || [\"authorization_code\"],\n response_types: registrationRequest.response_types || [\"code\"],\n redirect_uris: registrationRequest.redirect_uris || [],\n token_endpoint_auth_method: registrationRequest.token_endpoint_auth_method || \"client_secret_post\",\n }\n\n if (registrationRequest.client_name) {\n response.client_name = registrationRequest.client_name\n }\n if (registrationRequest.scope) {\n response.scope = registrationRequest.scope\n }\n\n return c.json(response, 201)\n } catch (error) {\n return c.json(\n {\n error: \"invalid_client_metadata\",\n error_description:\n \"Invalid client registration request: \" + (error instanceof Error ? error.message : String(error)),\n },\n 400,\n )\n }\n })\n\n log(\"✓ OAuth flow endpoints added\")\n}\n\nfunction setupUIEndpoints(\n app: ReturnType<FastMCP[\"getApp\"]>,\n duckpond: DuckPondServer,\n options: FastMCPServerOptions,\n): void {\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n const uiInternalPort = duckpond.getUIPort()\n\n // GET /ui - Info endpoint\n app.get(\"/ui\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n const listResult = duckpond.listUsers()\n return c.json({\n message: currentUser\n ? `UI active for user: ${currentUser}. Access directly at http://localhost:${uiInternalPort}`\n : \"No UI active. Visit /ui/:userId to start DuckDB UI for a user.\",\n currentUser,\n uiUrl: currentUser ? `http://localhost:${uiInternalPort}` : null,\n availableUsers: listResult.success ? listResult.data.users : [],\n endpoints: {\n startUI: `${baseUrl}/ui/:userId`,\n },\n })\n })\n\n // GET /ui/:userId - Start UI for a specific user\n app.get(\"/ui/:userId\", async (c) => {\n const userId = c.req.param(\"userId\")\n\n log(`Starting UI for user: ${userId}`)\n const result = await duckpond.startUI(userId)\n\n if (!result.success) {\n return c.json(\n {\n error: \"Failed to start UI\",\n message: result.error.message,\n details: result.error.details,\n },\n 500,\n )\n }\n\n return c.json({\n success: true,\n message: `UI started for user: ${userId}`,\n uiUrl: `http://localhost:${uiInternalPort}`,\n hint: \"Access the DuckDB UI directly at the uiUrl above\",\n })\n })\n\n log(\"✓ UI endpoints added\")\n}\n\nexport async function startServer(options: FastMCPServerOptions, transport: \"stdio\" | \"http\"): Promise<void> {\n const { server, duckpond } = createFastMCPServer(options)\n\n // Initialize DuckPond\n const initResult = await duckpond.init()\n if (!initResult.success) {\n throw new Error(`Failed to initialize DuckPond: ${initResult.error.message}`)\n }\n\n log(\"DuckPond initialized successfully\")\n\n // Set UI internal port if configured\n if (options.ui?.internalPort) {\n duckpond.setUIPort(options.ui.internalPort)\n }\n\n // Start the server with appropriate transport\n if (transport === \"stdio\") {\n await server.start({\n transportType: \"stdio\",\n })\n log(\"✓ FastMCP server running with stdio transport\")\n\n // Start UI if enabled in stdio mode\n if (options.ui?.enabled) {\n if (options.ui.autoStartUser) {\n // Auto-start UI directly for default user (no management server needed)\n log(`Auto-starting UI for user: ${options.ui.autoStartUser}`)\n const uiResult = await duckpond.startUI(options.ui.autoStartUser)\n if (uiResult.success) {\n console.error(`🖥️ DuckDB UI running at http://localhost:${duckpond.getUIPort()}`)\n } else {\n log(`Failed to auto-start UI: ${uiResult.error.message}`)\n }\n } else {\n // No default user - start management server for manual user selection\n await startUIServer({\n port: options.ui.port,\n duckpond,\n })\n }\n }\n } else {\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port: options.port || 3000,\n endpoint: (options.endpoint || \"/mcp\") as `/${string}`,\n },\n })\n log(`✓ FastMCP server running on http://0.0.0.0:${options.port || 3000}${options.endpoint || \"/mcp\"}`)\n log(\"🔌 Connect with StreamableHTTPClientTransport\")\n }\n\n // Handle cleanup on exit\n process.on(\"SIGINT\", async () => {\n log(\"Received SIGINT, closing server...\")\n await duckpond.close()\n process.exit(0)\n })\n\n process.on(\"SIGTERM\", async () => {\n log(\"Received SIGTERM, closing server...\")\n await duckpond.close()\n process.exit(0)\n })\n}\n"],"mappings":"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","names":["webcrypto","FastMCP","createHash","randomBytes","createRequire","jwt","URL","webcrypto","require","createRequire","packageJson","log","loggers","JWT_SECRET","randomBytes","JWT_EXPIRES_IN","authorizationCodes","refreshTokens","createFastMCPServer","options","duckpond","DuckPondServer","baseConfig","server","FastMCP","request","authHeader","baseUrl","credentials","username","password","token","decoded","expectedAudience","error","querySchema","args","userId","resolveUserId","result","errorMessage","executeSchema","getUserStatsSchema","isAttachedSchema","detachUserSchema","listUsersSchema","setupOAuthEndpoints","app","c","serverInfo","setupUIEndpoints","now","code","data","params","responseType","redirectUri","state","codeChallenge","codeChallengeMethod","clientId","loginForm","body","redirectUrl","URL","grantType","codeVerifier","refreshTokenParam","tokenData","accessTokenPayload","newRefreshToken","accessToken","codeData","expectedChallenge","createHash","refreshToken","registrationRequest","parseError","clientSecret","response","uiInternalPort","currentUser","listResult","startServer","transport","initResult","uiResult","startUIServer"]}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{b as U}from"./chunk-JXCFUXVK.js";import"./chunk-R65UMTEX.js";import"./chunk-VSQCOL7H.js";import{a as _}from"./chunk-MPMUZFRC.js";import{b as D}from"./chunk-A3S6D44B.js";import{webcrypto as m}from"crypto";import{Command as O}from"commander";import{createRequire as d}from"module";globalThis.crypto||(globalThis.crypto=m);var v=d(import.meta.url),A=v("../package.json"),t=D.main;function P(){let e={memoryLimit:process.env.DUCKPOND_MEMORY_LIMIT||"4GB",threads:parseInt(process.env.DUCKPOND_THREADS||"4"),maxActiveUsers:parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS||"10"),evictionTimeout:parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT||"300000"),cacheType:process.env.DUCKPOND_CACHE_TYPE||"disk",strategy:process.env.DUCKPOND_STRATEGY||"parquet",tempDir:process.env.DUCKPOND_TEMP_DIR,cacheDir:process.env.DUCKPOND_CACHE_DIR};return process.env.DUCKPOND_R2_ACCOUNT_ID&&(e.r2={accountId:process.env.DUCKPOND_R2_ACCOUNT_ID,accessKeyId:process.env.DUCKPOND_R2_ACCESS_KEY_ID||"",secretAccessKey:process.env.DUCKPOND_R2_SECRET_ACCESS_KEY||"",bucket:process.env.DUCKPOND_R2_BUCKET||""}),process.env.DUCKPOND_S3_REGION&&(e.s3={region:process.env.DUCKPOND_S3_REGION,accessKeyId:process.env.DUCKPOND_S3_ACCESS_KEY_ID||"",secretAccessKey:process.env.DUCKPOND_S3_SECRET_ACCESS_KEY||"",bucket:process.env.DUCKPOND_S3_BUCKET||""},process.env.DUCKPOND_S3_ENDPOINT&&(e.s3.endpoint=process.env.DUCKPOND_S3_ENDPOINT)),e}var C=new O;C.name("duckpond-mcp-server").description("MCP server for multi-tenant DuckDB management with R2/S3 storage").version(A.version).option("-t, --transport <type>","Transport mode: stdio or http","stdio").option("-p, --port <port>","HTTP port (when using http transport)","3000").option("--ui","Enable DuckDB UI server (for stdio mode)").option("--ui-port <port>","UI server port (default: 4000)","4000").option("--ui-internal-port <port>","Internal DuckDB UI port (default: 4213)","4213").action(async e=>{try{let r=P(),n=_();t(`Starting DuckPond MCP Server with ${e.transport} transport`),t("Configuration:",{memoryLimit:r.memoryLimit,threads:r.threads,maxActiveUsers:r.maxActiveUsers,strategy:r.strategy,tempDir:r.tempDir,cacheDir:r.cacheDir,cacheType:r.cacheType,hasR2:!!r.r2,hasS3:!!r.s3,defaultUser:n||"(not set)"}),n&&console.error(`\u{1F464} Default user: ${n}`);let o;if(process.env.DUCKPOND_OAUTH_ENABLED==="true"){let a=process.env.DUCKPOND_OAUTH_USERNAME,p=process.env.DUCKPOND_OAUTH_PASSWORD;(!a||!p)&&(console.error("\u274C OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required"),process.exit(1)),o={enabled:!0,username:a,password:p,userId:process.env.DUCKPOND_OAUTH_USER_ID||a,email:process.env.DUCKPOND_OAUTH_EMAIL,issuer:process.env.DUCKPOND_OAUTH_ISSUER||`http://localhost:${parseInt(e.port)||3e3}`,resource:process.env.DUCKPOND_OAUTH_RESOURCE},console.error("\u{1F510} OAuth enabled with username/password authentication"),console.error(` Username: ${o.username}`),console.error(` User ID: ${o.userId}`),console.error(" \u2713 Login form will be shown at authorization endpoint")}let s;process.env.DUCKPOND_BASIC_AUTH_USERNAME&&process.env.DUCKPOND_BASIC_AUTH_PASSWORD&&(s={username:process.env.DUCKPOND_BASIC_AUTH_USERNAME,password:process.env.DUCKPOND_BASIC_AUTH_PASSWORD,userId:process.env.DUCKPOND_BASIC_AUTH_USER_ID,email:process.env.DUCKPOND_BASIC_AUTH_EMAIL},console.error("\u{1F510} Basic authentication enabled"),console.error(` Username: ${s.username}`),console.error(` User ID: ${s.userId||s.username}`));let c=e.ui||process.env.DUCKPOND_UI_ENABLED==="true",i=parseInt(e.uiPort)||4e3,u=parseInt(e.uiInternalPort)||4213;c&&e.transport==="stdio"&&console.error(`\u{1F5A5}\uFE0F UI server will be available at http://localhost:${i}/ui`),e.transport==="stdio"||e.transport==="http"?await U({config:r,port:parseInt(e.port)||3e3,endpoint:"/mcp",oauth:o,basicAuth:s,ui:c?{enabled:!0,port:i,internalPort:u}:void 0},e.transport==="stdio"?"stdio":"http"):(t(`Unknown transport: ${e.transport}`),process.exit(1))}catch(r){t("Fatal error:",r),console.error("Fatal error:",r),process.exit(1)}});C.parse();
2
+ import{b as U}from"./chunk-XTQYREFT.js";import"./chunk-SRB6JVEI.js";import"./chunk-E5JCTMWX.js";import{a as _}from"./chunk-MPMUZFRC.js";import{b as u}from"./chunk-A3S6D44B.js";import{webcrypto as d}from"crypto";import{Command as m}from"commander";import{createRequire as l}from"module";globalThis.crypto||(globalThis.crypto=d);var O=l(import.meta.url),S=O("../package.json"),n=u.main;function v(r){return r.startsWith("~/")?`${process.env.HOME||process.env.USERPROFILE||"."}${r.slice(1)}`:r}function A(){return`${process.env.HOME||process.env.USERPROFILE||"."}/.duckpond/data`}function P(){let r=v(process.env.DUCKPOND_DATA_DIR||A()),e={memoryLimit:process.env.DUCKPOND_MEMORY_LIMIT||"4GB",threads:parseInt(process.env.DUCKPOND_THREADS||"4"),maxActiveUsers:parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS||"10"),evictionTimeout:parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT||"300000"),cacheType:process.env.DUCKPOND_CACHE_TYPE||"disk",strategy:process.env.DUCKPOND_STRATEGY||"duckdb",tempDir:process.env.DUCKPOND_TEMP_DIR,cacheDir:process.env.DUCKPOND_CACHE_DIR||r,dataDir:r};return process.env.DUCKPOND_R2_ACCOUNT_ID&&(e.r2={accountId:process.env.DUCKPOND_R2_ACCOUNT_ID,accessKeyId:process.env.DUCKPOND_R2_ACCESS_KEY_ID||"",secretAccessKey:process.env.DUCKPOND_R2_SECRET_ACCESS_KEY||"",bucket:process.env.DUCKPOND_R2_BUCKET||""}),process.env.DUCKPOND_S3_REGION&&(e.s3={region:process.env.DUCKPOND_S3_REGION,accessKeyId:process.env.DUCKPOND_S3_ACCESS_KEY_ID||"",secretAccessKey:process.env.DUCKPOND_S3_SECRET_ACCESS_KEY||"",bucket:process.env.DUCKPOND_S3_BUCKET||""},process.env.DUCKPOND_S3_ENDPOINT&&(e.s3.endpoint=process.env.DUCKPOND_S3_ENDPOINT)),e}var C=new m;C.name("duckpond-mcp-server").description("MCP server for multi-tenant DuckDB management with R2/S3 storage").version(S.version).option("-t, --transport <type>","Transport mode: stdio or http","stdio").option("-p, --port <port>","HTTP port (when using http transport)","3000").option("--ui","Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)").option("--ui-port <port>","UI management server port, only used when no default user (default: 4000)","4000").option("--ui-internal-port <port>","DuckDB UI port (default: 4213)","4213").action(async r=>{try{let e=P(),s=_();n(`Starting DuckPond MCP Server with ${r.transport} transport`),n("Configuration:",{memoryLimit:e.memoryLimit,threads:e.threads,maxActiveUsers:e.maxActiveUsers,strategy:e.strategy,dataDir:e.dataDir,tempDir:e.tempDir,cacheDir:e.cacheDir,cacheType:e.cacheType,hasR2:!!e.r2,hasS3:!!e.s3,defaultUser:s||"(not set)"}),e.r2?console.error("\u2601\uFE0F Storage: Cloudflare R2"):e.s3?console.error("\u2601\uFE0F Storage: AWS S3"):console.error(`\u{1F4BE} Storage: Local disk (${e.dataDir})`),s&&console.error(`\u{1F464} Default user: ${s}`);let t;if(process.env.DUCKPOND_OAUTH_ENABLED==="true"){let a=process.env.DUCKPOND_OAUTH_USERNAME,p=process.env.DUCKPOND_OAUTH_PASSWORD;(!a||!p)&&(console.error("\u274C OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required"),process.exit(1)),t={enabled:!0,username:a,password:p,userId:process.env.DUCKPOND_OAUTH_USER_ID||a,email:process.env.DUCKPOND_OAUTH_EMAIL,issuer:process.env.DUCKPOND_OAUTH_ISSUER||`http://localhost:${parseInt(r.port)||3e3}`,resource:process.env.DUCKPOND_OAUTH_RESOURCE},console.error("\u{1F510} OAuth enabled with username/password authentication"),console.error(` Username: ${t.username}`),console.error(` User ID: ${t.userId}`),console.error(" \u2713 Login form will be shown at authorization endpoint")}let o;process.env.DUCKPOND_BASIC_AUTH_USERNAME&&process.env.DUCKPOND_BASIC_AUTH_PASSWORD&&(o={username:process.env.DUCKPOND_BASIC_AUTH_USERNAME,password:process.env.DUCKPOND_BASIC_AUTH_PASSWORD,userId:process.env.DUCKPOND_BASIC_AUTH_USER_ID,email:process.env.DUCKPOND_BASIC_AUTH_EMAIL},console.error("\u{1F510} Basic authentication enabled"),console.error(` Username: ${o.username}`),console.error(` User ID: ${o.userId||o.username}`));let c=r.ui||process.env.DUCKPOND_UI_ENABLED==="true",i=parseInt(r.uiPort)||4e3,D=parseInt(r.uiInternalPort)||4213;c&&r.transport==="stdio"&&(s?console.error(`\u{1F5A5}\uFE0F DuckDB UI will start at http://localhost:${D}`):(console.error(`\u{1F5A5}\uFE0F UI management server at http://localhost:${i}/ui`),console.error(" Visit /ui/:userId to start DuckDB UI for a user"))),r.transport==="stdio"||r.transport==="http"?await U({config:e,port:parseInt(r.port)||3e3,endpoint:"/mcp",oauth:t,basicAuth:o,ui:c?{enabled:!0,port:i,internalPort:D,autoStartUser:s}:void 0},r.transport==="stdio"?"stdio":"http"):(n(`Unknown transport: ${r.transport}`),process.exit(1))}catch(e){n("Fatal error:",e),console.error("Fatal error:",e),process.exit(1)}});C.parse();
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { Command } from \"commander\"\nimport { createRequire } from \"module\"\n\nimport type { OAuthConfig } from \"./server\"\nimport { getDefaultUserId } from \"./tools\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport { startServer } from \"./server\"\nimport type { DuckPondServerConfig } from \"./server-core\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * Parse environment variables into DuckPond configuration\n */\nfunction getConfigFromEnv(): DuckPondServerConfig {\n const config: DuckPondServerConfig = {\n memoryLimit: process.env.DUCKPOND_MEMORY_LIMIT || \"4GB\",\n threads: parseInt(process.env.DUCKPOND_THREADS || \"4\"),\n maxActiveUsers: parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS || \"10\"),\n evictionTimeout: parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT || \"300000\"),\n cacheType: (process.env.DUCKPOND_CACHE_TYPE as \"disk\" | \"memory\" | \"noop\") || \"disk\",\n strategy: (process.env.DUCKPOND_STRATEGY as \"parquet\" | \"duckdb\" | \"hybrid\") || \"parquet\",\n tempDir: process.env.DUCKPOND_TEMP_DIR,\n cacheDir: process.env.DUCKPOND_CACHE_DIR,\n }\n\n // R2 configuration\n if (process.env.DUCKPOND_R2_ACCOUNT_ID) {\n config.r2 = {\n accountId: process.env.DUCKPOND_R2_ACCOUNT_ID,\n accessKeyId: process.env.DUCKPOND_R2_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_R2_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_R2_BUCKET || \"\",\n }\n }\n\n // S3 configuration\n if (process.env.DUCKPOND_S3_REGION) {\n config.s3 = {\n region: process.env.DUCKPOND_S3_REGION,\n accessKeyId: process.env.DUCKPOND_S3_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_S3_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_S3_BUCKET || \"\",\n }\n\n if (process.env.DUCKPOND_S3_ENDPOINT) {\n config.s3.endpoint = process.env.DUCKPOND_S3_ENDPOINT\n }\n }\n\n return config\n}\n\n/**\n * Main CLI program\n */\nconst program = new Command()\n\nprogram\n .name(\"duckpond-mcp-server\")\n .description(\"MCP server for multi-tenant DuckDB management with R2/S3 storage\")\n .version(packageJson.version)\n .option(\"-t, --transport <type>\", \"Transport mode: stdio or http\", \"stdio\")\n .option(\"-p, --port <port>\", \"HTTP port (when using http transport)\", \"3000\")\n .option(\"--ui\", \"Enable DuckDB UI server (for stdio mode)\")\n .option(\"--ui-port <port>\", \"UI server port (default: 4000)\", \"4000\")\n .option(\"--ui-internal-port <port>\", \"Internal DuckDB UI port (default: 4213)\", \"4213\")\n .action(async (options) => {\n try {\n const config = getConfigFromEnv()\n\n const defaultUser = getDefaultUserId()\n log(`Starting DuckPond MCP Server with ${options.transport} transport`)\n log(\"Configuration:\", {\n memoryLimit: config.memoryLimit,\n threads: config.threads,\n maxActiveUsers: config.maxActiveUsers,\n strategy: config.strategy,\n tempDir: config.tempDir,\n cacheDir: config.cacheDir,\n cacheType: config.cacheType,\n hasR2: !!config.r2,\n hasS3: !!config.s3,\n defaultUser: defaultUser || \"(not set)\",\n })\n\n if (defaultUser) {\n console.error(`👤 Default user: ${defaultUser}`)\n }\n\n // Load OAuth configuration from environment variables (for HTTP transport)\n let oauthConfig: OAuthConfig | undefined\n if (process.env.DUCKPOND_OAUTH_ENABLED === \"true\") {\n const username = process.env.DUCKPOND_OAUTH_USERNAME\n const password = process.env.DUCKPOND_OAUTH_PASSWORD\n\n if (!username || !password) {\n console.error(\"❌ OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required\")\n process.exit(1)\n }\n\n oauthConfig = {\n enabled: true,\n username,\n password,\n userId: process.env.DUCKPOND_OAUTH_USER_ID || username,\n email: process.env.DUCKPOND_OAUTH_EMAIL,\n issuer: process.env.DUCKPOND_OAUTH_ISSUER || `http://localhost:${parseInt(options.port) || 3000}`,\n resource: process.env.DUCKPOND_OAUTH_RESOURCE,\n }\n\n console.error(\"🔐 OAuth enabled with username/password authentication\")\n console.error(` Username: ${oauthConfig.username}`)\n console.error(` User ID: ${oauthConfig.userId}`)\n console.error(\" ✓ Login form will be shown at authorization endpoint\")\n }\n\n // Load Basic Auth configuration from environment variables (for HTTP transport)\n let basicAuthConfig: { username: string; password: string; userId?: string; email?: string } | undefined\n if (process.env.DUCKPOND_BASIC_AUTH_USERNAME && process.env.DUCKPOND_BASIC_AUTH_PASSWORD) {\n basicAuthConfig = {\n username: process.env.DUCKPOND_BASIC_AUTH_USERNAME,\n password: process.env.DUCKPOND_BASIC_AUTH_PASSWORD,\n userId: process.env.DUCKPOND_BASIC_AUTH_USER_ID,\n email: process.env.DUCKPOND_BASIC_AUTH_EMAIL,\n }\n\n console.error(\"🔐 Basic authentication enabled\")\n console.error(` Username: ${basicAuthConfig.username}`)\n console.error(` User ID: ${basicAuthConfig.userId || basicAuthConfig.username}`)\n }\n\n // Parse UI options\n const uiEnabled = options.ui || process.env.DUCKPOND_UI_ENABLED === \"true\"\n const uiPort = parseInt(options.uiPort) || 4000\n const uiInternalPort = parseInt(options.uiInternalPort) || 4213\n\n if (uiEnabled && options.transport === \"stdio\") {\n console.error(`🖥️ UI server will be available at http://localhost:${uiPort}/ui`)\n }\n\n // Start unified FastMCP server with appropriate transport\n if (options.transport === \"stdio\" || options.transport === \"http\") {\n await startServer(\n {\n config,\n port: parseInt(options.port) || 3000,\n endpoint: \"/mcp\",\n oauth: oauthConfig,\n basicAuth: basicAuthConfig,\n ui: uiEnabled\n ? {\n enabled: true,\n port: uiPort,\n internalPort: uiInternalPort,\n }\n : undefined,\n },\n options.transport === \"stdio\" ? \"stdio\" : \"http\",\n )\n } else {\n log(`Unknown transport: ${options.transport}`)\n process.exit(1)\n }\n } catch (error) {\n log(\"Fatal error:\", error)\n console.error(\"Fatal error:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":"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","names":["webcrypto","Command","createRequire","webcrypto","require","createRequire","packageJson","log","loggers","getConfigFromEnv","config","program","Command","options","defaultUser","getDefaultUserId","oauthConfig","username","password","basicAuthConfig","uiEnabled","uiPort","uiInternalPort","startServer","error"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { Command } from \"commander\"\nimport { createRequire } from \"module\"\n\nimport type { OAuthConfig } from \"./server\"\nimport { getDefaultUserId } from \"./tools\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport { startServer } from \"./server\"\nimport type { DuckPondServerConfig } from \"./server-core\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * Expand ~ to home directory in paths\n */\nfunction expandTilde(path: string): string {\n if (path.startsWith(\"~/\")) {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}${path.slice(1)}`\n }\n return path\n}\n\n/**\n * Get the default data directory for persistent storage\n */\nfunction getDefaultDataDir(): string {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}/.duckpond/data`\n}\n\n/**\n * Parse environment variables into DuckPond configuration\n */\nfunction getConfigFromEnv(): DuckPondServerConfig {\n // Default to local disk storage (expand ~ if present)\n const dataDir = expandTilde(process.env.DUCKPOND_DATA_DIR || getDefaultDataDir())\n\n const config: DuckPondServerConfig = {\n memoryLimit: process.env.DUCKPOND_MEMORY_LIMIT || \"4GB\",\n threads: parseInt(process.env.DUCKPOND_THREADS || \"4\"),\n maxActiveUsers: parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS || \"10\"),\n evictionTimeout: parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT || \"300000\"),\n cacheType: (process.env.DUCKPOND_CACHE_TYPE as \"disk\" | \"memory\" | \"noop\") || \"disk\",\n strategy: (process.env.DUCKPOND_STRATEGY as \"parquet\" | \"duckdb\" | \"hybrid\") || \"duckdb\",\n tempDir: process.env.DUCKPOND_TEMP_DIR,\n cacheDir: process.env.DUCKPOND_CACHE_DIR || dataDir,\n dataDir,\n }\n\n // R2 configuration\n if (process.env.DUCKPOND_R2_ACCOUNT_ID) {\n config.r2 = {\n accountId: process.env.DUCKPOND_R2_ACCOUNT_ID,\n accessKeyId: process.env.DUCKPOND_R2_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_R2_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_R2_BUCKET || \"\",\n }\n }\n\n // S3 configuration\n if (process.env.DUCKPOND_S3_REGION) {\n config.s3 = {\n region: process.env.DUCKPOND_S3_REGION,\n accessKeyId: process.env.DUCKPOND_S3_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_S3_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_S3_BUCKET || \"\",\n }\n\n if (process.env.DUCKPOND_S3_ENDPOINT) {\n config.s3.endpoint = process.env.DUCKPOND_S3_ENDPOINT\n }\n }\n\n return config\n}\n\n/**\n * Main CLI program\n */\nconst program = new Command()\n\nprogram\n .name(\"duckpond-mcp-server\")\n .description(\"MCP server for multi-tenant DuckDB management with R2/S3 storage\")\n .version(packageJson.version)\n .option(\"-t, --transport <type>\", \"Transport mode: stdio or http\", \"stdio\")\n .option(\"-p, --port <port>\", \"HTTP port (when using http transport)\", \"3000\")\n .option(\"--ui\", \"Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)\")\n .option(\"--ui-port <port>\", \"UI management server port, only used when no default user (default: 4000)\", \"4000\")\n .option(\"--ui-internal-port <port>\", \"DuckDB UI port (default: 4213)\", \"4213\")\n .action(async (options) => {\n try {\n const config = getConfigFromEnv()\n\n const defaultUser = getDefaultUserId()\n log(`Starting DuckPond MCP Server with ${options.transport} transport`)\n log(\"Configuration:\", {\n memoryLimit: config.memoryLimit,\n threads: config.threads,\n maxActiveUsers: config.maxActiveUsers,\n strategy: config.strategy,\n dataDir: config.dataDir,\n tempDir: config.tempDir,\n cacheDir: config.cacheDir,\n cacheType: config.cacheType,\n hasR2: !!config.r2,\n hasS3: !!config.s3,\n defaultUser: defaultUser || \"(not set)\",\n })\n\n // Log storage mode\n if (config.r2) {\n console.error(\"☁️ Storage: Cloudflare R2\")\n } else if (config.s3) {\n console.error(\"☁️ Storage: AWS S3\")\n } else {\n console.error(`💾 Storage: Local disk (${config.dataDir})`)\n }\n\n if (defaultUser) {\n console.error(`👤 Default user: ${defaultUser}`)\n }\n\n // Load OAuth configuration from environment variables (for HTTP transport)\n let oauthConfig: OAuthConfig | undefined\n if (process.env.DUCKPOND_OAUTH_ENABLED === \"true\") {\n const username = process.env.DUCKPOND_OAUTH_USERNAME\n const password = process.env.DUCKPOND_OAUTH_PASSWORD\n\n if (!username || !password) {\n console.error(\"❌ OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required\")\n process.exit(1)\n }\n\n oauthConfig = {\n enabled: true,\n username,\n password,\n userId: process.env.DUCKPOND_OAUTH_USER_ID || username,\n email: process.env.DUCKPOND_OAUTH_EMAIL,\n issuer: process.env.DUCKPOND_OAUTH_ISSUER || `http://localhost:${parseInt(options.port) || 3000}`,\n resource: process.env.DUCKPOND_OAUTH_RESOURCE,\n }\n\n console.error(\"🔐 OAuth enabled with username/password authentication\")\n console.error(` Username: ${oauthConfig.username}`)\n console.error(` User ID: ${oauthConfig.userId}`)\n console.error(\" ✓ Login form will be shown at authorization endpoint\")\n }\n\n // Load Basic Auth configuration from environment variables (for HTTP transport)\n let basicAuthConfig: { username: string; password: string; userId?: string; email?: string } | undefined\n if (process.env.DUCKPOND_BASIC_AUTH_USERNAME && process.env.DUCKPOND_BASIC_AUTH_PASSWORD) {\n basicAuthConfig = {\n username: process.env.DUCKPOND_BASIC_AUTH_USERNAME,\n password: process.env.DUCKPOND_BASIC_AUTH_PASSWORD,\n userId: process.env.DUCKPOND_BASIC_AUTH_USER_ID,\n email: process.env.DUCKPOND_BASIC_AUTH_EMAIL,\n }\n\n console.error(\"🔐 Basic authentication enabled\")\n console.error(` Username: ${basicAuthConfig.username}`)\n console.error(` User ID: ${basicAuthConfig.userId || basicAuthConfig.username}`)\n }\n\n // Parse UI options\n const uiEnabled = options.ui || process.env.DUCKPOND_UI_ENABLED === \"true\"\n const uiPort = parseInt(options.uiPort) || 4000\n const uiInternalPort = parseInt(options.uiInternalPort) || 4213\n\n if (uiEnabled && options.transport === \"stdio\") {\n if (defaultUser) {\n // Will auto-start UI for default user - show where to access it\n console.error(`🖥️ DuckDB UI will start at http://localhost:${uiInternalPort}`)\n } else {\n // No default user - management server needed\n console.error(`🖥️ UI management server at http://localhost:${uiPort}/ui`)\n console.error(` Visit /ui/:userId to start DuckDB UI for a user`)\n }\n }\n\n // Start unified FastMCP server with appropriate transport\n if (options.transport === \"stdio\" || options.transport === \"http\") {\n await startServer(\n {\n config,\n port: parseInt(options.port) || 3000,\n endpoint: \"/mcp\",\n oauth: oauthConfig,\n basicAuth: basicAuthConfig,\n ui: uiEnabled\n ? {\n enabled: true,\n port: uiPort,\n internalPort: uiInternalPort,\n autoStartUser: defaultUser,\n }\n : undefined,\n },\n options.transport === \"stdio\" ? \"stdio\" : \"http\",\n )\n } else {\n log(`Unknown transport: ${options.transport}`)\n process.exit(1)\n }\n } catch (error) {\n log(\"Fatal error:\", error)\n console.error(\"Fatal error:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":"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","names":["webcrypto","Command","createRequire","webcrypto","require","createRequire","packageJson","log","loggers","expandTilde","path","getDefaultDataDir","getConfigFromEnv","dataDir","config","program","Command","options","defaultUser","getDefaultUserId","oauthConfig","username","password","basicAuthConfig","uiEnabled","uiPort","uiInternalPort","startServer","error"]}
@@ -21,9 +21,12 @@ type MCPResult<T> = {
21
21
  };
22
22
  /**
23
23
  * Configuration for DuckPond MCP Server
24
- * Currently identical to DuckPondConfig, but can be extended with server-specific options
24
+ * Extends DuckPondConfig with server-specific options
25
25
  */
26
- type DuckPondServerConfig = DuckPondConfig;
26
+ type DuckPondServerConfig = DuckPondConfig & {
27
+ /** Directory for persistent database storage (default: ~/.duckpond/data) */
28
+ dataDir?: string;
29
+ };
27
30
  /**
28
31
  * Core DuckPond MCP Server
29
32
  *
@@ -35,6 +38,8 @@ declare class DuckPondServer {
35
38
  private initialized;
36
39
  private currentUIUserId;
37
40
  private uiInternalPort;
41
+ private uiKeepaliveTimer;
42
+ private readonly UI_KEEPALIVE_INTERVAL;
38
43
  constructor(config: DuckPondServerConfig);
39
44
  /**
40
45
  * Initialize the DuckPond instance
@@ -80,6 +85,14 @@ declare class DuckPondServer {
80
85
  startUI(userId: string): Promise<MCPResult<{
81
86
  port: number;
82
87
  }>>;
88
+ /**
89
+ * Start keepalive timer to prevent UI user from being evicted
90
+ */
91
+ private startUIKeepalive;
92
+ /**
93
+ * Stop the keepalive timer
94
+ */
95
+ private stopUIKeepalive;
83
96
  /**
84
97
  * Stop the currently running DuckDB UI
85
98
  */
@@ -1,2 +1,2 @@
1
- import{a}from"./chunk-R65UMTEX.js";import"./chunk-A3S6D44B.js";export{a as DuckPondServer};
1
+ import{a}from"./chunk-SRB6JVEI.js";import"./chunk-A3S6D44B.js";export{a as DuckPondServer};
2
2
  //# sourceMappingURL=server-core.js.map
package/dist/server.d.ts CHANGED
@@ -26,6 +26,7 @@ type FastMCPServerOptions = {
26
26
  enabled: boolean;
27
27
  port: number;
28
28
  internalPort?: number;
29
+ autoStartUser?: string;
29
30
  };
30
31
  };
31
32
  declare function createFastMCPServer(options: FastMCPServerOptions): {
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import{a,b}from"./chunk-JXCFUXVK.js";import"./chunk-R65UMTEX.js";import"./chunk-VSQCOL7H.js";import"./chunk-MPMUZFRC.js";import"./chunk-A3S6D44B.js";export{a as createFastMCPServer,b as startServer};
1
+ import{a,b}from"./chunk-XTQYREFT.js";import"./chunk-SRB6JVEI.js";import"./chunk-E5JCTMWX.js";import"./chunk-MPMUZFRC.js";import"./chunk-A3S6D44B.js";export{a as createFastMCPServer,b as startServer};
2
2
  //# sourceMappingURL=server.js.map
@@ -7,7 +7,7 @@ type UIServerOptions = {
7
7
  };
8
8
  /**
9
9
  * Start a lightweight HTTP server for UI access in stdio mode
10
- * This allows accessing DuckDB UI even when MCP is running over stdio
10
+ * This allows starting DuckDB UI even when MCP is running over stdio
11
11
  */
12
12
  declare function startUIServer(options: UIServerOptions): Promise<void>;
13
13
 
package/dist/ui-server.js CHANGED
@@ -1,2 +1,2 @@
1
- import{a}from"./chunk-VSQCOL7H.js";import"./chunk-A3S6D44B.js";export{a as startUIServer};
1
+ import{a}from"./chunk-E5JCTMWX.js";import"./chunk-A3S6D44B.js";export{a as startUIServer};
2
2
  //# sourceMappingURL=ui-server.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duckpond-mcp-server",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "MCP server for multi-tenant DuckDB management with R2/S3 storage",
5
5
  "keywords": [
6
6
  "mcp",
@@ -32,7 +32,7 @@
32
32
  "@types/jsonwebtoken": "^9.0.10",
33
33
  "commander": "^14.0.2",
34
34
  "debug": "^4.4.3",
35
- "duckpond": "^0.2.1",
35
+ "duckpond": "^0.3.0",
36
36
  "express": "^5.2.1",
37
37
  "hono": "^4.11.3",
38
38
  "jsonwebtoken": "^9.0.3",
@@ -1,48 +0,0 @@
1
- import{a as A}from"./chunk-R65UMTEX.js";import{a as D}from"./chunk-VSQCOL7H.js";import{b as y,c as C,d as $,e as T,f as O,g as P,h as E}from"./chunk-MPMUZFRC.js";import{b as k}from"./chunk-A3S6D44B.js";import{webcrypto as z}from"crypto";import{FastMCP as q}from"@jordanburke/fastmcp";import{createHash as N,randomBytes as w}from"crypto";import{createRequire as J}from"module";import*as S from"jsonwebtoken";import{URL as x}from"url";globalThis.crypto||(globalThis.crypto=z);var F=J(import.meta.url),I=F("../package.json"),d=k.fastmcp,R=process.env.DUCKPOND_JWT_SECRET||w(32).toString("hex"),b=process.env.DUCKPOND_JWT_EXPIRES_IN?parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN,10):365*24*60*60,v=new Map,_=new Map;function W(s){d("\u{1F680} Initializing FastMCP server...");let a=new A(s.config),h={name:"duckpond",version:I.version,health:{enabled:!0,path:"/health",status:200,message:JSON.stringify({status:"healthy",service:"duckpond-mcp-server",version:I.version,timestamp:new Date().toISOString()})}},o=s.oauth?.enabled||s.basicAuth?new q({...h,oauth:{enabled:!0,authorizationServer:{issuer:s.oauth?.issuer||`http://localhost:${s.port||3e3}`,authorizationEndpoint:`${s.oauth?.issuer||`http://localhost:${s.port||3e3}`}/oauth/authorize`,tokenEndpoint:`${s.oauth?.issuer||`http://localhost:${s.port||3e3}`}/oauth/token`,jwksUri:`${s.oauth?.issuer||`http://localhost:${s.port||3e3}`}/oauth/jwks`,registrationEndpoint:`${s.oauth?.issuer||`http://localhost:${s.port||3e3}`}/oauth/register`,responseTypesSupported:["code"],grantTypesSupported:["authorization_code"],tokenEndpointAuthMethodsSupported:["client_secret_post","client_secret_basic"],codeChallengeMethodsSupported:["S256","plain"]},protectedResource:{resource:process.env.DUCKPOND_OAUTH_RESOURCE||s.oauth?.resource||`${s.oauth?.issuer||`http://localhost:${s.port||3e3}`}/mcp`,authorizationServers:[s.oauth?.issuer||`http://localhost:${s.port||3e3}`]}},authenticate:e=>{let r=e.headers?.authorization,t=s.oauth?.issuer||`http://localhost:${s.port||3e3}`;if(!r)throw s.oauth?.enabled?new Response(JSON.stringify({error:"unauthorized",error_description:"Authorization required. Please authenticate via OAuth."}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer realm="MCP", authorization_uri="${t}/oauth/authorize", resource="${t}/.well-known/oauth-protected-resource"`}}):new Response(JSON.stringify({error:"unauthorized",error_description:"Authorization required."}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json"}});if(s.basicAuth&&r.startsWith("Basic ")){let c=Buffer.from(r.slice(6),"base64").toString("utf-8"),[i,l]=c.split(":");if(i===s.basicAuth.username&&l===s.basicAuth.password)return Promise.resolve({userId:s.basicAuth.userId||i,email:s.basicAuth.email||`${i}@example.com`,scope:"read write"});throw new Response(JSON.stringify({error:"unauthorized",error_description:"Invalid username or password"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Basic realm="MCP"'}})}if(s.oauth?.enabled&&r.startsWith("Bearer ")){let c=r.slice(7);try{let i=S.verify(c,R);if(!i.sub||!i.iat||!i.exp)throw new Response(JSON.stringify({error:"invalid_token",error_description:"Invalid token structure"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Invalid token structure"'}});let l=s.oauth?.resource||`${t}/mcp`;if(i.aud&&i.aud!==l)throw new Response(JSON.stringify({error:"invalid_token",error_description:"Token audience mismatch"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Token audience mismatch"'}});return Promise.resolve({userId:i.sub,email:i.email||"",scope:i.scope||"read write"})}catch(i){throw i instanceof Response?i:new Response(JSON.stringify({error:"invalid_token",error_description:"Invalid or expired token"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer realm="MCP", error="invalid_token", error_description="Invalid or expired token"'}})}}throw new Response(JSON.stringify({error:"unauthorized",error_description:"Invalid authorization header format"}),{status:401,statusText:"Unauthorized",headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer realm="MCP", authorization_uri="${t}/oauth/authorize", resource="${t}/.well-known/oauth-protected-resource"`}})}}):new q(h);o.addTool({name:"query",description:"Execute a SQL query for a specific user and return results",parameters:C,execute:async e=>{try{let r=y(e.userId),t=await a.query(r,e.sql);return t.success?JSON.stringify({rows:t.data,rowCount:t.data.length,executionTime:t.executionTime},null,2):`ERROR: ${t.error.message}`}catch(r){d("Error in query tool:",r);let t=r instanceof Error?r.message:String(r);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"execute",description:"Execute SQL statement (DDL/DML) for a specific user without returning results",parameters:$,execute:async e=>{try{let r=y(e.userId),t=await a.execute(r,e.sql);return t.success?JSON.stringify({success:!0,message:"Statement executed successfully",executionTime:t.executionTime},null,2):`ERROR: ${t.error.message}`}catch(r){d("Error in execute tool:",r);let t=r instanceof Error?r.message:String(r);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"getUserStats",description:"Get statistics about a user's database (memory usage, query count, etc.)",parameters:T,execute:async e=>{try{let r=y(e.userId),t=await a.getUserStats(r);return t.success?JSON.stringify({...t.data,lastAccess:t.data.lastAccess.toISOString()},null,2):`ERROR: ${t.error.message}`}catch(r){d("Error in getUserStats tool:",r);let t=r instanceof Error?r.message:String(r);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"isAttached",description:"Check if a user's database is currently cached in memory",parameters:O,execute:async e=>{try{let r=y(e.userId),t=a.isAttached(r);return t.success?JSON.stringify({attached:t.data,userId:r},null,2):`ERROR: ${t.error.message}`}catch(r){d("Error in isAttached tool:",r);let t=r instanceof Error?r.message:String(r);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"detachUser",description:"Manually detach a user's database from the cache to free resources",parameters:P,execute:async e=>{try{let r=y(e.userId),t=await a.detachUser(r);return t.success?JSON.stringify({success:!0,message:`User ${r} detached successfully`},null,2):`ERROR: ${t.error.message}`}catch(r){d("Error in detachUser tool:",r);let t=r instanceof Error?r.message:String(r);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),o.addTool({name:"listUsers",description:"List all currently cached users and cache statistics",parameters:E,execute:async()=>{try{let e=a.listUsers();return e.success?JSON.stringify(e.data,null,2):`ERROR: ${e.error.message}`}catch(e){d("Error in listUsers tool:",e);let r=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:r},null,2)}`}}}),s.oauth?.enabled&&B(o,s);let n=o.getApp();return n.get("/",e=>{let r=s.oauth?.issuer||`http://localhost:${s.port||3e3}`,t={name:"DuckPond MCP Server",version:I.version,description:"Model Context Protocol server for multi-tenant DuckDB with R2/S3 storage",service:"duckpond-mcp-server",capabilities:{tools:["query","execute","getUserStats","isAttached","detachUser","listUsers"],transports:["stdio","http"],authentication:{oauth:s.oauth?.enabled||!1,basicAuth:!!s.basicAuth}},endpoints:{mcp:`${r}${s.endpoint||"/mcp"}`,health:`${r}/health`,ui:`${r}/ui/:userId`,...s.oauth?.enabled&&{oauth:{authorization:`${r}/oauth/authorize`,token:`${r}/oauth/token`,jwks:`${r}/oauth/jwks`,register:`${r}/oauth/register`}}},timestamp:new Date().toISOString()};return e.json(t)}),L(n,a,s),d("\u2713 FastMCP server created"),{server:o,duckpond:a}}function B(s,a){let h=s.getApp();setInterval(()=>{let o=Date.now();for(let[n,e]of v.entries())o-e.createdAt>6e5&&v.delete(n);for(let[n,e]of _.entries())o-e.createdAt>2592e6&&_.delete(n)},6e4),h.get("/oauth/authorize",o=>{let n=o.req.query(),e=n.response_type,r=n.redirect_uri,t=n.state,c=n.code_challenge,i=n.code_challenge_method,l=n.client_id;if(e!=="code")return o.json({error:"unsupported_response_type",error_description:"Only 'code' response type is supported"},400);if(!r)return o.json({error:"invalid_request",error_description:"redirect_uri is required"},400);if(c&&(!i||!["S256","plain"].includes(i)))return o.json({error:"invalid_request",error_description:"Invalid code_challenge_method. Only 'S256' and 'plain' are supported"},400);let u=`
2
- <!DOCTYPE html>
3
- <html>
4
- <head>
5
- <title>OAuth Login - DuckPond MCP Server</title>
6
- <style>
7
- body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }
8
- .form-group { margin-bottom: 15px; }
9
- label { display: block; margin-bottom: 5px; font-weight: bold; }
10
- input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
11
- button { width: 100%; padding: 12px; background: #007cba; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }
12
- button:hover { background: #005a87; }
13
- .app-info { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 20px; }
14
- </style>
15
- </head>
16
- <body>
17
- <div class="app-info">
18
- <h3>\u{1F510} OAuth Authorization</h3>
19
- <p><strong>Application:</strong> ${l||"MCP Client"}</p>
20
- <p><strong>Permissions:</strong> Read and write access to DuckDB databases</p>
21
- </div>
22
-
23
- <form method="POST" action="/oauth/authorize">
24
- <input type="hidden" name="response_type" value="${e}">
25
- <input type="hidden" name="redirect_uri" value="${r}">
26
- <input type="hidden" name="state" value="${t||""}">
27
- <input type="hidden" name="code_challenge" value="${c||""}">
28
- <input type="hidden" name="code_challenge_method" value="${i||""}">
29
- <input type="hidden" name="client_id" value="${l||""}">
30
-
31
- <div class="form-group">
32
- <label for="username">Username:</label>
33
- <input type="text" id="username" name="username" required>
34
- </div>
35
-
36
- <div class="form-group">
37
- <label for="password">Password:</label>
38
- <input type="password" id="password" name="password" required>
39
- </div>
40
-
41
- <button type="submit">Authorize Application</button>
42
- </form>
43
- </body>
44
- </html>`;return o.html(u)}),h.post("/oauth/authorize",async o=>{try{let n=await o.req.text(),e=new URLSearchParams(n),r=e.get("username"),t=e.get("password"),c=e.get("redirect_uri"),i=e.get("state"),l=e.get("code_challenge"),u=e.get("code_challenge_method");if(r!==a.oauth?.username||t!==a.oauth?.password)return o.html(`
45
- <!DOCTYPE html>
46
- <html><head><title>Login Failed</title><style>body{font-family:Arial;max-width:400px;margin:100px auto;padding:20px;}.error{color:red;background:#fee;padding:10px;border-radius:4px;margin-bottom:15px;}</style></head>
47
- <body><div class="error">\u274C Invalid username or password</div><a href="javascript:history.back()">\u2190 Try Again</a></body></html>`,401);let m=w(16).toString("hex");v.set(m,{createdAt:Date.now(),redirectUri:c||"",codeChallenge:l||void 0,codeChallengeMethod:u||void 0,userId:a.oauth?.userId||r||"oauth-user"});let p=new x(c||"");return p.searchParams.set("code",m),i&&p.searchParams.set("state",i),o.redirect(p.toString(),302)}catch{return o.json({error:"invalid_request",error_description:"Failed to process authorization request"},400)}}),h.post("/oauth/token",async o=>{let n=await o.req.text(),e=new URLSearchParams(n),r=e.get("grant_type"),t=e.get("code"),c=e.get("redirect_uri"),i=e.get("code_verifier"),l=e.get("refresh_token");if(r==="refresh_token"){if(!l)return o.json({error:"invalid_request",error_description:"refresh_token is required for refresh_token grant type"},400);let f=_.get(l);if(!f)return o.json({error:"invalid_grant",error_description:"Invalid or expired refresh token"},400);_.delete(l);let M={sub:f.userId,email:f.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+b,iss:a.oauth?.issuer||`http://localhost:${a.port||3e3}`,aud:a.oauth?.resource||`${a.oauth?.issuer||`http://localhost:${a.port||3e3}`}/mcp`},U=w(32).toString("hex");_.set(U,{createdAt:Date.now(),userId:f.userId,email:f.email});let j=S.sign(M,R);return o.json({access_token:j,token_type:"Bearer",expires_in:b,scope:"read write",refresh_token:U})}if(r!=="authorization_code")return o.json({error:"unsupported_grant_type",error_description:"Only 'authorization_code' and 'refresh_token' grant types are supported"},400);let u=v.get(t||"");if(!u)return o.json({error:"invalid_grant",error_description:"Invalid or expired authorization code"},400);if(u.redirectUri&&u.redirectUri!==c)return o.json({error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(u.codeChallenge){if(!i)return o.json({error:"invalid_grant",error_description:"code_verifier is required when code_challenge was used"},400);let f;if(u.codeChallengeMethod==="S256"?f=N("sha256").update(i).digest().toString("base64url"):f=i,f!==u.codeChallenge)return o.json({error:"invalid_grant",error_description:"Invalid code_verifier"},400)}v.delete(t);let m={sub:u.userId,email:a.oauth?.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+b,iss:a.oauth?.issuer||`http://localhost:${a.port||3e3}`,aud:a.oauth?.resource||`${a.oauth?.issuer||`http://localhost:${a.port||3e3}`}/mcp`},p=w(32).toString("hex");_.set(p,{createdAt:Date.now(),userId:u.userId,email:a.oauth?.email});let g=S.sign(m,R);return o.json({access_token:g,token_type:"Bearer",expires_in:b,scope:"read write",refresh_token:p})}),h.get("/oauth/jwks",o=>o.json({keys:[{kty:"oct",use:"sig",kid:"duckpond-hmac-key",alg:"HS256"}]})),h.post("/oauth/register",async o=>{try{let n={};try{let c=await o.req.text();if(c&&c!=="[object Object]")try{n=JSON.parse(c)}catch{n=Object.fromEntries(new URLSearchParams(c))}}catch(c){d("Error parsing request body:",c)}let e=`client-${w(8).toString("hex")}`,r=w(16).toString("hex"),t={client_id:e,client_secret:r,client_id_issued_at:Math.floor(Date.now()/1e3),client_secret_expires_at:0,grant_types:n.grant_types||["authorization_code"],response_types:n.response_types||["code"],redirect_uris:n.redirect_uris||[],token_endpoint_auth_method:n.token_endpoint_auth_method||"client_secret_post"};return n.client_name&&(t.client_name=n.client_name),n.scope&&(t.scope=n.scope),o.json(t,201)}catch(n){return o.json({error:"invalid_client_metadata",error_description:"Invalid client registration request: "+(n instanceof Error?n.message:String(n))},400)}}),d("\u2713 OAuth flow endpoints added")}function L(s,a,h){let o=h.oauth?.issuer||`http://localhost:${h.port||3e3}`,n=a.getUIPort();s.get("/ui",e=>{if(!a.getCurrentUIUser()){let t=a.listUsers();return e.json({message:"No UI active. Visit /ui/:userId to start DuckDB UI for a user.",currentUser:null,availableUsers:t.success?t.data.users:[],endpoints:{startUI:`${o}/ui/:userId`}})}return e.redirect("/ui/")}),s.get("/ui/:userId",async e=>{let r=e.req.param("userId");d(`Starting UI for user: ${r}`);let t=await a.startUI(r);return t.success?e.redirect("/ui/"):e.json({error:"Failed to start UI",message:t.error.message,details:t.error.details},500)}),s.all("/ui/*",async e=>{if(!a.getCurrentUIUser())return e.json({error:"No UI active",message:"Start UI by visiting /ui/:userId first"},400);let t=e.req.path.replace(/^\/ui/,"")||"/";try{let c=`http://localhost:${n}${t}`,i=new x(e.req.url);i.search;let l=new Headers;e.req.raw.headers.forEach((p,g)=>{g.toLowerCase()!=="host"&&l.set(g,p)});let u=await fetch(c+i.search,{method:e.req.method,headers:l,body:["GET","HEAD"].includes(e.req.method)?void 0:await e.req.arrayBuffer()}),m=new Headers;return u.headers.forEach((p,g)=>{["transfer-encoding","connection"].includes(g.toLowerCase())||m.set(g,p)}),new Response(u.body,{status:u.status,statusText:u.statusText,headers:m})}catch(c){return d(`UI proxy error: ${c instanceof Error?c.message:String(c)}`),e.json({error:"UI proxy error",message:c instanceof Error?c.message:"Failed to connect to DuckDB UI",hint:"The DuckDB UI server may not be running. Try visiting /ui/:userId to restart it."},502)}}),s.all("*",async e=>{if(!a.getCurrentUIUser())return e.notFound();let t=e.req.path;if(!t.match(/\.(js|css|json|woff2?|ttf|svg|png|ico)$/)&&!t.includes("hatchling"))return e.notFound();try{let c=`http://localhost:${n}${t}`,i=new x(e.req.url),l=new Headers;e.req.raw.headers.forEach((p,g)=>{g.toLowerCase()!=="host"&&l.set(g,p)});let u=await fetch(c+i.search,{method:e.req.method,headers:l,body:["GET","HEAD"].includes(e.req.method)?void 0:await e.req.arrayBuffer()}),m=new Headers;return u.headers.forEach((p,g)=>{["transfer-encoding","connection"].includes(g.toLowerCase())||m.set(g,p)}),new Response(u.body,{status:u.status,statusText:u.statusText,headers:m})}catch{return e.notFound()}}),d("\u2713 UI endpoints added")}async function te(s,a){let{server:h,duckpond:o}=W(s),n=await o.init();if(!n.success)throw new Error(`Failed to initialize DuckPond: ${n.error.message}`);d("DuckPond initialized successfully"),s.ui?.internalPort&&o.setUIPort(s.ui.internalPort),a==="stdio"?(await h.start({transportType:"stdio"}),d("\u2713 FastMCP server running with stdio transport"),s.ui?.enabled&&await D({port:s.ui.port,duckpond:o})):(await h.start({transportType:"httpStream",httpStream:{port:s.port||3e3,endpoint:s.endpoint||"/mcp"}}),d(`\u2713 FastMCP server running on http://0.0.0.0:${s.port||3e3}${s.endpoint||"/mcp"}`),d("\u{1F50C} Connect with StreamableHTTPClientTransport")),process.on("SIGINT",async()=>{d("Received SIGINT, closing server..."),await o.close(),process.exit(0)}),process.on("SIGTERM",async()=>{d("Received SIGTERM, closing server..."),await o.close(),process.exit(0)})}export{W as a,te as b};
48
- //# sourceMappingURL=chunk-JXCFUXVK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { FastMCP } from \"@jordanburke/fastmcp\"\nimport { createHash, randomBytes } from \"crypto\"\nimport { createRequire } from \"module\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport * as jwt from \"jsonwebtoken\"\nimport { URL } from \"url\"\nimport { z } from \"zod\"\n\nimport { DuckPondServer, type DuckPondServerConfig } from \"./server-core\"\nimport {\n detachUserSchema,\n executeSchema,\n getDefaultUserId,\n getUserStatsSchema,\n isAttachedSchema,\n listUsersSchema,\n querySchema,\n resolveUserId,\n} from \"./tools\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.fastmcp\n\nexport type OAuthConfig = {\n enabled: boolean\n username: string\n password: string\n userId: string\n email?: string\n issuer?: string\n resource?: string\n}\n\nexport type FastMCPServerOptions = {\n config: DuckPondServerConfig\n port?: number\n endpoint?: string\n oauth?: OAuthConfig\n basicAuth?: {\n username: string\n password: string\n userId?: string\n email?: string\n }\n ui?: {\n enabled: boolean\n port: number\n internalPort?: number\n }\n}\n\n// JWT secret for token signing/validation\nconst JWT_SECRET = process.env.DUCKPOND_JWT_SECRET || randomBytes(32).toString(\"hex\")\n\n// JWT token expiration configuration (default: 1 year)\nconst JWT_EXPIRES_IN = process.env.DUCKPOND_JWT_EXPIRES_IN\n ? parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN, 10)\n : 365 * 24 * 60 * 60 // 1 year in seconds\n\n// In-memory stores for OAuth flow\nconst authorizationCodes = new Map<\n string,\n {\n createdAt: number\n redirectUri?: string\n codeChallenge?: string\n codeChallengeMethod?: string\n userId: string\n }\n>()\n\nconst refreshTokens = new Map<\n string,\n {\n createdAt: number\n userId: string\n email?: string\n }\n>()\n\n// AuthSession type for FastMCP authentication\ntype AuthSession = {\n userId: string\n email: string\n scope: string\n [key: string]: unknown // Allow additional properties\n}\n\ntype OAuthClientRegistrationRequest = {\n grant_types?: string[]\n response_types?: string[]\n redirect_uris?: string[]\n token_endpoint_auth_method?: string\n client_name?: string\n scope?: string\n}\n\ntype OAuthClientRegistrationResponse = {\n client_id: string\n client_secret: string\n client_id_issued_at: number\n client_secret_expires_at: number\n grant_types: string[]\n response_types: string[]\n redirect_uris: string[]\n token_endpoint_auth_method: string\n client_name?: string\n scope?: string\n}\n\nexport function createFastMCPServer(options: FastMCPServerOptions): {\n server: FastMCP\n duckpond: DuckPondServer\n} {\n log(\"🚀 Initializing FastMCP server...\")\n\n // Create DuckPond server instance\n const duckpond = new DuckPondServer(options.config)\n\n // Build server configuration\n const baseConfig = {\n name: \"duckpond\",\n version: packageJson.version as `${number}.${number}.${number}`,\n health: {\n enabled: true,\n path: \"/health\",\n status: 200,\n message: JSON.stringify({\n status: \"healthy\",\n service: \"duckpond-mcp-server\",\n version: packageJson.version,\n timestamp: new Date().toISOString(),\n }),\n },\n }\n\n // Create server with authentication (OAuth, Basic Auth, or none)\n const server =\n options.oauth?.enabled || options.basicAuth\n ? new FastMCP<AuthSession>({\n ...baseConfig,\n oauth: {\n enabled: true,\n authorizationServer: {\n issuer: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n authorizationEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/authorize`,\n tokenEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/token`,\n jwksUri: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/jwks`,\n registrationEndpoint: `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/oauth/register`,\n responseTypesSupported: [\"code\"],\n grantTypesSupported: [\"authorization_code\"],\n tokenEndpointAuthMethodsSupported: [\"client_secret_post\", \"client_secret_basic\"],\n codeChallengeMethodsSupported: [\"S256\", \"plain\"],\n },\n protectedResource: {\n resource:\n process.env.DUCKPOND_OAUTH_RESOURCE ||\n options.oauth?.resource ||\n `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n authorizationServers: [options.oauth?.issuer || `http://localhost:${options.port || 3000}`],\n },\n },\n authenticate: (request) => {\n const authHeader = request.headers?.authorization\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n\n // For OAuth-enabled servers, require authentication\n if (!authHeader) {\n if (options.oauth?.enabled) {\n // Return HTTP 401 with WWW-Authenticate header for proper OAuth discovery\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Authorization required. Please authenticate via OAuth.\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", authorization_uri=\"${baseUrl}/oauth/authorize\", resource=\"${baseUrl}/.well-known/oauth-protected-resource\"`,\n },\n },\n )\n }\n\n // For non-OAuth servers, also require some form of auth\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Authorization required.\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n },\n )\n }\n\n // Handle Basic Authentication\n if (options.basicAuth && authHeader.startsWith(\"Basic \")) {\n const credentials = Buffer.from(authHeader.slice(6), \"base64\").toString(\"utf-8\")\n const [username, password] = credentials.split(\":\")\n\n if (username === options.basicAuth.username && password === options.basicAuth.password) {\n return Promise.resolve({\n userId: options.basicAuth.userId || username,\n email: options.basicAuth.email || `${username}@example.com`,\n scope: \"read write\",\n })\n } else {\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Invalid username or password\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Basic realm=\"MCP\"`,\n },\n },\n )\n }\n }\n\n // Handle Bearer Token (OAuth) - Validate JWT\n if (options.oauth?.enabled && authHeader.startsWith(\"Bearer \")) {\n const token = authHeader.slice(7) // Remove 'Bearer ' prefix\n\n try {\n // Verify JWT token\n const decoded = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload\n\n if (!decoded.sub || !decoded.iat || !decoded.exp) {\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Invalid token structure\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Invalid token structure\"`,\n },\n },\n )\n }\n\n // Validate audience\n const expectedAudience = options.oauth?.resource || `${baseUrl}/mcp`\n if (decoded.aud && decoded.aud !== expectedAudience) {\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Token audience mismatch\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Token audience mismatch\"`,\n },\n },\n )\n }\n\n // Return user info from JWT claims\n return Promise.resolve({\n userId: decoded.sub,\n email: (decoded.email as string) || \"\",\n scope: (decoded.scope as string) || \"read write\",\n })\n } catch (error) {\n if (error instanceof Response) {\n throw error // Re-throw our custom Response errors\n }\n\n throw new Response(\n JSON.stringify({\n error: \"invalid_token\",\n error_description: \"Invalid or expired token\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", error=\"invalid_token\", error_description=\"Invalid or expired token\"`,\n },\n },\n )\n }\n }\n\n throw new Response(\n JSON.stringify({\n error: \"unauthorized\",\n error_description: \"Invalid authorization header format\",\n }),\n {\n status: 401,\n statusText: \"Unauthorized\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer realm=\"MCP\", authorization_uri=\"${baseUrl}/oauth/authorize\", resource=\"${baseUrl}/.well-known/oauth-protected-resource\"`,\n },\n },\n )\n },\n })\n : new FastMCP(baseConfig)\n\n // Add query tool\n server.addTool({\n name: \"query\",\n description: \"Execute a SQL query for a specific user and return results\",\n parameters: querySchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.query(userId, args.sql)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n rows: result.data,\n rowCount: result.data.length,\n executionTime: result.executionTime,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in query tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add execute tool\n server.addTool({\n name: \"execute\",\n description: \"Execute SQL statement (DDL/DML) for a specific user without returning results\",\n parameters: executeSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.execute(userId, args.sql)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n success: true,\n message: \"Statement executed successfully\",\n executionTime: result.executionTime,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in execute tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add getUserStats tool\n server.addTool({\n name: \"getUserStats\",\n description: \"Get statistics about a user's database (memory usage, query count, etc.)\",\n parameters: getUserStatsSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.getUserStats(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n ...result.data,\n lastAccess: result.data.lastAccess.toISOString(),\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in getUserStats tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add isAttached tool\n server.addTool({\n name: \"isAttached\",\n description: \"Check if a user's database is currently cached in memory\",\n parameters: isAttachedSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = duckpond.isAttached(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n attached: result.data,\n userId,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in isAttached tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add detachUser tool\n server.addTool({\n name: \"detachUser\",\n description: \"Manually detach a user's database from the cache to free resources\",\n parameters: detachUserSchema,\n execute: async (args) => {\n try {\n const userId = resolveUserId(args.userId)\n const result = await duckpond.detachUser(userId)\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(\n {\n success: true,\n message: `User ${userId} detached successfully`,\n },\n null,\n 2,\n )\n } catch (error) {\n log(\"Error in detachUser tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add listUsers tool\n server.addTool({\n name: \"listUsers\",\n description: \"List all currently cached users and cache statistics\",\n parameters: listUsersSchema,\n execute: async () => {\n try {\n const result = duckpond.listUsers()\n\n if (!result.success) {\n return `ERROR: ${result.error.message}`\n }\n\n return JSON.stringify(result.data, null, 2)\n } catch (error) {\n log(\"Error in listUsers tool:\", error)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return `ERROR: ${JSON.stringify({ error: errorMessage }, null, 2)}`\n }\n },\n })\n\n // Add OAuth flow endpoints if OAuth is enabled\n if (options.oauth?.enabled) {\n setupOAuthEndpoints(server, options)\n }\n\n // Add root info endpoint using Hono\n const app = server.getApp()\n app.get(\"/\", (c) => {\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n\n const serverInfo = {\n name: \"DuckPond MCP Server\",\n version: packageJson.version,\n description: \"Model Context Protocol server for multi-tenant DuckDB with R2/S3 storage\",\n service: \"duckpond-mcp-server\",\n capabilities: {\n tools: [\"query\", \"execute\", \"getUserStats\", \"isAttached\", \"detachUser\", \"listUsers\"],\n transports: [\"stdio\", \"http\"],\n authentication: {\n oauth: options.oauth?.enabled || false,\n basicAuth: !!options.basicAuth,\n },\n },\n endpoints: {\n mcp: `${baseUrl}${options.endpoint || \"/mcp\"}`,\n health: `${baseUrl}/health`,\n ui: `${baseUrl}/ui/:userId`,\n ...(options.oauth?.enabled && {\n oauth: {\n authorization: `${baseUrl}/oauth/authorize`,\n token: `${baseUrl}/oauth/token`,\n jwks: `${baseUrl}/oauth/jwks`,\n register: `${baseUrl}/oauth/register`,\n },\n }),\n },\n timestamp: new Date().toISOString(),\n }\n\n return c.json(serverInfo)\n })\n\n // Add UI endpoints for DuckDB UI access\n setupUIEndpoints(app, duckpond, options)\n\n log(\"✓ FastMCP server created\")\n\n return { server, duckpond }\n}\n\nfunction setupOAuthEndpoints(server: FastMCP, options: FastMCPServerOptions): void {\n const app = server.getApp()\n\n // Clean up old codes and refresh tokens every minute\n setInterval(() => {\n const now = Date.now()\n // Clean authorization codes (10 minutes)\n for (const [code, data] of authorizationCodes.entries()) {\n if (now - data.createdAt > 600000) {\n authorizationCodes.delete(code)\n }\n }\n // Clean refresh tokens (30 days)\n for (const [token, data] of refreshTokens.entries()) {\n if (now - data.createdAt > 2592000000) {\n refreshTokens.delete(token)\n }\n }\n }, 60000)\n\n // OAuth Authorization Endpoint - Login Form\n app.get(\"/oauth/authorize\", (c) => {\n const params = c.req.query()\n const responseType = params.response_type\n const redirectUri = params.redirect_uri\n const state = params.state\n const codeChallenge = params.code_challenge\n const codeChallengeMethod = params.code_challenge_method\n const clientId = params.client_id\n\n if (responseType !== \"code\") {\n return c.json(\n {\n error: \"unsupported_response_type\",\n error_description: \"Only 'code' response type is supported\",\n },\n 400,\n )\n }\n\n if (!redirectUri) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"redirect_uri is required\",\n },\n 400,\n )\n }\n\n // Validate PKCE parameters if present\n if (codeChallenge) {\n if (!codeChallengeMethod || ![\"S256\", \"plain\"].includes(codeChallengeMethod)) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"Invalid code_challenge_method. Only 'S256' and 'plain' are supported\",\n },\n 400,\n )\n }\n }\n\n // Serve login form\n const loginForm = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>OAuth Login - DuckPond MCP Server</title>\n <style>\n body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }\n .form-group { margin-bottom: 15px; }\n label { display: block; margin-bottom: 5px; font-weight: bold; }\n input[type=\"text\"], input[type=\"password\"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }\n button { width: 100%; padding: 12px; background: #007cba; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }\n button:hover { background: #005a87; }\n .app-info { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 20px; }\n </style>\n</head>\n<body>\n <div class=\"app-info\">\n <h3>🔐 OAuth Authorization</h3>\n <p><strong>Application:</strong> ${clientId || \"MCP Client\"}</p>\n <p><strong>Permissions:</strong> Read and write access to DuckDB databases</p>\n </div>\n\n <form method=\"POST\" action=\"/oauth/authorize\">\n <input type=\"hidden\" name=\"response_type\" value=\"${responseType}\">\n <input type=\"hidden\" name=\"redirect_uri\" value=\"${redirectUri}\">\n <input type=\"hidden\" name=\"state\" value=\"${state || \"\"}\">\n <input type=\"hidden\" name=\"code_challenge\" value=\"${codeChallenge || \"\"}\">\n <input type=\"hidden\" name=\"code_challenge_method\" value=\"${codeChallengeMethod || \"\"}\">\n <input type=\"hidden\" name=\"client_id\" value=\"${clientId || \"\"}\">\n\n <div class=\"form-group\">\n <label for=\"username\">Username:</label>\n <input type=\"text\" id=\"username\" name=\"username\" required>\n </div>\n\n <div class=\"form-group\">\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required>\n </div>\n\n <button type=\"submit\">Authorize Application</button>\n </form>\n</body>\n</html>`\n\n return c.html(loginForm)\n })\n\n // OAuth Authorization POST - Process Login\n app.post(\"/oauth/authorize\", async (c) => {\n try {\n const body = await c.req.text()\n const params = new URLSearchParams(body)\n\n const username = params.get(\"username\")\n const password = params.get(\"password\")\n const redirectUri = params.get(\"redirect_uri\")\n const state = params.get(\"state\")\n const codeChallenge = params.get(\"code_challenge\")\n const codeChallengeMethod = params.get(\"code_challenge_method\")\n\n // Validate credentials\n if (username !== options.oauth?.username || password !== options.oauth?.password) {\n const errorForm = `\n<!DOCTYPE html>\n<html><head><title>Login Failed</title><style>body{font-family:Arial;max-width:400px;margin:100px auto;padding:20px;}.error{color:red;background:#fee;padding:10px;border-radius:4px;margin-bottom:15px;}</style></head>\n<body><div class=\"error\">❌ Invalid username or password</div><a href=\"javascript:history.back()\">← Try Again</a></body></html>`\n return c.html(errorForm, 401)\n }\n\n // Generate authorization code\n const code = randomBytes(16).toString(\"hex\")\n authorizationCodes.set(code, {\n createdAt: Date.now(),\n redirectUri: redirectUri || \"\",\n codeChallenge: codeChallenge || undefined,\n codeChallengeMethod: codeChallengeMethod || undefined,\n userId: options.oauth?.userId || username || \"oauth-user\",\n })\n\n // Redirect with authorization code\n const redirectUrl = new URL(redirectUri || \"\")\n redirectUrl.searchParams.set(\"code\", code)\n if (state) {\n redirectUrl.searchParams.set(\"state\", state)\n }\n\n return c.redirect(redirectUrl.toString(), 302)\n } catch {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"Failed to process authorization request\",\n },\n 400,\n )\n }\n })\n\n // OAuth Token Endpoint\n app.post(\"/oauth/token\", async (c) => {\n const body = await c.req.text()\n const params = new URLSearchParams(body)\n const grantType = params.get(\"grant_type\")\n const code = params.get(\"code\")\n const redirectUri = params.get(\"redirect_uri\")\n const codeVerifier = params.get(\"code_verifier\")\n const refreshTokenParam = params.get(\"refresh_token\")\n\n if (grantType === \"refresh_token\") {\n // Handle refresh token flow\n if (!refreshTokenParam) {\n return c.json(\n {\n error: \"invalid_request\",\n error_description: \"refresh_token is required for refresh_token grant type\",\n },\n 400,\n )\n }\n\n const tokenData = refreshTokens.get(refreshTokenParam)\n if (!tokenData) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid or expired refresh token\",\n },\n 400,\n )\n }\n\n // Remove old refresh token (token rotation)\n refreshTokens.delete(refreshTokenParam)\n\n // Generate new JWT access token\n const accessTokenPayload = {\n sub: tokenData.userId,\n email: tokenData.email || \"\",\n scope: \"read write\",\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + JWT_EXPIRES_IN,\n iss: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n aud: options.oauth?.resource || `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n }\n\n // Generate new refresh token\n const newRefreshToken = randomBytes(32).toString(\"hex\")\n refreshTokens.set(newRefreshToken, {\n createdAt: Date.now(),\n userId: tokenData.userId,\n email: tokenData.email,\n })\n\n const accessToken = jwt.sign(accessTokenPayload, JWT_SECRET)\n\n return c.json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: JWT_EXPIRES_IN,\n scope: \"read write\",\n refresh_token: newRefreshToken,\n })\n }\n\n if (grantType !== \"authorization_code\") {\n return c.json(\n {\n error: \"unsupported_grant_type\",\n error_description: \"Only 'authorization_code' and 'refresh_token' grant types are supported\",\n },\n 400,\n )\n }\n\n const codeData = authorizationCodes.get(code || \"\")\n if (!codeData) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid or expired authorization code\",\n },\n 400,\n )\n }\n\n // Validate redirect_uri matches\n if (codeData.redirectUri && codeData.redirectUri !== redirectUri) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"redirect_uri mismatch\",\n },\n 400,\n )\n }\n\n // Validate PKCE if code_challenge was provided\n if (codeData.codeChallenge) {\n if (!codeVerifier) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"code_verifier is required when code_challenge was used\",\n },\n 400,\n )\n }\n\n let expectedChallenge: string\n if (codeData.codeChallengeMethod === \"S256\") {\n expectedChallenge = createHash(\"sha256\").update(codeVerifier).digest().toString(\"base64url\")\n } else {\n // 'plain' method\n expectedChallenge = codeVerifier\n }\n\n if (expectedChallenge !== codeData.codeChallenge) {\n return c.json(\n {\n error: \"invalid_grant\",\n error_description: \"Invalid code_verifier\",\n },\n 400,\n )\n }\n }\n\n // Remove used code\n authorizationCodes.delete(code!)\n\n // Generate JWT access token\n const accessTokenPayload = {\n sub: codeData.userId,\n email: options.oauth?.email || \"\",\n scope: \"read write\",\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + JWT_EXPIRES_IN,\n iss: options.oauth?.issuer || `http://localhost:${options.port || 3000}`,\n aud: options.oauth?.resource || `${options.oauth?.issuer || `http://localhost:${options.port || 3000}`}/mcp`,\n }\n\n // Generate refresh token\n const refreshToken = randomBytes(32).toString(\"hex\")\n refreshTokens.set(refreshToken, {\n createdAt: Date.now(),\n userId: codeData.userId,\n email: options.oauth?.email,\n })\n\n const accessToken = jwt.sign(accessTokenPayload, JWT_SECRET)\n\n return c.json({\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: JWT_EXPIRES_IN,\n scope: \"read write\",\n refresh_token: refreshToken,\n })\n })\n\n // JWKS Endpoint\n app.get(\"/oauth/jwks\", (c) => {\n return c.json({\n keys: [\n {\n kty: \"oct\", // Octet sequence for symmetric keys\n use: \"sig\",\n kid: \"duckpond-hmac-key\",\n alg: \"HS256\",\n },\n ],\n })\n })\n\n // Dynamic Client Registration\n app.post(\"/oauth/register\", async (c) => {\n try {\n let registrationRequest: OAuthClientRegistrationRequest = {}\n\n try {\n const body = await c.req.text()\n if (body && body !== \"[object Object]\") {\n try {\n registrationRequest = JSON.parse(body) as OAuthClientRegistrationRequest\n } catch {\n const formData = Object.fromEntries(new URLSearchParams(body))\n registrationRequest = formData as OAuthClientRegistrationRequest\n }\n }\n } catch (parseError) {\n log(\"Error parsing request body:\", parseError)\n }\n\n const clientId = `client-${randomBytes(8).toString(\"hex\")}`\n const clientSecret = randomBytes(16).toString(\"hex\")\n\n const response: OAuthClientRegistrationResponse = {\n client_id: clientId,\n client_secret: clientSecret,\n client_id_issued_at: Math.floor(Date.now() / 1000),\n client_secret_expires_at: 0, // Never expires\n grant_types: registrationRequest.grant_types || [\"authorization_code\"],\n response_types: registrationRequest.response_types || [\"code\"],\n redirect_uris: registrationRequest.redirect_uris || [],\n token_endpoint_auth_method: registrationRequest.token_endpoint_auth_method || \"client_secret_post\",\n }\n\n if (registrationRequest.client_name) {\n response.client_name = registrationRequest.client_name\n }\n if (registrationRequest.scope) {\n response.scope = registrationRequest.scope\n }\n\n return c.json(response, 201)\n } catch (error) {\n return c.json(\n {\n error: \"invalid_client_metadata\",\n error_description:\n \"Invalid client registration request: \" + (error instanceof Error ? error.message : String(error)),\n },\n 400,\n )\n }\n })\n\n log(\"✓ OAuth flow endpoints added\")\n}\n\nfunction setupUIEndpoints(\n app: ReturnType<FastMCP[\"getApp\"]>,\n duckpond: DuckPondServer,\n options: FastMCPServerOptions,\n): void {\n const baseUrl = options.oauth?.issuer || `http://localhost:${options.port || 3000}`\n const uiInternalPort = duckpond.getUIPort()\n\n // GET /ui - Info endpoint when no UI running, or redirect to UI\n app.get(\"/ui\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n const listResult = duckpond.listUsers()\n return c.json({\n message: \"No UI active. Visit /ui/:userId to start DuckDB UI for a user.\",\n currentUser: null,\n availableUsers: listResult.success ? listResult.data.users : [],\n endpoints: {\n startUI: `${baseUrl}/ui/:userId`,\n },\n })\n }\n // Redirect to the UI root\n return c.redirect(\"/ui/\")\n })\n\n // GET /ui/:userId - Start UI for a specific user and redirect\n app.get(\"/ui/:userId\", async (c) => {\n const userId = c.req.param(\"userId\")\n\n log(`Starting UI for user: ${userId}`)\n const result = await duckpond.startUI(userId)\n\n if (!result.success) {\n return c.json(\n {\n error: \"Failed to start UI\",\n message: result.error.message,\n details: result.error.details,\n },\n 500,\n )\n }\n\n // Redirect to the UI\n return c.redirect(\"/ui/\")\n })\n\n // ALL /ui/* - Proxy requests to DuckDB UI server\n app.all(\"/ui/*\", async (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n return c.json(\n {\n error: \"No UI active\",\n message: \"Start UI by visiting /ui/:userId first\",\n },\n 400,\n )\n }\n\n // Get the path after /ui\n const path = c.req.path.replace(/^\\/ui/, \"\") || \"/\"\n\n try {\n // Build the proxy URL\n const proxyUrl = `http://localhost:${uiInternalPort}${path}`\n const url = new URL(c.req.url)\n if (url.search) {\n // Append query string if present\n }\n\n // Prepare headers, filtering out host\n const headers = new Headers()\n c.req.raw.headers.forEach((value, key) => {\n if (key.toLowerCase() !== \"host\") {\n headers.set(key, value)\n }\n })\n\n // Make the proxy request\n const response = await fetch(proxyUrl + url.search, {\n method: c.req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(c.req.method) ? undefined : await c.req.arrayBuffer(),\n })\n\n // Return the proxied response\n const responseHeaders = new Headers()\n response.headers.forEach((value, key) => {\n // Don't forward certain headers\n if (![\"transfer-encoding\", \"connection\"].includes(key.toLowerCase())) {\n responseHeaders.set(key, value)\n }\n })\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n })\n } catch (error) {\n log(`UI proxy error: ${error instanceof Error ? error.message : String(error)}`)\n return c.json(\n {\n error: \"UI proxy error\",\n message: error instanceof Error ? error.message : \"Failed to connect to DuckDB UI\",\n hint: \"The DuckDB UI server may not be running. Try visiting /ui/:userId to restart it.\",\n },\n 502,\n )\n }\n })\n\n // Fallback: Proxy unknown paths to DuckDB UI (for assets like JS/CSS)\n // DuckDB UI uses <base href=\"/\"> so assets are requested at root\n // Only proxy if UI is active and path looks like a DuckDB UI asset\n app.all(\"*\", async (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n return c.notFound()\n }\n\n const path = c.req.path\n\n // Only proxy paths that look like DuckDB UI assets\n if (!path.match(/\\.(js|css|json|woff2?|ttf|svg|png|ico)$/) && !path.includes(\"hatchling\")) {\n return c.notFound()\n }\n\n try {\n const proxyUrl = `http://localhost:${uiInternalPort}${path}`\n const url = new URL(c.req.url)\n\n const headers = new Headers()\n c.req.raw.headers.forEach((value, key) => {\n if (key.toLowerCase() !== \"host\") {\n headers.set(key, value)\n }\n })\n\n const response = await fetch(proxyUrl + url.search, {\n method: c.req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(c.req.method) ? undefined : await c.req.arrayBuffer(),\n })\n\n const responseHeaders = new Headers()\n response.headers.forEach((value, key) => {\n if (![\"transfer-encoding\", \"connection\"].includes(key.toLowerCase())) {\n responseHeaders.set(key, value)\n }\n })\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n })\n } catch {\n return c.notFound()\n }\n })\n\n log(\"✓ UI endpoints added\")\n}\n\nexport async function startServer(options: FastMCPServerOptions, transport: \"stdio\" | \"http\"): Promise<void> {\n const { server, duckpond } = createFastMCPServer(options)\n\n // Initialize DuckPond\n const initResult = await duckpond.init()\n if (!initResult.success) {\n throw new Error(`Failed to initialize DuckPond: ${initResult.error.message}`)\n }\n\n log(\"DuckPond initialized successfully\")\n\n // Set UI internal port if configured\n if (options.ui?.internalPort) {\n duckpond.setUIPort(options.ui.internalPort)\n }\n\n // Start the server with appropriate transport\n if (transport === \"stdio\") {\n await server.start({\n transportType: \"stdio\",\n })\n log(\"✓ FastMCP server running with stdio transport\")\n\n // Start UI server if enabled in stdio mode\n if (options.ui?.enabled) {\n await startUIServer({\n port: options.ui.port,\n duckpond,\n })\n }\n } else {\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port: options.port || 3000,\n endpoint: (options.endpoint || \"/mcp\") as `/${string}`,\n },\n })\n log(`✓ FastMCP server running on http://0.0.0.0:${options.port || 3000}${options.endpoint || \"/mcp\"}`)\n log(\"🔌 Connect with StreamableHTTPClientTransport\")\n }\n\n // Handle cleanup on exit\n process.on(\"SIGINT\", async () => {\n log(\"Received SIGINT, closing server...\")\n await duckpond.close()\n process.exit(0)\n })\n\n process.on(\"SIGTERM\", async () => {\n log(\"Received SIGTERM, closing server...\")\n await duckpond.close()\n process.exit(0)\n })\n}\n"],"mappings":"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","names":["webcrypto","FastMCP","createHash","randomBytes","createRequire","jwt","URL","webcrypto","require","createRequire","packageJson","log","loggers","JWT_SECRET","randomBytes","JWT_EXPIRES_IN","authorizationCodes","refreshTokens","createFastMCPServer","options","duckpond","DuckPondServer","baseConfig","server","FastMCP","request","authHeader","baseUrl","credentials","username","password","token","decoded","expectedAudience","error","querySchema","args","userId","resolveUserId","result","errorMessage","executeSchema","getUserStatsSchema","isAttachedSchema","detachUserSchema","listUsersSchema","setupOAuthEndpoints","app","c","serverInfo","setupUIEndpoints","now","code","data","params","responseType","redirectUri","state","codeChallenge","codeChallengeMethod","clientId","loginForm","body","redirectUrl","URL","grantType","codeVerifier","refreshTokenParam","tokenData","accessTokenPayload","newRefreshToken","accessToken","codeData","expectedChallenge","createHash","refreshToken","registrationRequest","parseError","clientSecret","response","uiInternalPort","listResult","path","proxyUrl","url","headers","value","key","responseHeaders","startServer","transport","initResult","startUIServer"]}
@@ -1,2 +0,0 @@
1
- import{a as n,b as a}from"./chunk-A3S6D44B.js";import{DuckPond as c}from"duckpond";var s=a.core,u=class{constructor(t){this.config=t;n(this,"pond",null);n(this,"initialized",!1);n(this,"currentUIUserId",null);n(this,"uiInternalPort",4213);s("DuckPondServer created")}async init(){if(this.initialized)return s("Already initialized"),{success:!0,data:void 0};s("Initializing DuckPond...");let t=Date.now();this.pond=new c(this.config);let e=await this.pond.init();return this.handleEither(e,Date.now()-t)}async query(t,e){if(!this.pond)return this.notInitializedError();s(`Query for user ${t}: ${e.substring(0,100)}...`);let r=Date.now(),i=await this.pond.query(t,e);return this.handleEither(i,Date.now()-r)}async execute(t,e){if(!this.pond)return this.notInitializedError();s(`Execute for user ${t}: ${e.substring(0,100)}...`);let r=Date.now(),i=await this.pond.execute(t,e);return this.handleEither(i,Date.now()-r)}async getUserStats(t){if(!this.pond)return this.notInitializedError();s(`Getting stats for user ${t}`);let e=Date.now(),r=await this.pond.getUserStats(t);return this.handleEither(r,Date.now()-e)}isAttached(t){if(!this.pond)return this.notInitializedError();let e=this.pond.isAttached(t);return s(`User ${t} attached: ${e}`),{success:!0,data:e}}async detachUser(t){if(!this.pond)return this.notInitializedError();s(`Detaching user ${t}`);let e=Date.now(),r=await this.pond.detachUser(t);return this.handleEither(r,Date.now()-e)}listUsers(){if(!this.pond)return this.notInitializedError();s("Listing cached users");let t=this.pond.listUsers();return{success:!0,data:{users:t.users.toArray(),count:t.count,maxActiveUsers:t.maxActiveUsers,utilizationPercent:t.utilizationPercent}}}async close(){if(!this.pond)return{success:!0,data:void 0};s("Closing DuckPond...");let t=Date.now();this.currentUIUserId&&await this.stopUI();let e=await this.pond.close();return this.initialized=!1,this.pond=null,this.handleEither(e,Date.now()-t)}async startUI(t){if(!this.pond)return this.notInitializedError();s(`Starting UI for user ${t}`);let e=Date.now();if(this.currentUIUserId&&this.currentUIUserId!==t&&(s(`Stopping UI for previous user ${this.currentUIUserId}`),await this.stopUI()),this.currentUIUserId===t)return s(`UI already running for user ${t}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-e};let r=await this.execute(t,"INSTALL ui; LOAD ui;");if(!r.success)return s(`Failed to install UI extension: ${r.error.message}`),r;let i=await this.execute(t,`SET ui_local_port = ${this.uiInternalPort}`);if(!i.success)return s(`Failed to set UI port: ${i.error.message}`),i;let o=await this.execute(t,"CALL start_ui_server()");return o.success?(this.currentUIUserId=t,s(`UI started for user ${t} on port ${this.uiInternalPort}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-e}):(s(`Failed to start UI server: ${o.error.message}`),o)}async stopUI(){if(!this.currentUIUserId)return s("No UI running to stop"),{success:!0,data:void 0};if(!this.pond)return this.currentUIUserId=null,{success:!0,data:void 0};s(`Stopping UI for user ${this.currentUIUserId}`);let t=Date.now(),e=await this.execute(this.currentUIUserId,"CALL stop_ui_server()"),r=this.currentUIUserId;return this.currentUIUserId=null,e.success?(s(`UI stopped for user ${r}`),{success:!0,data:void 0,executionTime:Date.now()-t}):(s(`Failed to stop UI: ${e.error.message}`),e)}getCurrentUIUser(){return this.currentUIUserId}setUIPort(t){this.uiInternalPort=t}getUIPort(){return this.uiInternalPort}handleEither(t,e){return t.fold(r=>({success:!1,error:{code:this.mapErrorCode(r.code),message:r.message,details:{originalCode:r.code,context:r.context,cause:r.cause?.message}}}),r=>({success:!0,data:r,executionTime:e}))}mapErrorCode(t){return{CONNECTION_FAILED:"SERVICE_UNAVAILABLE",CONNECTION_TIMEOUT:"TIMEOUT",R2_CONNECTION_ERROR:"SERVICE_UNAVAILABLE",S3_CONNECTION_ERROR:"SERVICE_UNAVAILABLE",USER_NOT_FOUND:"NOT_FOUND",USER_ALREADY_EXISTS:"ALREADY_EXISTS",USER_NOT_ATTACHED:"NOT_FOUND",QUERY_EXECUTION_ERROR:"INVALID_REQUEST",QUERY_TIMEOUT:"TIMEOUT",INVALID_SQL:"INVALID_REQUEST",MEMORY_LIMIT_EXCEEDED:"RESOURCE_EXHAUSTED",STORAGE_ERROR:"SERVICE_UNAVAILABLE",STORAGE_QUOTA_EXCEEDED:"RESOURCE_EXHAUSTED",INVALID_CONFIG:"INVALID_ARGUMENT",NOT_INITIALIZED:"FAILED_PRECONDITION",UNKNOWN_ERROR:"INTERNAL_ERROR"}[t]||"INTERNAL_ERROR"}notInitializedError(){return{success:!1,error:{code:"FAILED_PRECONDITION",message:"DuckPond not initialized. Call init() first."}}}};export{u as a};
2
- //# sourceMappingURL=chunk-R65UMTEX.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server-core.ts"],"sourcesContent":["import type { Either } from \"duckpond\"\nimport { DuckPond, type DuckPondConfig, type ErrorCode, type ListUsersResult, type UserStats } from \"duckpond\"\n\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.core\n\n/**\n * Result type for MCP tool responses\n */\nexport type MCPResult<T> =\n | {\n success: true\n data: T\n executionTime?: number\n }\n | {\n success: false\n error: {\n code: string\n message: string\n details?: {\n originalCode?: ErrorCode\n context?: Record<string, unknown>\n cause?: string\n }\n }\n }\n\n/**\n * Configuration for DuckPond MCP Server\n * Currently identical to DuckPondConfig, but can be extended with server-specific options\n */\nexport type DuckPondServerConfig = DuckPondConfig\n\n/**\n * Core DuckPond MCP Server\n *\n * Wraps DuckPond library with MCP-compatible result types\n */\nexport class DuckPondServer {\n private pond: DuckPond | null = null\n private initialized = false\n private currentUIUserId: string | null = null\n private uiInternalPort: number = 4213\n\n constructor(private config: DuckPondServerConfig) {\n log(\"DuckPondServer created\")\n }\n\n /**\n * Initialize the DuckPond instance\n */\n async init(): Promise<MCPResult<void>> {\n if (this.initialized) {\n log(\"Already initialized\")\n return { success: true, data: undefined }\n }\n\n log(\"Initializing DuckPond...\")\n const startTime = Date.now()\n\n this.pond = new DuckPond(this.config)\n const result = await this.pond.init()\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute a SQL query for a user\n */\n async query<T = unknown>(userId: string, sql: string): Promise<MCPResult<T[]>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Query for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.query<T>(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute DDL/DML without returning results\n */\n async execute(userId: string, sql: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Execute for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.execute(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Get statistics about a user's database\n */\n async getUserStats(userId: string): Promise<MCPResult<UserStats>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Getting stats for user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.getUserStats(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Check if a user is currently cached\n */\n isAttached(userId: string): MCPResult<boolean> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n const attached = this.pond.isAttached(userId)\n log(`User ${userId} attached: ${attached}`)\n\n return {\n success: true,\n data: attached,\n }\n }\n\n /**\n * Manually detach a user from the cache\n */\n async detachUser(userId: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Detaching user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.detachUser(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * List all currently cached users\n */\n listUsers(): MCPResult<{ users: string[]; count: number; maxActiveUsers: number; utilizationPercent: number }> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(\"Listing cached users\")\n const result = this.pond.listUsers()\n\n return {\n success: true,\n data: {\n users: result.users.toArray(), // Convert List<string> to string[]\n count: result.count,\n maxActiveUsers: result.maxActiveUsers,\n utilizationPercent: result.utilizationPercent,\n },\n }\n }\n\n /**\n * Close the DuckPond instance\n */\n async close(): Promise<MCPResult<void>> {\n if (!this.pond) {\n return { success: true, data: undefined }\n }\n\n log(\"Closing DuckPond...\")\n const startTime = Date.now()\n\n // Stop UI server if running\n if (this.currentUIUserId) {\n await this.stopUI()\n }\n\n const result = await this.pond.close()\n this.initialized = false\n this.pond = null\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Start DuckDB UI for a specific user\n * Only one user's UI can be active at a time\n */\n async startUI(userId: string): Promise<MCPResult<{ port: number }>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Starting UI for user ${userId}`)\n const startTime = Date.now()\n\n // Stop existing UI if different user\n if (this.currentUIUserId && this.currentUIUserId !== userId) {\n log(`Stopping UI for previous user ${this.currentUIUserId}`)\n await this.stopUI()\n }\n\n // If already running for this user, just return success\n if (this.currentUIUserId === userId) {\n log(`UI already running for user ${userId}`)\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n // Install and load UI extension\n const installResult = await this.execute(userId, \"INSTALL ui; LOAD ui;\")\n if (!installResult.success) {\n log(`Failed to install UI extension: ${installResult.error.message}`)\n return installResult as MCPResult<{ port: number }>\n }\n\n // Set the UI port\n const portResult = await this.execute(userId, `SET ui_local_port = ${this.uiInternalPort}`)\n if (!portResult.success) {\n log(`Failed to set UI port: ${portResult.error.message}`)\n return portResult as MCPResult<{ port: number }>\n }\n\n // Start the UI server (without opening browser)\n const startResult = await this.execute(userId, \"CALL start_ui_server()\")\n if (!startResult.success) {\n log(`Failed to start UI server: ${startResult.error.message}`)\n return startResult as MCPResult<{ port: number }>\n }\n\n this.currentUIUserId = userId\n log(`UI started for user ${userId} on port ${this.uiInternalPort}`)\n\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n /**\n * Stop the currently running DuckDB UI\n */\n async stopUI(): Promise<MCPResult<void>> {\n if (!this.currentUIUserId) {\n log(\"No UI running to stop\")\n return { success: true, data: undefined }\n }\n\n if (!this.pond) {\n this.currentUIUserId = null\n return { success: true, data: undefined }\n }\n\n log(`Stopping UI for user ${this.currentUIUserId}`)\n const startTime = Date.now()\n\n const result = await this.execute(this.currentUIUserId, \"CALL stop_ui_server()\")\n const previousUser = this.currentUIUserId\n this.currentUIUserId = null\n\n if (result.success) {\n log(`UI stopped for user ${previousUser}`)\n return {\n success: true,\n data: undefined,\n executionTime: Date.now() - startTime,\n }\n }\n\n log(`Failed to stop UI: ${result.error.message}`)\n return result\n }\n\n /**\n * Get the user ID for the currently running UI\n */\n getCurrentUIUser(): string | null {\n return this.currentUIUserId\n }\n\n /**\n * Set the internal port for DuckDB UI\n */\n setUIPort(port: number): void {\n this.uiInternalPort = port\n }\n\n /**\n * Get the internal port for DuckDB UI\n */\n getUIPort(): number {\n return this.uiInternalPort\n }\n\n /**\n * Convert Either<Error, T> to MCPResult<T>\n */\n private handleEither<T>(\n result: Either<{ code: ErrorCode; message: string; cause?: Error; context?: Record<string, unknown> }, T>,\n executionTime: number,\n ): MCPResult<T> {\n return result.fold(\n (error) => ({\n success: false,\n error: {\n code: this.mapErrorCode(error.code),\n message: error.message,\n details: {\n originalCode: error.code,\n context: error.context,\n cause: error.cause?.message,\n },\n },\n }),\n (data) => ({\n success: true,\n data,\n executionTime,\n }),\n )\n }\n\n /**\n * Map DuckPond ErrorCode to MCP error code\n */\n private mapErrorCode(code: ErrorCode): string {\n const mapping: Record<ErrorCode, string> = {\n CONNECTION_FAILED: \"SERVICE_UNAVAILABLE\",\n CONNECTION_TIMEOUT: \"TIMEOUT\",\n R2_CONNECTION_ERROR: \"SERVICE_UNAVAILABLE\",\n S3_CONNECTION_ERROR: \"SERVICE_UNAVAILABLE\",\n USER_NOT_FOUND: \"NOT_FOUND\",\n USER_ALREADY_EXISTS: \"ALREADY_EXISTS\",\n USER_NOT_ATTACHED: \"NOT_FOUND\",\n QUERY_EXECUTION_ERROR: \"INVALID_REQUEST\",\n QUERY_TIMEOUT: \"TIMEOUT\",\n INVALID_SQL: \"INVALID_REQUEST\",\n MEMORY_LIMIT_EXCEEDED: \"RESOURCE_EXHAUSTED\",\n STORAGE_ERROR: \"SERVICE_UNAVAILABLE\",\n STORAGE_QUOTA_EXCEEDED: \"RESOURCE_EXHAUSTED\",\n INVALID_CONFIG: \"INVALID_ARGUMENT\",\n NOT_INITIALIZED: \"FAILED_PRECONDITION\",\n UNKNOWN_ERROR: \"INTERNAL_ERROR\",\n }\n\n return mapping[code] || \"INTERNAL_ERROR\"\n }\n\n /**\n * Helper for not initialized error\n */\n private notInitializedError<T>(): MCPResult<T> {\n return {\n success: false,\n error: {\n code: \"FAILED_PRECONDITION\",\n message: \"DuckPond not initialized. Call init() first.\",\n },\n }\n }\n}\n"],"mappings":"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","names":["DuckPond","log","loggers","DuckPondServer","config","__publicField","startTime","DuckPond","result","userId","sql","attached","installResult","portResult","startResult","previousUser","port","executionTime","error","data","code"]}
@@ -1,2 +0,0 @@
1
- import{b as I}from"./chunk-A3S6D44B.js";import{serve as g}from"@hono/node-server";import{Hono as m}from"hono";var U=I.core;async function D(f){let{port:i,duckpond:n}=f,p=n.getUIPort(),c=new m;c.get("/ui",e=>{if(!n.getCurrentUIUser()){let t=n.listUsers();return e.json({message:"No UI active. Visit /ui/:userId to start DuckDB UI for a user.",currentUser:null,availableUsers:t.success?t.data.users:[],endpoints:{startUI:`http://localhost:${i}/ui/:userId`}})}return e.redirect("/ui/")}),c.get("/ui/:userId",async e=>{let a=e.req.param("userId");U(`[UI Server] Starting UI for user: ${a}`);let t=await n.startUI(a);return t.success?e.redirect("/ui/"):e.json({error:"Failed to start UI",message:t.error.message,details:t.error.details},500)}),c.all("/ui/*",async e=>{if(!n.getCurrentUIUser())return e.json({error:"No UI active",message:"Start UI by visiting /ui/:userId first"},400);let t=e.req.path.replace(/^\/ui/,"")||"/";try{let s=`http://localhost:${p}${t}`,l=new URL(e.req.url),d=new Headers;e.req.raw.headers.forEach((u,r)=>{r.toLowerCase()!=="host"&&d.set(r,u)});let o=await fetch(s+l.search,{method:e.req.method,headers:d,body:["GET","HEAD"].includes(e.req.method)?void 0:await e.req.arrayBuffer()}),h=new Headers;return o.headers.forEach((u,r)=>{["transfer-encoding","connection"].includes(r.toLowerCase())||h.set(r,u)}),new Response(o.body,{status:o.status,statusText:o.statusText,headers:h})}catch(s){return U(`[UI Server] Proxy error: ${s instanceof Error?s.message:String(s)}`),e.json({error:"UI proxy error",message:s instanceof Error?s.message:"Failed to connect to DuckDB UI",hint:"The DuckDB UI server may not be running. Try visiting /ui/:userId to restart it."},502)}}),c.get("/",e=>{let a=n.getCurrentUIUser();return e.json({name:"DuckPond UI Server",description:"Lightweight HTTP server for DuckDB UI access in stdio mode",currentUIUser:a,endpoints:{ui:`http://localhost:${i}/ui/:userId`,uiRoot:`http://localhost:${i}/ui/`}})}),c.all("*",async e=>{if(!n.getCurrentUIUser())return e.notFound();let t=e.req.path;try{let s=`http://localhost:${p}${t}`,l=new URL(e.req.url),d=new Headers;e.req.raw.headers.forEach((u,r)=>{r.toLowerCase()!=="host"&&d.set(r,u)});let o=await fetch(s+l.search,{method:e.req.method,headers:d,body:["GET","HEAD"].includes(e.req.method)?void 0:await e.req.arrayBuffer()}),h=new Headers;return o.headers.forEach((u,r)=>{["transfer-encoding","connection"].includes(r.toLowerCase())||h.set(r,u)}),new Response(o.body,{status:o.status,statusText:o.statusText,headers:h})}catch{return e.notFound()}}),g({fetch:c.fetch,port:i}),U(`\u2713 UI server running at http://localhost:${i}/ui`),U(` Visit http://localhost:${i}/ui/:userId to start DuckDB UI`)}export{D as a};
2
- //# sourceMappingURL=chunk-VSQCOL7H.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui-server.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\"\nimport { Hono } from \"hono\"\n\nimport type { DuckPondServer } from \"./server-core\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.core\n\nexport type UIServerOptions = {\n port: number\n duckpond: DuckPondServer\n}\n\n/**\n * Start a lightweight HTTP server for UI access in stdio mode\n * This allows accessing DuckDB UI even when MCP is running over stdio\n */\nexport async function startUIServer(options: UIServerOptions): Promise<void> {\n const { port, duckpond } = options\n const uiInternalPort = duckpond.getUIPort()\n\n const app = new Hono()\n\n // GET /ui - Info endpoint when no UI running, or redirect to UI\n app.get(\"/ui\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n const listResult = duckpond.listUsers()\n return c.json({\n message: \"No UI active. Visit /ui/:userId to start DuckDB UI for a user.\",\n currentUser: null,\n availableUsers: listResult.success ? listResult.data.users : [],\n endpoints: {\n startUI: `http://localhost:${port}/ui/:userId`,\n },\n })\n }\n // Redirect to the UI root\n return c.redirect(\"/ui/\")\n })\n\n // GET /ui/:userId - Start UI for a specific user and redirect\n app.get(\"/ui/:userId\", async (c) => {\n const userId = c.req.param(\"userId\")\n\n log(`[UI Server] Starting UI for user: ${userId}`)\n const result = await duckpond.startUI(userId)\n\n if (!result.success) {\n return c.json(\n {\n error: \"Failed to start UI\",\n message: result.error.message,\n details: result.error.details,\n },\n 500,\n )\n }\n\n // Redirect to the UI\n return c.redirect(\"/ui/\")\n })\n\n // ALL /ui/* - Proxy requests to DuckDB UI server\n app.all(\"/ui/*\", async (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n return c.json(\n {\n error: \"No UI active\",\n message: \"Start UI by visiting /ui/:userId first\",\n },\n 400,\n )\n }\n\n // Get the path after /ui\n const path = c.req.path.replace(/^\\/ui/, \"\") || \"/\"\n\n try {\n // Build the proxy URL\n const proxyUrl = `http://localhost:${uiInternalPort}${path}`\n const url = new URL(c.req.url)\n\n // Prepare headers, filtering out host\n const headers = new Headers()\n c.req.raw.headers.forEach((value, key) => {\n if (key.toLowerCase() !== \"host\") {\n headers.set(key, value)\n }\n })\n\n // Make the proxy request\n const response = await fetch(proxyUrl + url.search, {\n method: c.req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(c.req.method) ? undefined : await c.req.arrayBuffer(),\n })\n\n // Return the proxied response\n const responseHeaders = new Headers()\n response.headers.forEach((value, key) => {\n // Don't forward certain headers\n if (![\"transfer-encoding\", \"connection\"].includes(key.toLowerCase())) {\n responseHeaders.set(key, value)\n }\n })\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n })\n } catch (error) {\n log(`[UI Server] Proxy error: ${error instanceof Error ? error.message : String(error)}`)\n return c.json(\n {\n error: \"UI proxy error\",\n message: error instanceof Error ? error.message : \"Failed to connect to DuckDB UI\",\n hint: \"The DuckDB UI server may not be running. Try visiting /ui/:userId to restart it.\",\n },\n 502,\n )\n }\n })\n\n // Root endpoint with info\n app.get(\"/\", (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n return c.json({\n name: \"DuckPond UI Server\",\n description: \"Lightweight HTTP server for DuckDB UI access in stdio mode\",\n currentUIUser: currentUser,\n endpoints: {\n ui: `http://localhost:${port}/ui/:userId`,\n uiRoot: `http://localhost:${port}/ui/`,\n },\n })\n })\n\n // Fallback: Proxy unknown paths to DuckDB UI (for assets like JS/CSS)\n // DuckDB UI uses <base href=\"/\"> so assets are requested at root\n app.all(\"*\", async (c) => {\n const currentUser = duckpond.getCurrentUIUser()\n if (!currentUser) {\n return c.notFound()\n }\n\n const path = c.req.path\n\n try {\n const proxyUrl = `http://localhost:${uiInternalPort}${path}`\n const url = new URL(c.req.url)\n\n const headers = new Headers()\n c.req.raw.headers.forEach((value, key) => {\n if (key.toLowerCase() !== \"host\") {\n headers.set(key, value)\n }\n })\n\n const response = await fetch(proxyUrl + url.search, {\n method: c.req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(c.req.method) ? undefined : await c.req.arrayBuffer(),\n })\n\n const responseHeaders = new Headers()\n response.headers.forEach((value, key) => {\n if (![\"transfer-encoding\", \"connection\"].includes(key.toLowerCase())) {\n responseHeaders.set(key, value)\n }\n })\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n })\n } catch {\n return c.notFound()\n }\n })\n\n // Start the server\n serve({\n fetch: app.fetch,\n port,\n })\n\n log(`✓ UI server running at http://localhost:${port}/ui`)\n log(` Visit http://localhost:${port}/ui/:userId to start DuckDB UI`)\n}\n"],"mappings":"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","names":["serve","Hono","log","loggers","startUIServer","options","port","duckpond","uiInternalPort","app","Hono","c","listResult","userId","result","path","proxyUrl","url","headers","value","key","response","responseHeaders","error","currentUser","serve"]}