duckpond-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Jordan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,497 @@
1
+ # DuckPond MCP Server
2
+
3
+ [![Node.js CI](https://github.com/jordanburke/duckpond-mcp-server/actions/workflows/node.js.yml/badge.svg)](https://github.com/jordanburke/duckpond-mcp-server/actions/workflows/node.js.yml)
4
+ [![CodeQL](https://github.com/jordanburke/duckpond-mcp-server/actions/workflows/codeql.yml/badge.svg)](https://github.com/jordanburke/duckpond-mcp-server/actions/workflows/codeql.yml)
5
+
6
+ **Model Context Protocol (MCP) server for multi-tenant DuckDB management with R2/S3 cloud storage.**
7
+
8
+ Built on top of the [duckpond](https://github.com/jordanburke/duckpond) library, this MCP server enables AI agents to manage per-user DuckDB databases with automatic cloud persistence.
9
+
10
+ ## Features
11
+
12
+ - 🦆 **Multi-Tenant DuckDB** - Isolated databases per user with LRU caching
13
+ - ☁️ **Cloud Storage** - Seamless R2/S3 integration for persistence
14
+ - 🔌 **Dual Transport** - stdio (Claude Desktop) and HTTP (server deployments)
15
+ - 🔐 **Authentication** - OAuth 2.0 and Basic Auth support for HTTP
16
+ - 🎯 **MCP Tools** - Query, execute, stats, cache management
17
+ - 📊 **Type Safe** - Full TypeScript with functype error handling
18
+
19
+ ## Quick Start
20
+
21
+ ### Installation
22
+
23
+ ```bash
24
+ # Global installation
25
+ npm install -g duckpond-mcp-server
26
+
27
+ # Or use directly with npx
28
+ npx duckpond-mcp-server
29
+ ```
30
+
31
+ ### Claude Desktop Setup (stdio)
32
+
33
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "duckpond": {
39
+ "command": "npx",
40
+ "args": ["-y", "duckpond-mcp-server"],
41
+ "env": {
42
+ "DUCKPOND_R2_ACCOUNT_ID": "your-account-id",
43
+ "DUCKPOND_R2_ACCESS_KEY_ID": "your-access-key",
44
+ "DUCKPOND_R2_SECRET_ACCESS_KEY": "your-secret-key",
45
+ "DUCKPOND_R2_BUCKET": "your-bucket"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### HTTP Server
53
+
54
+ ```bash
55
+ # Start HTTP server on port 3000
56
+ npx duckpond-mcp-server --transport http
57
+
58
+ # With custom port
59
+ npx duckpond-mcp-server --transport http --port 8080
60
+ ```
61
+
62
+ ## Available MCP Tools
63
+
64
+ ### `query`
65
+
66
+ Execute a SQL query for a specific user and return results.
67
+
68
+ **Input:**
69
+
70
+ ```typescript
71
+ {
72
+ userId: string // User identifier
73
+ sql: string // SQL query to execute
74
+ }
75
+ ```
76
+
77
+ **Output:**
78
+
79
+ ```typescript
80
+ {
81
+ rows: T[] // Query results
82
+ rowCount: number // Number of rows
83
+ executionTime: number // Execution time in ms
84
+ }
85
+ ```
86
+
87
+ ### `execute`
88
+
89
+ Execute DDL/DML statements (CREATE, INSERT, UPDATE, DELETE) without returning results.
90
+
91
+ **Input:**
92
+
93
+ ```typescript
94
+ {
95
+ userId: string // User identifier
96
+ sql: string // SQL statement to execute
97
+ }
98
+ ```
99
+
100
+ **Output:**
101
+
102
+ ```typescript
103
+ {
104
+ success: boolean
105
+ message: string
106
+ executionTime: number
107
+ }
108
+ ```
109
+
110
+ ### `getUserStats`
111
+
112
+ Get statistics about a user's database.
113
+
114
+ **Input:**
115
+
116
+ ```typescript
117
+ {
118
+ userId: string // User identifier
119
+ }
120
+ ```
121
+
122
+ **Output:**
123
+
124
+ ```typescript
125
+ {
126
+ userId: string
127
+ attached: boolean // Is user currently cached?
128
+ lastAccess: string // ISO 8601 timestamp
129
+ memoryUsage: number // Bytes
130
+ storageUsage: number // Bytes
131
+ queryCount: number
132
+ }
133
+ ```
134
+
135
+ ### `isAttached`
136
+
137
+ Check if a user's database is currently cached in memory.
138
+
139
+ **Input:**
140
+
141
+ ```typescript
142
+ {
143
+ userId: string // User identifier
144
+ }
145
+ ```
146
+
147
+ **Output:**
148
+
149
+ ```typescript
150
+ {
151
+ attached: boolean
152
+ userId: string
153
+ }
154
+ ```
155
+
156
+ ### `detachUser`
157
+
158
+ Manually detach a user's database from the cache to free resources.
159
+
160
+ **Input:**
161
+
162
+ ```typescript
163
+ {
164
+ userId: string // User identifier
165
+ }
166
+ ```
167
+
168
+ **Output:**
169
+
170
+ ```typescript
171
+ {
172
+ success: boolean
173
+ message: string
174
+ }
175
+ ```
176
+
177
+ ## Configuration
178
+
179
+ ### Environment Variables
180
+
181
+ #### DuckDB Settings
182
+
183
+ - `DUCKPOND_MEMORY_LIMIT` - Memory limit (default: `4GB`)
184
+ - `DUCKPOND_THREADS` - Number of threads (default: `4`)
185
+ - `DUCKPOND_CACHE_TYPE` - Cache type: `disk`, `memory`, `noop` (default: `disk`)
186
+
187
+ #### Multi-Tenant Settings
188
+
189
+ - `DUCKPOND_MAX_ACTIVE_USERS` - LRU cache size (default: `10`)
190
+ - `DUCKPOND_EVICTION_TIMEOUT` - Idle timeout in ms (default: `300000`)
191
+ - `DUCKPOND_STRATEGY` - Storage strategy: `parquet`, `duckdb`, `hybrid` (default: `parquet`)
192
+
193
+ #### Cloudflare R2 Configuration
194
+
195
+ - `DUCKPOND_R2_ACCOUNT_ID` - R2 account ID
196
+ - `DUCKPOND_R2_ACCESS_KEY_ID` - R2 access key
197
+ - `DUCKPOND_R2_SECRET_ACCESS_KEY` - R2 secret key
198
+ - `DUCKPOND_R2_BUCKET` - R2 bucket name
199
+
200
+ #### AWS S3 Configuration
201
+
202
+ - `DUCKPOND_S3_REGION` - S3 region (e.g., `us-east-1`)
203
+ - `DUCKPOND_S3_ACCESS_KEY_ID` - S3 access key
204
+ - `DUCKPOND_S3_SECRET_ACCESS_KEY` - S3 secret key
205
+ - `DUCKPOND_S3_BUCKET` - S3 bucket name
206
+ - `DUCKPOND_S3_ENDPOINT` - Custom S3 endpoint (for MinIO, etc.)
207
+
208
+ ### HTTP Transport Authentication
209
+
210
+ #### OAuth 2.0
211
+
212
+ ```bash
213
+ export DUCKPOND_OAUTH_ENABLED=true
214
+ export DUCKPOND_OAUTH_USERNAME=admin
215
+ export DUCKPOND_OAUTH_PASSWORD=secret123
216
+ export DUCKPOND_OAUTH_USER_ID=admin-user
217
+ export DUCKPOND_OAUTH_EMAIL=admin@example.com
218
+
219
+ npx duckpond-mcp-server --transport http
220
+ ```
221
+
222
+ **OAuth Endpoints:**
223
+
224
+ - `/oauth/authorize` - Authorization endpoint (login form)
225
+ - `/oauth/token` - Token endpoint (authorization_code & refresh_token)
226
+ - `/oauth/jwks` - JSON Web Key Set
227
+ - `/oauth/register` - Dynamic client registration
228
+
229
+ **Features:**
230
+
231
+ - Authorization code flow with PKCE (S256 & plain)
232
+ - Refresh token rotation
233
+ - JWT access tokens (configurable expiration)
234
+
235
+ #### Basic Authentication
236
+
237
+ ```bash
238
+ export DUCKPOND_BASIC_AUTH_USERNAME=admin
239
+ export DUCKPOND_BASIC_AUTH_PASSWORD=secret123
240
+ export DUCKPOND_BASIC_AUTH_USER_ID=admin-user
241
+ export DUCKPOND_BASIC_AUTH_EMAIL=admin@example.com
242
+
243
+ npx duckpond-mcp-server --transport http
244
+ ```
245
+
246
+ #### JWT Configuration
247
+
248
+ - `DUCKPOND_JWT_SECRET` - Secret for signing JWTs (auto-generated if not set)
249
+ - `DUCKPOND_JWT_EXPIRES_IN` - Token expiration in seconds (default: `31536000` = 1 year)
250
+
251
+ ## HTTP Endpoints
252
+
253
+ ### MCP Protocol
254
+
255
+ - `POST /mcp` - MCP protocol endpoint (Server-Sent Events)
256
+ - Requires: `Accept: application/json, text/event-stream`
257
+ - Initialize session, then call tools
258
+
259
+ ### Server Information
260
+
261
+ - `GET /` - Server info and capabilities
262
+ - `GET /health` - Health check
263
+
264
+ ### OAuth (when enabled)
265
+
266
+ - `GET /oauth/authorize` - Authorization endpoint
267
+ - `POST /oauth/token` - Token endpoint
268
+ - `GET /oauth/jwks` - JSON Web Key Set
269
+ - `POST /oauth/register` - Client registration
270
+
271
+ ## Development
272
+
273
+ ### Local Development
274
+
275
+ ```bash
276
+ # Clone repository
277
+ git clone https://github.com/jordanburke/duckpond-mcp-server.git
278
+ cd duckpond-mcp-server
279
+
280
+ # Install dependencies
281
+ pnpm install
282
+
283
+ # Development mode (watch)
284
+ pnpm dev
285
+
286
+ # Run tests
287
+ pnpm test
288
+
289
+ # Format and lint
290
+ pnpm validate
291
+ ```
292
+
293
+ ### Testing the Server
294
+
295
+ ```bash
296
+ # Test stdio transport
297
+ pnpm serve:test
298
+
299
+ # Test HTTP transport
300
+ pnpm serve:test:http
301
+
302
+ # Test with OAuth
303
+ DUCKPOND_OAUTH_ENABLED=true \
304
+ DUCKPOND_OAUTH_USERNAME=admin \
305
+ DUCKPOND_OAUTH_PASSWORD=secret \
306
+ pnpm serve:test:http
307
+
308
+ # Test with Basic Auth
309
+ DUCKPOND_BASIC_AUTH_USERNAME=admin \
310
+ DUCKPOND_BASIC_AUTH_PASSWORD=secret \
311
+ pnpm serve:test:http
312
+ ```
313
+
314
+ ### Development Commands
315
+
316
+ ```bash
317
+ # Pre-checkin validation
318
+ pnpm validate # format + lint + test + build
319
+
320
+ # Individual commands
321
+ pnpm format # Format with Prettier
322
+ pnpm lint # Fix ESLint issues
323
+ pnpm test # Run tests
324
+ pnpm test:watch # Run tests in watch mode
325
+ pnpm test:coverage # Run tests with coverage
326
+ pnpm build # Production build
327
+ pnpm ts-types # Check TypeScript types
328
+ ```
329
+
330
+ ## Architecture
331
+
332
+ ### Library-First Design
333
+
334
+ The MCP server is a **thin transport layer** over the [duckpond](https://github.com/jordanburke/duckpond) library:
335
+
336
+ ```
337
+ ┌─────────────┐ ┌──────────────┐
338
+ │ stdio Mode │ │ HTTP Mode │
339
+ │ (index.ts) │ │(FastMCP/3000)│
340
+ └──────┬──────┘ └──────┬───────┘
341
+ │ │
342
+ └───────┬───────────┘
343
+
344
+ ┌───────▼────────┐
345
+ │ MCP Tool Layer │ (server-core.ts)
346
+ │ - Error mapping│
347
+ │ - Result format│
348
+ └───────┬────────┘
349
+
350
+ ┌───────▼────────┐
351
+ │ DuckPond │ npm: duckpond@^0.1.0
352
+ │ - Multi-tenant │
353
+ │ - LRU Cache │
354
+ │ - R2/S3 │
355
+ │ - Either<E,T> │
356
+ └───────┬────────┘
357
+
358
+ ┌───────▼────────┐
359
+ │ DuckDB + Cloud │
360
+ └────────────────┘
361
+ ```
362
+
363
+ ### Key Components
364
+
365
+ - **`src/index.ts`** - CLI entry point, transport selection
366
+ - **`src/server-core.ts`** - DuckPond wrapper with MCP result types
367
+ - **`src/server-stdio.ts`** - stdio transport for Claude Desktop
368
+ - **`src/server-fastmcp.ts`** - HTTP transport with FastMCP
369
+ - **`src/tools/index.ts`** - MCP tool schemas and implementations
370
+
371
+ ### Error Handling
372
+
373
+ Uses [functype](https://github.com/jordanburke/functype) for functional error handling:
374
+
375
+ ```typescript
376
+ // DuckPond returns Either<Error, T>
377
+ const result = await pond.query(userId, sql)
378
+
379
+ // MCP server converts to MCPResult<T>
380
+ result.fold(
381
+ (error) => ({ success: false, error: formatError(error) }),
382
+ (data) => ({ success: true, data }),
383
+ )
384
+ ```
385
+
386
+ ## Use Cases
387
+
388
+ ### Personal Analytics
389
+
390
+ Store per-user analytics data with automatic cloud backup:
391
+
392
+ ```typescript
393
+ // User creates their own tables
394
+ await execute({
395
+ userId: "user123",
396
+ sql: "CREATE TABLE orders (id INT, total DECIMAL, date DATE)",
397
+ })
398
+
399
+ // Query their data
400
+ const result = await query({
401
+ userId: "user123",
402
+ sql: "SELECT SUM(total) FROM orders WHERE date > '2024-01-01'",
403
+ })
404
+ ```
405
+
406
+ ### Multi-User Applications
407
+
408
+ - Each user gets isolated DuckDB instance
409
+ - Automatic LRU eviction manages memory
410
+ - Cloud storage persists user data
411
+ - Fast queries with DuckDB's columnar engine
412
+
413
+ ### Data Science Workflows
414
+
415
+ - Parquet file management
416
+ - Cloud data lake integration
417
+ - Complex analytical queries
418
+ - Per-user sandboxed environments
419
+
420
+ ## Troubleshooting
421
+
422
+ ### Server Won't Start
423
+
424
+ **Check DuckDB installation:**
425
+
426
+ ```bash
427
+ npm list duckdb
428
+ ```
429
+
430
+ **Verify environment variables:**
431
+
432
+ ```bash
433
+ printenv | grep DUCKPOND
434
+ ```
435
+
436
+ ### Authentication Issues
437
+
438
+ **OAuth not working:**
439
+
440
+ - Verify `DUCKPOND_OAUTH_USERNAME` and `DUCKPOND_OAUTH_PASSWORD` are set
441
+ - Check browser console for errors
442
+ - Ensure redirect URIs match
443
+
444
+ **Basic Auth failing:**
445
+
446
+ - Verify credentials are set correctly
447
+ - Check `Authorization: Basic <base64>` header format
448
+ - Ensure username/password match environment variables
449
+
450
+ ### Memory Issues
451
+
452
+ **Adjust memory limits:**
453
+
454
+ ```bash
455
+ export DUCKPOND_MEMORY_LIMIT=8GB
456
+ export DUCKPOND_MAX_ACTIVE_USERS=5
457
+ ```
458
+
459
+ **Monitor cache usage:**
460
+
461
+ ```typescript
462
+ const stats = await getUserStats({ userId: "user123" })
463
+ console.log(`Memory: ${stats.memoryUsage} bytes`)
464
+ ```
465
+
466
+ ### Storage Issues
467
+
468
+ **R2/S3 connection errors:**
469
+
470
+ - Verify credentials are correct
471
+ - Check bucket exists and is accessible
472
+ - Test with AWS CLI: `aws s3 ls s3://your-bucket`
473
+
474
+ **Parquet file issues:**
475
+
476
+ - Ensure DuckDB parquet extension is loaded
477
+ - Check file permissions in storage bucket
478
+
479
+ ## Contributing
480
+
481
+ Contributions welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
482
+
483
+ ## License
484
+
485
+ MIT
486
+
487
+ ## Related Projects
488
+
489
+ - **[duckpond](https://github.com/jordanburke/duckpond)** - Core multi-tenant DuckDB library
490
+ - **[functype](https://github.com/jordanburke/functype)** - Functional programming utilities for TypeScript
491
+ - **[Model Context Protocol](https://modelcontextprotocol.io)** - MCP specification
492
+
493
+ ## Support
494
+
495
+ - **Issues**: [GitHub Issues](https://github.com/jordanburke/duckpond-mcp-server/issues)
496
+ - **Discussions**: [GitHub Discussions](https://github.com/jordanburke/duckpond-mcp-server/discussions)
497
+ - **Documentation**: [docs/](./docs/)
@@ -0,0 +1,48 @@
1
+ import{a as R}from"./chunk-SU42EK5H.js";import{a as C,b as O,c as T,d as A,e as $,f as I}from"./chunk-43NWQIU3.js";import{b as x}from"./chunk-A3S6D44B.js";import{webcrypto as U}from"crypto";import{FastMCP as P}from"@jordanburke/fastmcp";import{createHash as z,randomBytes as f}from"crypto";import*as y from"jsonwebtoken";import{URL as D}from"url";globalThis.crypto||(globalThis.crypto=U);var u=x.fastmcp,v=process.env.DUCKPOND_JWT_SECRET||f(32).toString("hex"),S=process.env.DUCKPOND_JWT_EXPIRES_IN?parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN,10):365*24*60*60,_=new Map,m=new Map;function j(r){u("\u{1F680} Initializing FastMCP server...");let i=new R(r.config),h={name:"duckpond",version:"0.1.0",health:{enabled:!0,path:"/health",status:200,message:JSON.stringify({status:"healthy",service:"duckpond-mcp-server",version:"0.1.0",timestamp:new Date().toISOString()})}},s=r.oauth?.enabled||r.basicAuth?new P({...h,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:t=>{let e=t.headers?.authorization,a=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="${a}/oauth/authorize", resource="${a}/.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 c=Buffer.from(e.slice(6),"base64").toString("utf-8"),[n,d]=c.split(":");if(n===r.basicAuth.username&&d===r.basicAuth.password)return Promise.resolve({userId:r.basicAuth.userId||n,email:r.basicAuth.email||`${n}@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 c=e.slice(7);try{let n=y.verify(c,v);if(!n.sub||!n.iat||!n.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 d=r.oauth?.resource||`${a}/mcp`;if(n.aud&&n.aud!==d)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:n.sub,email:n.email||"",scope:n.scope||"read write"})}catch(n){throw n instanceof Response?n: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="${a}/oauth/authorize", resource="${a}/.well-known/oauth-protected-resource"`}})}}):new P(h);return s.addTool({name:"query",description:"Execute a SQL query for a specific user and return results",parameters:C,execute:async t=>{try{let e=await i.query(t.userId,t.sql);return e.success?JSON.stringify({rows:e.data,rowCount:e.data.length,executionTime:e.executionTime},null,2):`ERROR: ${e.error.message}`}catch(e){u("Error in query tool:",e);let a=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:a},null,2)}`}}}),s.addTool({name:"execute",description:"Execute SQL statement (DDL/DML) for a specific user without returning results",parameters:O,execute:async t=>{try{let e=await i.execute(t.userId,t.sql);return e.success?JSON.stringify({success:!0,message:"Statement executed successfully",executionTime:e.executionTime},null,2):`ERROR: ${e.error.message}`}catch(e){u("Error in execute tool:",e);let a=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:a},null,2)}`}}}),s.addTool({name:"getUserStats",description:"Get statistics about a user's database (memory usage, query count, etc.)",parameters:T,execute:async t=>{try{let e=await i.getUserStats(t.userId);return e.success?JSON.stringify({...e.data,lastAccess:e.data.lastAccess.toISOString()},null,2):`ERROR: ${e.error.message}`}catch(e){u("Error in getUserStats tool:",e);let a=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:a},null,2)}`}}}),s.addTool({name:"isAttached",description:"Check if a user's database is currently cached in memory",parameters:A,execute:async t=>{try{let e=i.isAttached(t.userId);return e.success?JSON.stringify({attached:e.data,userId:t.userId},null,2):`ERROR: ${e.error.message}`}catch(e){u("Error in isAttached tool:",e);let a=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:a},null,2)}`}}}),s.addTool({name:"detachUser",description:"Manually detach a user's database from the cache to free resources",parameters:$,execute:async t=>{try{let e=await i.detachUser(t.userId);return e.success?JSON.stringify({success:!0,message:`User ${t.userId} detached successfully`},null,2):`ERROR: ${e.error.message}`}catch(e){u("Error in detachUser tool:",e);let a=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:a},null,2)}`}}}),s.addTool({name:"listUsers",description:"List all currently cached users and cache statistics",parameters:I,execute:async()=>{try{let t=i.listUsers();return t.success?JSON.stringify(t.data,null,2):`ERROR: ${t.error.message}`}catch(t){u("Error in listUsers tool:",t);let e=t instanceof Error?t.message:String(t);return`ERROR: ${JSON.stringify({error:e},null,2)}`}}}),r.oauth?.enabled&&q(s,r),s.getApp().get("/",t=>{let e=r.oauth?.issuer||`http://localhost:${r.port||3e3}`,a={name:"DuckPond MCP Server",version:"0.1.0",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`,...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 t.json(a)}),u("\u2713 FastMCP server created"),{server:s,duckpond:i}}function q(r,i){let h=r.getApp();setInterval(()=>{let s=Date.now();for(let[o,t]of _.entries())s-t.createdAt>6e5&&_.delete(o);for(let[o,t]of m.entries())s-t.createdAt>2592e6&&m.delete(o)},6e4),h.get("/oauth/authorize",s=>{let o=s.req.query(),t=o.response_type,e=o.redirect_uri,a=o.state,c=o.code_challenge,n=o.code_challenge_method,d=o.client_id;if(t!=="code")return s.json({error:"unsupported_response_type",error_description:"Only 'code' response type is supported"},400);if(!e)return s.json({error:"invalid_request",error_description:"redirect_uri is required"},400);if(c&&(!n||!["S256","plain"].includes(n)))return s.json({error:"invalid_request",error_description:"Invalid code_challenge_method. Only 'S256' and 'plain' are supported"},400);let l=`
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> ${d||"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="${t}">
25
+ <input type="hidden" name="redirect_uri" value="${e}">
26
+ <input type="hidden" name="state" value="${a||""}">
27
+ <input type="hidden" name="code_challenge" value="${c||""}">
28
+ <input type="hidden" name="code_challenge_method" value="${n||""}">
29
+ <input type="hidden" name="client_id" value="${d||""}">
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 s.html(l)}),h.post("/oauth/authorize",async s=>{try{let o=await s.req.text(),t=new URLSearchParams(o),e=t.get("username"),a=t.get("password"),c=t.get("redirect_uri"),n=t.get("state"),d=t.get("code_challenge"),l=t.get("code_challenge_method");if(e!==i.oauth?.username||a!==i.oauth?.password)return s.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=f(16).toString("hex");_.set(w,{createdAt:Date.now(),redirectUri:c||"",codeChallenge:d||void 0,codeChallengeMethod:l||void 0,userId:i.oauth?.userId||e||"oauth-user"});let g=new D(c||"");return g.searchParams.set("code",w),n&&g.searchParams.set("state",n),s.redirect(g.toString(),302)}catch{return s.json({error:"invalid_request",error_description:"Failed to process authorization request"},400)}}),h.post("/oauth/token",async s=>{let o=await s.req.text(),t=new URLSearchParams(o),e=t.get("grant_type"),a=t.get("code"),c=t.get("redirect_uri"),n=t.get("code_verifier"),d=t.get("refresh_token");if(e==="refresh_token"){if(!d)return s.json({error:"invalid_request",error_description:"refresh_token is required for refresh_token grant type"},400);let p=m.get(d);if(!p)return s.json({error:"invalid_grant",error_description:"Invalid or expired refresh token"},400);m.delete(d);let E={sub:p.userId,email:p.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+S,iss:i.oauth?.issuer||`http://localhost:${i.port||3e3}`,aud:i.oauth?.resource||`${i.oauth?.issuer||`http://localhost:${i.port||3e3}`}/mcp`},k=f(32).toString("hex");m.set(k,{createdAt:Date.now(),userId:p.userId,email:p.email});let M=y.sign(E,v);return s.json({access_token:M,token_type:"Bearer",expires_in:S,scope:"read write",refresh_token:k})}if(e!=="authorization_code")return s.json({error:"unsupported_grant_type",error_description:"Only 'authorization_code' and 'refresh_token' grant types are supported"},400);let l=_.get(a||"");if(!l)return s.json({error:"invalid_grant",error_description:"Invalid or expired authorization code"},400);if(l.redirectUri&&l.redirectUri!==c)return s.json({error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(l.codeChallenge){if(!n)return s.json({error:"invalid_grant",error_description:"code_verifier is required when code_challenge was used"},400);let p;if(l.codeChallengeMethod==="S256"?p=z("sha256").update(n).digest().toString("base64url"):p=n,p!==l.codeChallenge)return s.json({error:"invalid_grant",error_description:"Invalid code_verifier"},400)}_.delete(a);let w={sub:l.userId,email:i.oauth?.email||"",scope:"read write",iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+S,iss:i.oauth?.issuer||`http://localhost:${i.port||3e3}`,aud:i.oauth?.resource||`${i.oauth?.issuer||`http://localhost:${i.port||3e3}`}/mcp`},g=f(32).toString("hex");m.set(g,{createdAt:Date.now(),userId:l.userId,email:i.oauth?.email});let b=y.sign(w,v);return s.json({access_token:b,token_type:"Bearer",expires_in:S,scope:"read write",refresh_token:g})}),h.get("/oauth/jwks",s=>s.json({keys:[{kty:"oct",use:"sig",kid:"duckpond-hmac-key",alg:"HS256"}]})),h.post("/oauth/register",async s=>{try{let o={};try{let c=await s.req.text();if(c&&c!=="[object Object]")try{o=JSON.parse(c)}catch{o=Object.fromEntries(new URLSearchParams(c))}}catch(c){u("Error parsing request body:",c)}let t=`client-${f(8).toString("hex")}`,e=f(16).toString("hex"),a={client_id:t,client_secret:e,client_id_issued_at:Math.floor(Date.now()/1e3),client_secret_expires_at:0,grant_types:o.grant_types||["authorization_code"],response_types:o.response_types||["code"],redirect_uris:o.redirect_uris||[],token_endpoint_auth_method:o.token_endpoint_auth_method||"client_secret_post"};return o.client_name&&(a.client_name=o.client_name),o.scope&&(a.scope=o.scope),s.json(a,201)}catch(o){return s.json({error:"invalid_client_metadata",error_description:"Invalid client registration request: "+(o instanceof Error?o.message:String(o))},400)}}),u("\u2713 OAuth flow endpoints added")}async function H(r,i){let{server:h,duckpond:s}=j(r),o=await s.init();if(!o.success)throw new Error(`Failed to initialize DuckPond: ${o.error.message}`);u("DuckPond initialized successfully"),i==="stdio"?(await h.start({transportType:"stdio"}),u("\u2713 FastMCP server running with stdio transport")):(await h.start({transportType:"httpStream",httpStream:{port:r.port||3e3,endpoint:r.endpoint||"/mcp"}}),u(`\u2713 FastMCP server running on http://0.0.0.0:${r.port||3e3}${r.endpoint||"/mcp"}`),u("\u{1F50C} Connect with StreamableHTTPClientTransport")),process.on("SIGINT",async()=>{u("Received SIGINT, closing server..."),await s.close(),process.exit(0)}),process.on("SIGTERM",async()=>{u("Received SIGTERM, closing server..."),await s.close(),process.exit(0)})}export{j as a,H as b};
48
+ //# sourceMappingURL=chunk-3FXN77J7.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 * 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 getUserStatsSchema,\n isAttachedSchema,\n listUsersSchema,\n querySchema,\n} from \"./tools\"\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}\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: \"0.1.0\" as const,\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: \"0.1.0\",\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 result = await duckpond.query(args.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 result = await duckpond.execute(args.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 result = await duckpond.getUserStats(args.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 result = duckpond.isAttached(args.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: args.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 result = await duckpond.detachUser(args.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 ${args.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: \"0.1.0\",\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 ...(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 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}\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 // 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 } 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","jwt","URL","webcrypto","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","result","errorMessage","executeSchema","getUserStatsSchema","isAttachedSchema","detachUserSchema","listUsersSchema","setupOAuthEndpoints","c","serverInfo","app","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","startServer","transport","initResult"]}
@@ -0,0 +1,2 @@
1
+ import{b as o}from"./chunk-A3S6D44B.js";import{z as r}from"zod";var c=o.tools,u=r.object({userId:r.string().min(1).describe("User identifier"),sql:r.string().min(1).describe("SQL query to execute")}),a=r.object({userId:r.string().min(1).describe("User identifier"),sql:r.string().min(1).describe("SQL statement to execute (DDL/DML)")}),d=r.object({userId:r.string().min(1).describe("User identifier")}),l=r.object({userId:r.string().min(1).describe("User identifier")}),f=r.object({userId:r.string().min(1).describe("User identifier")}),m=r.object({}),S={async query(s,t){c(`Tool: query for user ${t.userId}`);let e=await s.query(t.userId,t.sql);if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify({rows:e.data,rowCount:e.data.length,executionTime:e.executionTime},null,2)}]}},async execute(s,t){c(`Tool: execute for user ${t.userId}`);let e=await s.execute(t.userId,t.sql);if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify({success:!0,message:"Statement executed successfully",executionTime:e.executionTime},null,2)}]}},async getUserStats(s,t){c(`Tool: getUserStats for user ${t.userId}`);let e=await s.getUserStats(t.userId);if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify({...e.data,lastAccess:e.data.lastAccess.toISOString()},null,2)}]}},isAttached(s,t){c(`Tool: isAttached for user ${t.userId}`);let e=s.isAttached(t.userId);if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify({attached:e.data,userId:t.userId},null,2)}]}},async detachUser(s,t){c(`Tool: detachUser for user ${t.userId}`);let e=await s.detachUser(t.userId);if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify({success:!0,message:`User ${t.userId} detached successfully`},null,2)}]}},listUsers(s,t){c("Tool: listUsers");let e=s.listUsers();if(!e.success)throw new Error(e.error.message);return{content:[{type:"text",text:JSON.stringify(e.data,null,2)}]}}};export{u as a,a as b,d as c,l as d,f as e,m as f,S as g};
2
+ //# sourceMappingURL=chunk-43NWQIU3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/index.ts"],"sourcesContent":["import { z } from \"zod\"\n\nimport type { DuckPondServer } from \"../server-core\"\nimport { loggers } from \"../utils/logger\"\n\nconst log = loggers.tools\n\n/**\n * Zod schema for query tool\n */\nexport const querySchema = z.object({\n userId: z.string().min(1).describe(\"User identifier\"),\n sql: z.string().min(1).describe(\"SQL query to execute\"),\n})\n\n/**\n * Zod schema for execute tool\n */\nexport const executeSchema = z.object({\n userId: z.string().min(1).describe(\"User identifier\"),\n sql: z.string().min(1).describe(\"SQL statement to execute (DDL/DML)\"),\n})\n\n/**\n * Zod schema for getUserStats tool\n */\nexport const getUserStatsSchema = z.object({\n userId: z.string().min(1).describe(\"User identifier\"),\n})\n\n/**\n * Zod schema for isAttached tool\n */\nexport const isAttachedSchema = z.object({\n userId: z.string().min(1).describe(\"User identifier\"),\n})\n\n/**\n * Zod schema for detachUser tool\n */\nexport const detachUserSchema = z.object({\n userId: z.string().min(1).describe(\"User identifier\"),\n})\n\n/**\n * Zod schema for listUsers tool\n */\nexport const listUsersSchema = z.object({})\n\n/**\n * Tool implementations for MCP server\n */\nexport const tools = {\n /**\n * Execute a SQL query for a user\n */\n async query(server: DuckPondServer, input: z.infer<typeof querySchema>) {\n log(`Tool: query for user ${input.userId}`)\n const result = await server.query(input.userId, input.sql)\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n rows: result.data,\n rowCount: result.data.length,\n executionTime: result.executionTime,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n\n /**\n * Execute DDL/DML statement\n */\n async execute(server: DuckPondServer, input: z.infer<typeof executeSchema>) {\n log(`Tool: execute for user ${input.userId}`)\n const result = await server.execute(input.userId, input.sql)\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n success: true,\n message: \"Statement executed successfully\",\n executionTime: result.executionTime,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n\n /**\n * Get user database statistics\n */\n async getUserStats(server: DuckPondServer, input: z.infer<typeof getUserStatsSchema>) {\n log(`Tool: getUserStats for user ${input.userId}`)\n const result = await server.getUserStats(input.userId)\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n ...result.data,\n lastAccess: result.data.lastAccess.toISOString(),\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n\n /**\n * Check if user is attached\n */\n isAttached(server: DuckPondServer, input: z.infer<typeof isAttachedSchema>) {\n log(`Tool: isAttached for user ${input.userId}`)\n const result = server.isAttached(input.userId)\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n attached: result.data,\n userId: input.userId,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n\n /**\n * Detach a user from cache\n */\n async detachUser(server: DuckPondServer, input: z.infer<typeof detachUserSchema>) {\n log(`Tool: detachUser for user ${input.userId}`)\n const result = await server.detachUser(input.userId)\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n success: true,\n message: `User ${input.userId} detached successfully`,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n\n /**\n * List all currently cached users\n */\n listUsers(server: DuckPondServer, _input: z.infer<typeof listUsersSchema>) {\n log(\"Tool: listUsers\")\n const result = server.listUsers()\n\n if (!result.success) {\n throw new Error(result.error.message)\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(result.data, null, 2),\n },\n ],\n }\n },\n}\n"],"mappings":"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","names":["z","log","loggers","querySchema","z","executeSchema","getUserStatsSchema","isAttachedSchema","detachUserSchema","listUsersSchema","tools","server","input","result","_input"]}
@@ -0,0 +1,2 @@
1
+ var t=Object.defineProperty;var n=(o,c,d)=>c in o?t(o,c,{enumerable:!0,configurable:!0,writable:!0,value:d}):o[c]=d;var m=(o,c,d)=>n(o,typeof c!="symbol"?c+"":c,d);import p from"debug";var s={core:p("duckpond-mcp:core"),stdio:p("duckpond-mcp:stdio"),fastmcp:p("duckpond-mcp:fastmcp"),tools:p("duckpond-mcp:tools"),main:p("duckpond-mcp:main")};function u(o){return p(`duckpond-mcp:${o}`)}export{m as a,s as b,u as c};
2
+ //# sourceMappingURL=chunk-A3S6D44B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/logger.ts"],"sourcesContent":["import debug from \"debug\"\n\n/**\n * Debug loggers for different modules\n */\nexport const loggers = {\n core: debug(\"duckpond-mcp:core\"),\n stdio: debug(\"duckpond-mcp:stdio\"),\n fastmcp: debug(\"duckpond-mcp:fastmcp\"),\n tools: debug(\"duckpond-mcp:tools\"),\n main: debug(\"duckpond-mcp:main\"),\n}\n\n/**\n * Create a custom logger\n */\nexport function createLogger(namespace: string) {\n return debug(`duckpond-mcp:${namespace}`)\n}\n"],"mappings":"oKAAA,OAAOA,MAAW,QAKX,IAAMC,EAAU,CACrB,KAAMD,EAAM,mBAAmB,EAC/B,MAAOA,EAAM,oBAAoB,EACjC,QAASA,EAAM,sBAAsB,EACrC,MAAOA,EAAM,oBAAoB,EACjC,KAAMA,EAAM,mBAAmB,CACjC,EAKO,SAASE,EAAaC,EAAmB,CAC9C,OAAOH,EAAM,gBAAgBG,CAAS,EAAE,CAC1C","names":["debug","loggers","createLogger","namespace"]}
@@ -0,0 +1,2 @@
1
+ import{a as n,b as o}from"./chunk-A3S6D44B.js";import{DuckPond as u}from"duckpond";var r=o.core,a=class{constructor(t){this.config=t;n(this,"pond",null);n(this,"initialized",!1);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 u(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(),e=await this.pond.close();return this.initialized=!1,this.pond=null,this.handleEither(e,Date.now()-t)}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{a};
2
+ //# sourceMappingURL=chunk-SU42EK5H.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 * 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\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 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 * 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","executionTime","error","data","code"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import{b as D}from"./chunk-3FXN77J7.js";import"./chunk-SU42EK5H.js";import"./chunk-43NWQIU3.js";import{b as a}from"./chunk-A3S6D44B.js";import{webcrypto as p}from"crypto";import{Command as _}from"commander";globalThis.crypto||(globalThis.crypto=p);var t=a.main;function C(){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"};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 i=new _;i.name("duckpond-mcp-server").description("MCP server for multi-tenant DuckDB management with R2/S3 storage").version("0.1.0").option("-t, --transport <type>","Transport mode: stdio or http","stdio").option("-p, --port <port>","HTTP port (when using http transport)","3000").action(async e=>{try{let r=C();t(`Starting DuckPond MCP Server with ${e.transport} transport`),t("Configuration:",{memoryLimit:r.memoryLimit,threads:r.threads,maxActiveUsers:r.maxActiveUsers,strategy:r.strategy,hasR2:!!r.r2,hasS3:!!r.s3});let o;if(process.env.DUCKPOND_OAUTH_ENABLED==="true"){let n=process.env.DUCKPOND_OAUTH_USERNAME,c=process.env.DUCKPOND_OAUTH_PASSWORD;(!n||!c)&&(console.error("\u274C OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required"),process.exit(1)),o={enabled:!0,username:n,password:c,userId:process.env.DUCKPOND_OAUTH_USER_ID||n,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}`)),e.transport==="stdio"||e.transport==="http"?await D({config:r,port:parseInt(e.port)||3e3,endpoint:"/mcp",oauth:o,basicAuth:s},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)}});i.parse();
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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\"\n\nimport type { OAuthConfig } from \"./server\"\nimport { startServer } from \"./server\"\nimport type { DuckPondServerConfig } from \"./server-core\"\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 }\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(\"0.1.0\")\n .option(\"-t, --transport <type>\", \"Transport mode: stdio or http\", \"stdio\")\n .option(\"-p, --port <port>\", \"HTTP port (when using http transport)\", \"3000\")\n .action(async (options) => {\n try {\n const config = getConfigFromEnv()\n\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 hasR2: !!config.r2,\n hasS3: !!config.s3,\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 // 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 },\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","webcrypto","log","loggers","getConfigFromEnv","config","program","Command","options","oauthConfig","username","password","basicAuthConfig","startServer","error"]}
@@ -0,0 +1,88 @@
1
+ import { ErrorCode, DuckPondConfig, UserStats } from 'duckpond';
2
+
3
+ /**
4
+ * Result type for MCP tool responses
5
+ */
6
+ type MCPResult<T> = {
7
+ success: true;
8
+ data: T;
9
+ executionTime?: number;
10
+ } | {
11
+ success: false;
12
+ error: {
13
+ code: string;
14
+ message: string;
15
+ details?: {
16
+ originalCode?: ErrorCode;
17
+ context?: Record<string, unknown>;
18
+ cause?: string;
19
+ };
20
+ };
21
+ };
22
+ /**
23
+ * Configuration for DuckPond MCP Server
24
+ * Currently identical to DuckPondConfig, but can be extended with server-specific options
25
+ */
26
+ type DuckPondServerConfig = DuckPondConfig;
27
+ /**
28
+ * Core DuckPond MCP Server
29
+ *
30
+ * Wraps DuckPond library with MCP-compatible result types
31
+ */
32
+ declare class DuckPondServer {
33
+ private config;
34
+ private pond;
35
+ private initialized;
36
+ constructor(config: DuckPondServerConfig);
37
+ /**
38
+ * Initialize the DuckPond instance
39
+ */
40
+ init(): Promise<MCPResult<void>>;
41
+ /**
42
+ * Execute a SQL query for a user
43
+ */
44
+ query<T = unknown>(userId: string, sql: string): Promise<MCPResult<T[]>>;
45
+ /**
46
+ * Execute DDL/DML without returning results
47
+ */
48
+ execute(userId: string, sql: string): Promise<MCPResult<void>>;
49
+ /**
50
+ * Get statistics about a user's database
51
+ */
52
+ getUserStats(userId: string): Promise<MCPResult<UserStats>>;
53
+ /**
54
+ * Check if a user is currently cached
55
+ */
56
+ isAttached(userId: string): MCPResult<boolean>;
57
+ /**
58
+ * Manually detach a user from the cache
59
+ */
60
+ detachUser(userId: string): Promise<MCPResult<void>>;
61
+ /**
62
+ * List all currently cached users
63
+ */
64
+ listUsers(): MCPResult<{
65
+ users: string[];
66
+ count: number;
67
+ maxActiveUsers: number;
68
+ utilizationPercent: number;
69
+ }>;
70
+ /**
71
+ * Close the DuckPond instance
72
+ */
73
+ close(): Promise<MCPResult<void>>;
74
+ /**
75
+ * Convert Either<Error, T> to MCPResult<T>
76
+ */
77
+ private handleEither;
78
+ /**
79
+ * Map DuckPond ErrorCode to MCP error code
80
+ */
81
+ private mapErrorCode;
82
+ /**
83
+ * Helper for not initialized error
84
+ */
85
+ private notInitializedError;
86
+ }
87
+
88
+ export { DuckPondServer, type DuckPondServerConfig, type MCPResult };
@@ -0,0 +1,2 @@
1
+ import{a}from"./chunk-SU42EK5H.js";import"./chunk-A3S6D44B.js";export{a as DuckPondServer};
2
+ //# sourceMappingURL=server-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,32 @@
1
+ import { FastMCP } from '@jordanburke/fastmcp';
2
+ import { DuckPondServerConfig, DuckPondServer } from './server-core.js';
3
+ import 'duckpond';
4
+
5
+ type OAuthConfig = {
6
+ enabled: boolean;
7
+ username: string;
8
+ password: string;
9
+ userId: string;
10
+ email?: string;
11
+ issuer?: string;
12
+ resource?: string;
13
+ };
14
+ type FastMCPServerOptions = {
15
+ config: DuckPondServerConfig;
16
+ port?: number;
17
+ endpoint?: string;
18
+ oauth?: OAuthConfig;
19
+ basicAuth?: {
20
+ username: string;
21
+ password: string;
22
+ userId?: string;
23
+ email?: string;
24
+ };
25
+ };
26
+ declare function createFastMCPServer(options: FastMCPServerOptions): {
27
+ server: FastMCP;
28
+ duckpond: DuckPondServer;
29
+ };
30
+ declare function startServer(options: FastMCPServerOptions, transport: "stdio" | "http"): Promise<void>;
31
+
32
+ export { type FastMCPServerOptions, type OAuthConfig, createFastMCPServer, startServer };
package/dist/server.js ADDED
@@ -0,0 +1,2 @@
1
+ import{a,b}from"./chunk-3FXN77J7.js";import"./chunk-SU42EK5H.js";import"./chunk-43NWQIU3.js";import"./chunk-A3S6D44B.js";export{a as createFastMCPServer,b as startServer};
2
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod';
2
+ import { DuckPondServer } from '../server-core.js';
3
+ import 'duckpond';
4
+
5
+ /**
6
+ * Zod schema for query tool
7
+ */
8
+ declare const querySchema: z.ZodObject<{
9
+ userId: z.ZodString;
10
+ sql: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ userId: string;
13
+ sql: string;
14
+ }, {
15
+ userId: string;
16
+ sql: string;
17
+ }>;
18
+ /**
19
+ * Zod schema for execute tool
20
+ */
21
+ declare const executeSchema: z.ZodObject<{
22
+ userId: z.ZodString;
23
+ sql: z.ZodString;
24
+ }, "strip", z.ZodTypeAny, {
25
+ userId: string;
26
+ sql: string;
27
+ }, {
28
+ userId: string;
29
+ sql: string;
30
+ }>;
31
+ /**
32
+ * Zod schema for getUserStats tool
33
+ */
34
+ declare const getUserStatsSchema: z.ZodObject<{
35
+ userId: z.ZodString;
36
+ }, "strip", z.ZodTypeAny, {
37
+ userId: string;
38
+ }, {
39
+ userId: string;
40
+ }>;
41
+ /**
42
+ * Zod schema for isAttached tool
43
+ */
44
+ declare const isAttachedSchema: z.ZodObject<{
45
+ userId: z.ZodString;
46
+ }, "strip", z.ZodTypeAny, {
47
+ userId: string;
48
+ }, {
49
+ userId: string;
50
+ }>;
51
+ /**
52
+ * Zod schema for detachUser tool
53
+ */
54
+ declare const detachUserSchema: z.ZodObject<{
55
+ userId: z.ZodString;
56
+ }, "strip", z.ZodTypeAny, {
57
+ userId: string;
58
+ }, {
59
+ userId: string;
60
+ }>;
61
+ /**
62
+ * Zod schema for listUsers tool
63
+ */
64
+ declare const listUsersSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
65
+ /**
66
+ * Tool implementations for MCP server
67
+ */
68
+ declare const tools: {
69
+ /**
70
+ * Execute a SQL query for a user
71
+ */
72
+ query(server: DuckPondServer, input: z.infer<typeof querySchema>): Promise<{
73
+ content: {
74
+ type: "text";
75
+ text: string;
76
+ }[];
77
+ }>;
78
+ /**
79
+ * Execute DDL/DML statement
80
+ */
81
+ execute(server: DuckPondServer, input: z.infer<typeof executeSchema>): Promise<{
82
+ content: {
83
+ type: "text";
84
+ text: string;
85
+ }[];
86
+ }>;
87
+ /**
88
+ * Get user database statistics
89
+ */
90
+ getUserStats(server: DuckPondServer, input: z.infer<typeof getUserStatsSchema>): Promise<{
91
+ content: {
92
+ type: "text";
93
+ text: string;
94
+ }[];
95
+ }>;
96
+ /**
97
+ * Check if user is attached
98
+ */
99
+ isAttached(server: DuckPondServer, input: z.infer<typeof isAttachedSchema>): {
100
+ content: {
101
+ type: "text";
102
+ text: string;
103
+ }[];
104
+ };
105
+ /**
106
+ * Detach a user from cache
107
+ */
108
+ detachUser(server: DuckPondServer, input: z.infer<typeof detachUserSchema>): Promise<{
109
+ content: {
110
+ type: "text";
111
+ text: string;
112
+ }[];
113
+ }>;
114
+ /**
115
+ * List all currently cached users
116
+ */
117
+ listUsers(server: DuckPondServer, _input: z.infer<typeof listUsersSchema>): {
118
+ content: {
119
+ type: "text";
120
+ text: string;
121
+ }[];
122
+ };
123
+ };
124
+
125
+ export { detachUserSchema, executeSchema, getUserStatsSchema, isAttachedSchema, listUsersSchema, querySchema, tools };
@@ -0,0 +1,2 @@
1
+ import{a,b,c,d,e,f,g}from"../chunk-43NWQIU3.js";import"../chunk-A3S6D44B.js";export{e as detachUserSchema,b as executeSchema,c as getUserStatsSchema,d as isAttachedSchema,f as listUsersSchema,a as querySchema,g as tools};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Debug loggers for different modules
3
+ */
4
+ declare const loggers: {
5
+ core: any;
6
+ stdio: any;
7
+ fastmcp: any;
8
+ tools: any;
9
+ main: any;
10
+ };
11
+ /**
12
+ * Create a custom logger
13
+ */
14
+ declare function createLogger(namespace: string): any;
15
+
16
+ export { createLogger, loggers };
@@ -0,0 +1,2 @@
1
+ import{b as a,c as b}from"../chunk-A3S6D44B.js";export{b as createLogger,a as loggers};
2
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "duckpond-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for multi-tenant DuckDB management with R2/S3 storage",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "duckdb",
9
+ "duckpond",
10
+ "r2",
11
+ "s3",
12
+ "cloudflare",
13
+ "multi-tenant",
14
+ "analytics",
15
+ "typescript"
16
+ ],
17
+ "author": "jordan.burke@gmail.com",
18
+ "license": "MIT",
19
+ "homepage": "https://github.com/jordanburke/duckpond-mcp-server",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/jordanburke/duckpond-mcp-server"
23
+ },
24
+ "type": "module",
25
+ "bin": {
26
+ "duckpond-mcp-server": "./dist/index.js"
27
+ },
28
+ "dependencies": {
29
+ "@jordanburke/fastmcp": "3.21.0",
30
+ "@types/express": "^5.0.3",
31
+ "@types/jsonwebtoken": "^9.0.10",
32
+ "commander": "^12.1.0",
33
+ "debug": "^4.4.3",
34
+ "duckpond": "^0.2.0",
35
+ "express": "^5.1.0",
36
+ "jsonwebtoken": "^9.0.2",
37
+ "zod": "^3.24.1"
38
+ },
39
+ "devDependencies": {
40
+ "@eslint/eslintrc": "^3.3.1",
41
+ "@eslint/js": "^9.38.0",
42
+ "@types/node": "^22.18.11",
43
+ "@typescript-eslint/eslint-plugin": "^8.46.1",
44
+ "@typescript-eslint/parser": "^8.46.1",
45
+ "@vitest/coverage-v8": "3.2.4",
46
+ "@vitest/ui": "^3.2.4",
47
+ "cross-env": "^10.1.0",
48
+ "eslint": "^9.38.0",
49
+ "eslint-config-prettier": "^10.1.8",
50
+ "eslint-plugin-import": "^2.32.0",
51
+ "eslint-plugin-prettier": "^5.5.4",
52
+ "eslint-plugin-simple-import-sort": "^12.1.1",
53
+ "globals": "^16.4.0",
54
+ "prettier": "^3.6.2",
55
+ "rimraf": "^6.0.1",
56
+ "ts-node": "^10.9.2",
57
+ "tsup": "^8.5.0",
58
+ "tsx": "^4.19.2",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^3.2.4"
61
+ },
62
+ "main": "./dist/index.js",
63
+ "module": "./dist/index.js",
64
+ "types": "./dist/index.d.ts",
65
+ "exports": {
66
+ ".": {
67
+ "types": "./dist/index.d.ts",
68
+ "import": "./dist/index.js"
69
+ }
70
+ },
71
+ "files": [
72
+ "lib",
73
+ "dist"
74
+ ],
75
+ "scripts": {
76
+ "validate": "pnpm format && pnpm lint && pnpm test && pnpm build",
77
+ "format": "prettier --write .",
78
+ "format:check": "prettier --check .",
79
+ "lint": "eslint ./src --fix",
80
+ "lint:check": "eslint ./src",
81
+ "test": "vitest run",
82
+ "test:watch": "vitest",
83
+ "test:coverage": "vitest run --coverage",
84
+ "test:ui": "vitest --ui",
85
+ "build": "rimraf dist && cross-env NODE_ENV=production tsup",
86
+ "build:watch": "tsup --watch",
87
+ "dev": "tsup --watch",
88
+ "serve:test": "tsx src/index.ts",
89
+ "serve:test:http": "tsx src/index.ts --transport http",
90
+ "ts-types": "tsc"
91
+ }
92
+ }