duckpond-mcp-server 0.3.4 → 0.3.6
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +4 -0
- package/dist/lib.js +1 -0
- package/dist/logger-CX5FKgvo.js +2 -0
- package/dist/logger-CX5FKgvo.js.map +1 -0
- package/dist/server-core-B66uY2zm.js +2 -0
- package/dist/server-core-B66uY2zm.js.map +1 -0
- package/dist/server-core.d.ts +108 -105
- package/dist/server-core.js +1 -2
- package/dist/server.d.ts +30 -29
- package/dist/server.js +47 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.d.ts +81 -79
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/ui-server-BoQ-kekq.js +2 -0
- package/dist/ui-server-BoQ-kekq.js.map +1 -0
- package/dist/ui-server.d.ts +7 -6
- package/dist/ui-server.js +1 -2
- package/dist/utils/logger.d.ts +8 -6
- package/dist/utils/logger.js +1 -2
- package/package.json +21 -34
- package/dist/chunk-A3S6D44B.js +0 -2
- package/dist/chunk-A3S6D44B.js.map +0 -1
- package/dist/chunk-E5JCTMWX.js +0 -2
- package/dist/chunk-E5JCTMWX.js.map +0 -1
- package/dist/chunk-MPMUZFRC.js +0 -2
- package/dist/chunk-MPMUZFRC.js.map +0 -1
- package/dist/chunk-N24XRZ5U.js +0 -48
- package/dist/chunk-N24XRZ5U.js.map +0 -1
- package/dist/chunk-SRB6JVEI.js +0 -2
- package/dist/chunk-SRB6JVEI.js.map +0 -1
- package/dist/server-core.js.map +0 -1
- package/dist/ui-server.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export { };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{n as e}from"./logger-CX5FKgvo.js";import{getDefaultUserId as t}from"./tools/index.js";import"./server-core-B66uY2zm.js";import"./ui-server-BoQ-kekq.js";import{startServer as n}from"./server.js";import{webcrypto as r}from"crypto";import{Command as i}from"commander";import{createRequire as a}from"module";globalThis.crypto||(globalThis.crypto=r);const o=a(import.meta.url)(`../package.json`),s=e.main;function c(e){return e.startsWith(`~/`)?`${process.env.HOME||process.env.USERPROFILE||`.`}${e.slice(1)}`:e}function l(){return`${process.env.HOME||process.env.USERPROFILE||`.`}/.duckpond/data`}function u(){let e=c(process.env.DUCKPOND_DATA_DIR||l()),t={memoryLimit:process.env.DUCKPOND_MEMORY_LIMIT||`4GB`,threads:parseInt(process.env.DUCKPOND_THREADS||`4`),maxActiveUsers:parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS||`10`),evictionTimeout:parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT||`300000`),cacheType:process.env.DUCKPOND_CACHE_TYPE||`disk`,strategy:process.env.DUCKPOND_STRATEGY||`duckdb`,tempDir:process.env.DUCKPOND_TEMP_DIR,cacheDir:process.env.DUCKPOND_CACHE_DIR||e,dataDir:e};return process.env.DUCKPOND_R2_ACCOUNT_ID&&(t.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&&(t.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&&(t.s3.endpoint=process.env.DUCKPOND_S3_ENDPOINT)),t}const d=new i;d.name(`duckpond-mcp-server`).description(`MCP server for multi-tenant DuckDB management with R2/S3 storage`).version(o.version).option(`-t, --transport <type>`,`Transport mode: stdio or http`,`stdio`).option(`-p, --port <port>`,`HTTP port (when using http transport)`,`3000`).option(`--ui`,`Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)`).option(`--ui-port <port>`,`UI management server port, only used when no default user (default: 4000)`,`4000`).option(`--ui-internal-port <port>`,`DuckDB UI port (default: 4213)`,`4213`).action(async e=>{try{let r=u(),i=t();s(`Starting DuckPond MCP Server with ${e.transport} transport`),s(`Configuration:`,{memoryLimit:r.memoryLimit,threads:r.threads,maxActiveUsers:r.maxActiveUsers,strategy:r.strategy,dataDir:r.dataDir,tempDir:r.tempDir,cacheDir:r.cacheDir,cacheType:r.cacheType,hasR2:!!r.r2,hasS3:!!r.s3,defaultUser:i||`(not set)`}),r.r2?console.error(`☁️ Storage: Cloudflare R2`):r.s3?console.error(`☁️ Storage: AWS S3`):console.error(`💾 Storage: Local disk (${r.dataDir})`),i&&console.error(`👤 Default user: ${i}`);let a;if(process.env.DUCKPOND_OAUTH_ENABLED===`true`){let t=process.env.DUCKPOND_OAUTH_USERNAME,n=process.env.DUCKPOND_OAUTH_PASSWORD;(!t||!n)&&(console.error(`❌ OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required`),process.exit(1)),a={enabled:!0,username:t,password:n,userId:process.env.DUCKPOND_OAUTH_USER_ID||t,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(`🔐 OAuth enabled with username/password authentication`),console.error(` Username: ${a.username}`),console.error(` User ID: ${a.userId}`),console.error(` ✓ Login form will be shown at authorization endpoint`)}let o;process.env.DUCKPOND_BASIC_AUTH_USERNAME&&process.env.DUCKPOND_BASIC_AUTH_PASSWORD&&(o={username:process.env.DUCKPOND_BASIC_AUTH_USERNAME,password:process.env.DUCKPOND_BASIC_AUTH_PASSWORD,userId:process.env.DUCKPOND_BASIC_AUTH_USER_ID,email:process.env.DUCKPOND_BASIC_AUTH_EMAIL},console.error(`🔐 Basic authentication enabled`),console.error(` Username: ${o.username}`),console.error(` User ID: ${o.userId||o.username}`));let c=e.ui||process.env.DUCKPOND_UI_ENABLED===`true`,l=parseInt(e.uiPort)||4e3,d=parseInt(e.uiInternalPort)||4213;c&&e.transport===`stdio`&&(i?console.error(`🖥️ DuckDB UI will start at http://localhost:${d}`):(console.error(`🖥️ UI management server at http://localhost:${l}/ui`),console.error(` Visit /ui/:userId to start DuckDB UI for a user`))),e.transport===`stdio`||e.transport===`http`?await n({config:r,port:parseInt(e.port)||3e3,endpoint:`/mcp`,oauth:a,basicAuth:o,ui:c?{enabled:!0,port:l,internalPort:d,autoStartUser:i}:void 0},e.transport===`stdio`?`stdio`:`http`):(s(`Unknown transport: ${e.transport}`),process.exit(1))}catch(e){s(`Fatal error:`,e),console.error(`Fatal error:`,e),process.exit(1)}}),d.parse();export{};
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { Command } from \"commander\"\nimport { createRequire } from \"module\"\n\nimport type { OAuthConfig } from \"./server\"\nimport { getDefaultUserId } from \"./tools\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport { startServer } from \"./server\"\nimport type { DuckPondServerConfig } from \"./server-core\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * Expand ~ to home directory in paths\n */\nfunction expandTilde(path: string): string {\n if (path.startsWith(\"~/\")) {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}${path.slice(1)}`\n }\n return path\n}\n\n/**\n * Get the default data directory for persistent storage\n */\nfunction getDefaultDataDir(): string {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}/.duckpond/data`\n}\n\n/**\n * Parse environment variables into DuckPond configuration\n */\nfunction getConfigFromEnv(): DuckPondServerConfig {\n // Default to local disk storage (expand ~ if present)\n const dataDir = expandTilde(process.env.DUCKPOND_DATA_DIR || getDefaultDataDir())\n\n const config: DuckPondServerConfig = {\n memoryLimit: process.env.DUCKPOND_MEMORY_LIMIT || \"4GB\",\n threads: parseInt(process.env.DUCKPOND_THREADS || \"4\"),\n maxActiveUsers: parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS || \"10\"),\n evictionTimeout: parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT || \"300000\"),\n cacheType: (process.env.DUCKPOND_CACHE_TYPE as \"disk\" | \"memory\" | \"noop\") || \"disk\",\n strategy: (process.env.DUCKPOND_STRATEGY as \"parquet\" | \"duckdb\" | \"hybrid\") || \"duckdb\",\n tempDir: process.env.DUCKPOND_TEMP_DIR,\n cacheDir: process.env.DUCKPOND_CACHE_DIR || dataDir,\n dataDir,\n }\n\n // R2 configuration\n if (process.env.DUCKPOND_R2_ACCOUNT_ID) {\n config.r2 = {\n accountId: process.env.DUCKPOND_R2_ACCOUNT_ID,\n accessKeyId: process.env.DUCKPOND_R2_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_R2_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_R2_BUCKET || \"\",\n }\n }\n\n // S3 configuration\n if (process.env.DUCKPOND_S3_REGION) {\n config.s3 = {\n region: process.env.DUCKPOND_S3_REGION,\n accessKeyId: process.env.DUCKPOND_S3_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_S3_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_S3_BUCKET || \"\",\n }\n\n if (process.env.DUCKPOND_S3_ENDPOINT) {\n config.s3.endpoint = process.env.DUCKPOND_S3_ENDPOINT\n }\n }\n\n return config\n}\n\n/**\n * Main CLI program\n */\nconst program = new Command()\n\nprogram\n .name(\"duckpond-mcp-server\")\n .description(\"MCP server for multi-tenant DuckDB management with R2/S3 storage\")\n .version(packageJson.version)\n .option(\"-t, --transport <type>\", \"Transport mode: stdio or http\", \"stdio\")\n .option(\"-p, --port <port>\", \"HTTP port (when using http transport)\", \"3000\")\n .option(\"--ui\", \"Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)\")\n .option(\"--ui-port <port>\", \"UI management server port, only used when no default user (default: 4000)\", \"4000\")\n .option(\"--ui-internal-port <port>\", \"DuckDB UI port (default: 4213)\", \"4213\")\n .action(async (options) => {\n try {\n const config = getConfigFromEnv()\n\n const defaultUser = getDefaultUserId()\n log(`Starting DuckPond MCP Server with ${options.transport} transport`)\n log(\"Configuration:\", {\n memoryLimit: config.memoryLimit,\n threads: config.threads,\n maxActiveUsers: config.maxActiveUsers,\n strategy: config.strategy,\n dataDir: config.dataDir,\n tempDir: config.tempDir,\n cacheDir: config.cacheDir,\n cacheType: config.cacheType,\n hasR2: !!config.r2,\n hasS3: !!config.s3,\n defaultUser: defaultUser || \"(not set)\",\n })\n\n // Log storage mode\n if (config.r2) {\n console.error(\"☁️ Storage: Cloudflare R2\")\n } else if (config.s3) {\n console.error(\"☁️ Storage: AWS S3\")\n } else {\n console.error(`💾 Storage: Local disk (${config.dataDir})`)\n }\n\n if (defaultUser) {\n console.error(`👤 Default user: ${defaultUser}`)\n }\n\n // Load OAuth configuration from environment variables (for HTTP transport)\n let oauthConfig: OAuthConfig | undefined\n if (process.env.DUCKPOND_OAUTH_ENABLED === \"true\") {\n const username = process.env.DUCKPOND_OAUTH_USERNAME\n const password = process.env.DUCKPOND_OAUTH_PASSWORD\n\n if (!username || !password) {\n console.error(\"❌ OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required\")\n process.exit(1)\n }\n\n oauthConfig = {\n enabled: true,\n username,\n password,\n userId: process.env.DUCKPOND_OAUTH_USER_ID || username,\n email: process.env.DUCKPOND_OAUTH_EMAIL,\n issuer: process.env.DUCKPOND_OAUTH_ISSUER || `http://localhost:${parseInt(options.port) || 3000}`,\n resource: process.env.DUCKPOND_OAUTH_RESOURCE,\n }\n\n console.error(\"🔐 OAuth enabled with username/password authentication\")\n console.error(` Username: ${oauthConfig.username}`)\n console.error(` User ID: ${oauthConfig.userId}`)\n console.error(\" ✓ Login form will be shown at authorization endpoint\")\n }\n\n // Load Basic Auth configuration from environment variables (for HTTP transport)\n let basicAuthConfig: { username: string; password: string; userId?: string; email?: string } | undefined\n if (process.env.DUCKPOND_BASIC_AUTH_USERNAME && process.env.DUCKPOND_BASIC_AUTH_PASSWORD) {\n basicAuthConfig = {\n username: process.env.DUCKPOND_BASIC_AUTH_USERNAME,\n password: process.env.DUCKPOND_BASIC_AUTH_PASSWORD,\n userId: process.env.DUCKPOND_BASIC_AUTH_USER_ID,\n email: process.env.DUCKPOND_BASIC_AUTH_EMAIL,\n }\n\n console.error(\"🔐 Basic authentication enabled\")\n console.error(` Username: ${basicAuthConfig.username}`)\n console.error(` User ID: ${basicAuthConfig.userId || basicAuthConfig.username}`)\n }\n\n // Parse UI options\n const uiEnabled = options.ui || process.env.DUCKPOND_UI_ENABLED === \"true\"\n const uiPort = parseInt(options.uiPort) || 4000\n const uiInternalPort = parseInt(options.uiInternalPort) || 4213\n\n if (uiEnabled && options.transport === \"stdio\") {\n if (defaultUser) {\n // Will auto-start UI for default user - show where to access it\n console.error(`🖥️ DuckDB UI will start at http://localhost:${uiInternalPort}`)\n } else {\n // No default user - management server needed\n console.error(`🖥️ UI management server at http://localhost:${uiPort}/ui`)\n console.error(` Visit /ui/:userId to start DuckDB UI for a user`)\n }\n }\n\n // Start unified FastMCP server with appropriate transport\n if (options.transport === \"stdio\" || options.transport === \"http\") {\n await startServer(\n {\n config,\n port: parseInt(options.port) || 3000,\n endpoint: \"/mcp\",\n oauth: oauthConfig,\n basicAuth: basicAuthConfig,\n ui: uiEnabled\n ? {\n enabled: true,\n port: uiPort,\n internalPort: uiInternalPort,\n autoStartUser: defaultUser,\n }\n : undefined,\n },\n options.transport === \"stdio\" ? \"stdio\" : \"http\",\n )\n } else {\n log(`Unknown transport: ${options.transport}`)\n process.exit(1)\n }\n } catch (error) {\n log(\"Fatal error:\", error)\n console.error(\"Fatal error:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":"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","names":["webcrypto","Command","createRequire","webcrypto","require","createRequire","packageJson","log","loggers","expandTilde","path","getDefaultDataDir","getConfigFromEnv","dataDir","config","program","Command","options","defaultUser","getDefaultUserId","oauthConfig","username","password","basicAuthConfig","uiEnabled","uiPort","uiInternalPort","startServer","error"]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["config: DuckPondServerConfig","oauthConfig: OAuthConfig | undefined","basicAuthConfig: { username: string; password: string; userId?: string; email?: string } | undefined"],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Polyfill for Web Crypto API in Node.js environments\nimport { webcrypto } from \"crypto\"\n\nif (!globalThis.crypto) {\n globalThis.crypto = webcrypto as Crypto\n}\n\nimport { Command } from \"commander\"\nimport { createRequire } from \"module\"\n\nimport type { OAuthConfig } from \"./server\"\nimport { getDefaultUserId } from \"./tools\"\n\nconst require = createRequire(import.meta.url)\nconst packageJson = require(\"../package.json\") as { version: string }\nimport { startServer } from \"./server\"\nimport type { DuckPondServerConfig } from \"./server-core\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * Expand ~ to home directory in paths\n */\nfunction expandTilde(path: string): string {\n if (path.startsWith(\"~/\")) {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}${path.slice(1)}`\n }\n return path\n}\n\n/**\n * Get the default data directory for persistent storage\n */\nfunction getDefaultDataDir(): string {\n const home = process.env.HOME || process.env.USERPROFILE || \".\"\n return `${home}/.duckpond/data`\n}\n\n/**\n * Parse environment variables into DuckPond configuration\n */\nfunction getConfigFromEnv(): DuckPondServerConfig {\n // Default to local disk storage (expand ~ if present)\n const dataDir = expandTilde(process.env.DUCKPOND_DATA_DIR || getDefaultDataDir())\n\n const config: DuckPondServerConfig = {\n memoryLimit: process.env.DUCKPOND_MEMORY_LIMIT || \"4GB\",\n threads: parseInt(process.env.DUCKPOND_THREADS || \"4\"),\n maxActiveUsers: parseInt(process.env.DUCKPOND_MAX_ACTIVE_USERS || \"10\"),\n evictionTimeout: parseInt(process.env.DUCKPOND_EVICTION_TIMEOUT || \"300000\"),\n cacheType: (process.env.DUCKPOND_CACHE_TYPE as \"disk\" | \"memory\" | \"noop\") || \"disk\",\n strategy: (process.env.DUCKPOND_STRATEGY as \"parquet\" | \"duckdb\" | \"hybrid\") || \"duckdb\",\n tempDir: process.env.DUCKPOND_TEMP_DIR,\n cacheDir: process.env.DUCKPOND_CACHE_DIR || dataDir,\n dataDir,\n }\n\n // R2 configuration\n if (process.env.DUCKPOND_R2_ACCOUNT_ID) {\n config.r2 = {\n accountId: process.env.DUCKPOND_R2_ACCOUNT_ID,\n accessKeyId: process.env.DUCKPOND_R2_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_R2_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_R2_BUCKET || \"\",\n }\n }\n\n // S3 configuration\n if (process.env.DUCKPOND_S3_REGION) {\n config.s3 = {\n region: process.env.DUCKPOND_S3_REGION,\n accessKeyId: process.env.DUCKPOND_S3_ACCESS_KEY_ID || \"\",\n secretAccessKey: process.env.DUCKPOND_S3_SECRET_ACCESS_KEY || \"\",\n bucket: process.env.DUCKPOND_S3_BUCKET || \"\",\n }\n\n if (process.env.DUCKPOND_S3_ENDPOINT) {\n config.s3.endpoint = process.env.DUCKPOND_S3_ENDPOINT\n }\n }\n\n return config\n}\n\n/**\n * Main CLI program\n */\nconst program = new Command()\n\nprogram\n .name(\"duckpond-mcp-server\")\n .description(\"MCP server for multi-tenant DuckDB management with R2/S3 storage\")\n .version(packageJson.version)\n .option(\"-t, --transport <type>\", \"Transport mode: stdio or http\", \"stdio\")\n .option(\"-p, --port <port>\", \"HTTP port (when using http transport)\", \"3000\")\n .option(\"--ui\", \"Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)\")\n .option(\"--ui-port <port>\", \"UI management server port, only used when no default user (default: 4000)\", \"4000\")\n .option(\"--ui-internal-port <port>\", \"DuckDB UI port (default: 4213)\", \"4213\")\n .action(async (options) => {\n try {\n const config = getConfigFromEnv()\n\n const defaultUser = getDefaultUserId()\n log(`Starting DuckPond MCP Server with ${options.transport} transport`)\n log(\"Configuration:\", {\n memoryLimit: config.memoryLimit,\n threads: config.threads,\n maxActiveUsers: config.maxActiveUsers,\n strategy: config.strategy,\n dataDir: config.dataDir,\n tempDir: config.tempDir,\n cacheDir: config.cacheDir,\n cacheType: config.cacheType,\n hasR2: !!config.r2,\n hasS3: !!config.s3,\n defaultUser: defaultUser || \"(not set)\",\n })\n\n // Log storage mode\n if (config.r2) {\n console.error(\"☁️ Storage: Cloudflare R2\")\n } else if (config.s3) {\n console.error(\"☁️ Storage: AWS S3\")\n } else {\n console.error(`💾 Storage: Local disk (${config.dataDir})`)\n }\n\n if (defaultUser) {\n console.error(`👤 Default user: ${defaultUser}`)\n }\n\n // Load OAuth configuration from environment variables (for HTTP transport)\n let oauthConfig: OAuthConfig | undefined\n if (process.env.DUCKPOND_OAUTH_ENABLED === \"true\") {\n const username = process.env.DUCKPOND_OAUTH_USERNAME\n const password = process.env.DUCKPOND_OAUTH_PASSWORD\n\n if (!username || !password) {\n console.error(\"❌ OAuth enabled but DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are required\")\n process.exit(1)\n }\n\n oauthConfig = {\n enabled: true,\n username,\n password,\n userId: process.env.DUCKPOND_OAUTH_USER_ID || username,\n email: process.env.DUCKPOND_OAUTH_EMAIL,\n issuer: process.env.DUCKPOND_OAUTH_ISSUER || `http://localhost:${parseInt(options.port) || 3000}`,\n resource: process.env.DUCKPOND_OAUTH_RESOURCE,\n }\n\n console.error(\"🔐 OAuth enabled with username/password authentication\")\n console.error(` Username: ${oauthConfig.username}`)\n console.error(` User ID: ${oauthConfig.userId}`)\n console.error(\" ✓ Login form will be shown at authorization endpoint\")\n }\n\n // Load Basic Auth configuration from environment variables (for HTTP transport)\n let basicAuthConfig: { username: string; password: string; userId?: string; email?: string } | undefined\n if (process.env.DUCKPOND_BASIC_AUTH_USERNAME && process.env.DUCKPOND_BASIC_AUTH_PASSWORD) {\n basicAuthConfig = {\n username: process.env.DUCKPOND_BASIC_AUTH_USERNAME,\n password: process.env.DUCKPOND_BASIC_AUTH_PASSWORD,\n userId: process.env.DUCKPOND_BASIC_AUTH_USER_ID,\n email: process.env.DUCKPOND_BASIC_AUTH_EMAIL,\n }\n\n console.error(\"🔐 Basic authentication enabled\")\n console.error(` Username: ${basicAuthConfig.username}`)\n console.error(` User ID: ${basicAuthConfig.userId || basicAuthConfig.username}`)\n }\n\n // Parse UI options\n const uiEnabled = options.ui || process.env.DUCKPOND_UI_ENABLED === \"true\"\n const uiPort = parseInt(options.uiPort) || 4000\n const uiInternalPort = parseInt(options.uiInternalPort) || 4213\n\n if (uiEnabled && options.transport === \"stdio\") {\n if (defaultUser) {\n // Will auto-start UI for default user - show where to access it\n console.error(`🖥️ DuckDB UI will start at http://localhost:${uiInternalPort}`)\n } else {\n // No default user - management server needed\n console.error(`🖥️ UI management server at http://localhost:${uiPort}/ui`)\n console.error(` Visit /ui/:userId to start DuckDB UI for a user`)\n }\n }\n\n // Start unified FastMCP server with appropriate transport\n if (options.transport === \"stdio\" || options.transport === \"http\") {\n await startServer(\n {\n config,\n port: parseInt(options.port) || 3000,\n endpoint: \"/mcp\",\n oauth: oauthConfig,\n basicAuth: basicAuthConfig,\n ui: uiEnabled\n ? {\n enabled: true,\n port: uiPort,\n internalPort: uiInternalPort,\n autoStartUser: defaultUser,\n }\n : undefined,\n },\n options.transport === \"stdio\" ? \"stdio\" : \"http\",\n )\n } else {\n log(`Unknown transport: ${options.transport}`)\n process.exit(1)\n }\n } catch (error) {\n log(\"Fatal error:\", error)\n console.error(\"Fatal error:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":"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"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DuckPondServer, DuckPondServerConfig } from "./server-core.js";
|
|
2
|
+
import { FastMCPServerOptions, createFastMCPServer } from "./server.js";
|
|
3
|
+
import { getDefaultUserId } from "./tools/index.js";
|
|
4
|
+
export { DuckPondServer, type DuckPondServerConfig, type FastMCPServerOptions, createFastMCPServer, getDefaultUserId };
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./logger-CX5FKgvo.js";import{getDefaultUserId as e}from"./tools/index.js";import{t}from"./server-core-B66uY2zm.js";import"./ui-server-BoQ-kekq.js";import{createFastMCPServer as n}from"./server.js";export{t as DuckPondServer,n as createFastMCPServer,e as getDefaultUserId};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"debug";const t={core:e(`duckpond-mcp:core`),stdio:e(`duckpond-mcp:stdio`),fastmcp:e(`duckpond-mcp:fastmcp`),tools:e(`duckpond-mcp:tools`),main:e(`duckpond-mcp:main`)};function n(t){return e(`duckpond-mcp:${t}`)}export{t as n,n as t};
|
|
2
|
+
//# sourceMappingURL=logger-CX5FKgvo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-CX5FKgvo.js","names":[],"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":"qBAKA,MAAa,EAAU,CACrB,KAAM,EAAM,oBAAoB,CAChC,MAAO,EAAM,qBAAqB,CAClC,QAAS,EAAM,uBAAuB,CACtC,MAAO,EAAM,qBAAqB,CAClC,KAAM,EAAM,oBAAoB,CACjC,CAKD,SAAgB,EAAa,EAAmB,CAC9C,OAAO,EAAM,gBAAgB,IAAY"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e}from"./logger-CX5FKgvo.js";import{DuckPond as t}from"duckpond";function n(e){"@babel/helpers - typeof";return n=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},n(e)}function r(e,t){if(n(e)!=`object`||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var i=r.call(e,t||`default`);if(n(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function i(e){var t=r(e,`string`);return n(t)==`symbol`?t:t+``}function a(e,t,n){return(t=i(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const o=e.core;var s=class{constructor(e){this.config=e,a(this,`pond`,null),a(this,`initialized`,!1),a(this,`currentUIUserId`,null),a(this,`uiInternalPort`,4213),a(this,`uiKeepaliveTimer`,null),a(this,`UI_KEEPALIVE_INTERVAL`,3e4),o(`DuckPondServer created`)}async init(){if(this.initialized)return o(`Already initialized`),{success:!0,data:void 0};o(`Initializing DuckPond...`);let e=Date.now();this.pond=new t(this.config);let n=await this.pond.init();return this.handleEither(n,Date.now()-e)}async query(e,t){if(!this.pond)return this.notInitializedError();o(`Query for user ${e}: ${t.substring(0,100)}...`);let n=Date.now(),r=await this.pond.query(e,t);return this.handleEither(r,Date.now()-n)}async execute(e,t){if(!this.pond)return this.notInitializedError();o(`Execute for user ${e}: ${t.substring(0,100)}...`);let n=Date.now(),r=await this.pond.execute(e,t);return this.handleEither(r,Date.now()-n)}async getUserStats(e){if(!this.pond)return this.notInitializedError();o(`Getting stats for user ${e}`);let t=Date.now(),n=await this.pond.getUserStats(e);return this.handleEither(n,Date.now()-t)}isAttached(e){if(!this.pond)return this.notInitializedError();let t=this.pond.isAttached(e);return o(`User ${e} attached: ${t}`),{success:!0,data:t}}async detachUser(e){if(!this.pond)return this.notInitializedError();o(`Detaching user ${e}`);let t=Date.now(),n=await this.pond.detachUser(e);return this.handleEither(n,Date.now()-t)}listUsers(){if(!this.pond)return this.notInitializedError();o(`Listing cached users`);let e=this.pond.listUsers();return{success:!0,data:{users:e.users.toArray(),count:e.count,maxActiveUsers:e.maxActiveUsers,utilizationPercent:e.utilizationPercent}}}async close(){if(!this.pond)return{success:!0,data:void 0};o(`Closing DuckPond...`);let e=Date.now();this.currentUIUserId&&await this.stopUI();let t=await this.pond.close();return this.initialized=!1,this.pond=null,this.handleEither(t,Date.now()-e)}async startUI(e){if(!this.pond)return this.notInitializedError();o(`Starting UI for user ${e}`);let t=Date.now();if(this.currentUIUserId&&this.currentUIUserId!==e&&(o(`Stopping UI for previous user ${this.currentUIUserId}`),await this.stopUI()),this.currentUIUserId===e)return o(`UI already running for user ${e}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-t};let n=await this.execute(e,`INSTALL ui; LOAD ui;`);if(!n.success)return o(`Failed to install UI extension: ${n.error.message}`),n;let r=await this.execute(e,`SET ui_local_port = ${this.uiInternalPort}`);if(!r.success)return o(`Failed to set UI port: ${r.error.message}`),r;let i=await this.execute(e,`CALL start_ui_server()`);return i.success?(this.currentUIUserId=e,this.startUIKeepalive(e),o(`UI started for user ${e} on port ${this.uiInternalPort}`),{success:!0,data:{port:this.uiInternalPort},executionTime:Date.now()-t}):(o(`Failed to start UI server: ${i.error.message}`),i)}startUIKeepalive(e){this.stopUIKeepalive(),o(`Starting UI keepalive for user ${e}`),this.uiKeepaliveTimer=setInterval(async()=>{this.pond&&this.currentUIUserId===e&&(o(`UI keepalive ping for user ${e}`),await this.pond.query(e,`SELECT 1`))},this.UI_KEEPALIVE_INTERVAL)}stopUIKeepalive(){this.uiKeepaliveTimer&&(o(`Stopping UI keepalive`),clearInterval(this.uiKeepaliveTimer),this.uiKeepaliveTimer=null)}async stopUI(){if(this.stopUIKeepalive(),!this.currentUIUserId)return o(`No UI running to stop`),{success:!0,data:void 0};if(!this.pond)return this.currentUIUserId=null,{success:!0,data:void 0};o(`Stopping UI for user ${this.currentUIUserId}`);let e=Date.now(),t=await this.execute(this.currentUIUserId,`CALL stop_ui_server()`),n=this.currentUIUserId;return this.currentUIUserId=null,t.success?(o(`UI stopped for user ${n}`),{success:!0,data:void 0,executionTime:Date.now()-e}):(o(`Failed to stop UI: ${t.error.message}`),t)}getCurrentUIUser(){return this.currentUIUserId}setUIPort(e){this.uiInternalPort=e}getUIPort(){return this.uiInternalPort}handleEither(e,t){return e.fold(e=>({success:!1,error:{code:this.mapErrorCode(e.code),message:e.message,details:{originalCode:e.code,context:e.context,cause:e.cause?.message}}}),e=>({success:!0,data:e,executionTime:t}))}mapErrorCode(e){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`}[e]||`INTERNAL_ERROR`}notInitializedError(){return{success:!1,error:{code:`FAILED_PRECONDITION`,message:`DuckPond not initialized. Call init() first.`}}}};export{s as t};
|
|
2
|
+
//# sourceMappingURL=server-core-B66uY2zm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-core-B66uY2zm.js","names":["config: DuckPondServerConfig"],"sources":["../src/server-core.ts"],"sourcesContent":["import type { Either } from \"duckpond\"\nimport { DuckPond, type DuckPondConfig, type ErrorCode, type ListUsersResult, type UserStats } from \"duckpond\"\n\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.core\n\n/**\n * Result type for MCP tool responses\n */\nexport type MCPResult<T> =\n | {\n success: true\n data: T\n executionTime?: number\n }\n | {\n success: false\n error: {\n code: string\n message: string\n details?: {\n originalCode?: ErrorCode\n context?: Record<string, unknown>\n cause?: string\n }\n }\n }\n\n/**\n * Configuration for DuckPond MCP Server\n * Extends DuckPondConfig with server-specific options\n */\nexport type DuckPondServerConfig = DuckPondConfig & {\n /** Directory for persistent database storage (default: ~/.duckpond/data) */\n dataDir?: string\n}\n\n/**\n * Core DuckPond MCP Server\n *\n * Wraps DuckPond library with MCP-compatible result types\n */\nexport class DuckPondServer {\n private pond: DuckPond | null = null\n private initialized = false\n private currentUIUserId: string | null = null\n private uiInternalPort: number = 4213\n private uiKeepaliveTimer: ReturnType<typeof setInterval> | null = null\n private readonly UI_KEEPALIVE_INTERVAL = 30000 // 30 seconds\n\n constructor(private config: DuckPondServerConfig) {\n log(\"DuckPondServer created\")\n }\n\n /**\n * Initialize the DuckPond instance\n */\n async init(): Promise<MCPResult<void>> {\n if (this.initialized) {\n log(\"Already initialized\")\n return { success: true, data: undefined }\n }\n\n log(\"Initializing DuckPond...\")\n const startTime = Date.now()\n\n this.pond = new DuckPond(this.config)\n const result = await this.pond.init()\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute a SQL query for a user\n */\n async query<T = unknown>(userId: string, sql: string): Promise<MCPResult<T[]>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Query for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.query<T>(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Execute DDL/DML without returning results\n */\n async execute(userId: string, sql: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Execute for user ${userId}: ${sql.substring(0, 100)}...`)\n const startTime = Date.now()\n\n const result = await this.pond.execute(userId, sql)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Get statistics about a user's database\n */\n async getUserStats(userId: string): Promise<MCPResult<UserStats>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Getting stats for user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.getUserStats(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Check if a user is currently cached\n */\n isAttached(userId: string): MCPResult<boolean> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n const attached = this.pond.isAttached(userId)\n log(`User ${userId} attached: ${attached}`)\n\n return {\n success: true,\n data: attached,\n }\n }\n\n /**\n * Manually detach a user from the cache\n */\n async detachUser(userId: string): Promise<MCPResult<void>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Detaching user ${userId}`)\n const startTime = Date.now()\n\n const result = await this.pond.detachUser(userId)\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * List all currently cached users\n */\n listUsers(): MCPResult<{ users: string[]; count: number; maxActiveUsers: number; utilizationPercent: number }> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(\"Listing cached users\")\n const result = this.pond.listUsers()\n\n return {\n success: true,\n data: {\n users: result.users.toArray(), // Convert List<string> to string[]\n count: result.count,\n maxActiveUsers: result.maxActiveUsers,\n utilizationPercent: result.utilizationPercent,\n },\n }\n }\n\n /**\n * Close the DuckPond instance\n */\n async close(): Promise<MCPResult<void>> {\n if (!this.pond) {\n return { success: true, data: undefined }\n }\n\n log(\"Closing DuckPond...\")\n const startTime = Date.now()\n\n // Stop UI server if running\n if (this.currentUIUserId) {\n await this.stopUI()\n }\n\n const result = await this.pond.close()\n this.initialized = false\n this.pond = null\n\n return this.handleEither(result, Date.now() - startTime)\n }\n\n /**\n * Start DuckDB UI for a specific user\n * Only one user's UI can be active at a time\n */\n async startUI(userId: string): Promise<MCPResult<{ port: number }>> {\n if (!this.pond) {\n return this.notInitializedError()\n }\n\n log(`Starting UI for user ${userId}`)\n const startTime = Date.now()\n\n // Stop existing UI if different user\n if (this.currentUIUserId && this.currentUIUserId !== userId) {\n log(`Stopping UI for previous user ${this.currentUIUserId}`)\n await this.stopUI()\n }\n\n // If already running for this user, just return success\n if (this.currentUIUserId === userId) {\n log(`UI already running for user ${userId}`)\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n // Install and load UI extension\n const installResult = await this.execute(userId, \"INSTALL ui; LOAD ui;\")\n if (!installResult.success) {\n log(`Failed to install UI extension: ${installResult.error.message}`)\n return installResult as MCPResult<{ port: number }>\n }\n\n // Set the UI port\n const portResult = await this.execute(userId, `SET ui_local_port = ${this.uiInternalPort}`)\n if (!portResult.success) {\n log(`Failed to set UI port: ${portResult.error.message}`)\n return portResult as MCPResult<{ port: number }>\n }\n\n // Start the UI server (without opening browser)\n const startResult = await this.execute(userId, \"CALL start_ui_server()\")\n if (!startResult.success) {\n log(`Failed to start UI server: ${startResult.error.message}`)\n return startResult as MCPResult<{ port: number }>\n }\n\n this.currentUIUserId = userId\n this.startUIKeepalive(userId)\n log(`UI started for user ${userId} on port ${this.uiInternalPort}`)\n\n return {\n success: true,\n data: { port: this.uiInternalPort },\n executionTime: Date.now() - startTime,\n }\n }\n\n /**\n * Start keepalive timer to prevent UI user from being evicted\n */\n private startUIKeepalive(userId: string): void {\n this.stopUIKeepalive()\n log(`Starting UI keepalive for user ${userId}`)\n this.uiKeepaliveTimer = setInterval(async () => {\n if (this.pond && this.currentUIUserId === userId) {\n log(`UI keepalive ping for user ${userId}`)\n await this.pond.query(userId, \"SELECT 1\")\n }\n }, this.UI_KEEPALIVE_INTERVAL)\n }\n\n /**\n * Stop the keepalive timer\n */\n private stopUIKeepalive(): void {\n if (this.uiKeepaliveTimer) {\n log(\"Stopping UI keepalive\")\n clearInterval(this.uiKeepaliveTimer)\n this.uiKeepaliveTimer = null\n }\n }\n\n /**\n * Stop the currently running DuckDB UI\n */\n async stopUI(): Promise<MCPResult<void>> {\n this.stopUIKeepalive()\n\n if (!this.currentUIUserId) {\n log(\"No UI running to stop\")\n return { success: true, data: undefined }\n }\n\n if (!this.pond) {\n this.currentUIUserId = null\n return { success: true, data: undefined }\n }\n\n log(`Stopping UI for user ${this.currentUIUserId}`)\n const startTime = Date.now()\n\n const result = await this.execute(this.currentUIUserId, \"CALL stop_ui_server()\")\n const previousUser = this.currentUIUserId\n this.currentUIUserId = null\n\n if (result.success) {\n log(`UI stopped for user ${previousUser}`)\n return {\n success: true,\n data: undefined,\n executionTime: Date.now() - startTime,\n }\n }\n\n log(`Failed to stop UI: ${result.error.message}`)\n return result\n }\n\n /**\n * Get the user ID for the currently running UI\n */\n getCurrentUIUser(): string | null {\n return this.currentUIUserId\n }\n\n /**\n * Set the internal port for DuckDB UI\n */\n setUIPort(port: number): void {\n this.uiInternalPort = port\n }\n\n /**\n * Get the internal port for DuckDB UI\n */\n getUIPort(): number {\n return this.uiInternalPort\n }\n\n /**\n * Convert Either<Error, T> to MCPResult<T>\n */\n private handleEither<T>(\n result: Either<{ code: ErrorCode; message: string; cause?: Error; context?: Record<string, unknown> }, T>,\n executionTime: number,\n ): MCPResult<T> {\n return result.fold(\n (error) => ({\n success: false as const,\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 as const,\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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"}
|
package/dist/server-core.d.ts
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DuckPondConfig, ErrorCode, UserStats } from "duckpond";
|
|
2
|
+
|
|
3
|
+
//#region src/server-core.d.ts
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Result type for MCP tool responses
|
|
5
7
|
*/
|
|
6
8
|
type MCPResult<T> = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
success: true;
|
|
10
|
+
data: T;
|
|
11
|
+
executionTime?: number;
|
|
10
12
|
} | {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
13
|
+
success: false;
|
|
14
|
+
error: {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
details?: {
|
|
18
|
+
originalCode?: ErrorCode;
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
cause?: string;
|
|
20
21
|
};
|
|
22
|
+
};
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* Configuration for DuckPond MCP Server
|
|
24
26
|
* Extends DuckPondConfig with server-specific options
|
|
25
27
|
*/
|
|
26
28
|
type DuckPondServerConfig = DuckPondConfig & {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
/** Directory for persistent database storage (default: ~/.duckpond/data) */
|
|
30
|
+
dataDir?: string;
|
|
29
31
|
};
|
|
30
32
|
/**
|
|
31
33
|
* Core DuckPond MCP Server
|
|
@@ -33,94 +35,95 @@ type DuckPondServerConfig = DuckPondConfig & {
|
|
|
33
35
|
* Wraps DuckPond library with MCP-compatible result types
|
|
34
36
|
*/
|
|
35
37
|
declare class DuckPondServer {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
38
|
+
private config;
|
|
39
|
+
private pond;
|
|
40
|
+
private initialized;
|
|
41
|
+
private currentUIUserId;
|
|
42
|
+
private uiInternalPort;
|
|
43
|
+
private uiKeepaliveTimer;
|
|
44
|
+
private readonly UI_KEEPALIVE_INTERVAL;
|
|
45
|
+
constructor(config: DuckPondServerConfig);
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the DuckPond instance
|
|
48
|
+
*/
|
|
49
|
+
init(): Promise<MCPResult<void>>;
|
|
50
|
+
/**
|
|
51
|
+
* Execute a SQL query for a user
|
|
52
|
+
*/
|
|
53
|
+
query<T = unknown>(userId: string, sql: string): Promise<MCPResult<T[]>>;
|
|
54
|
+
/**
|
|
55
|
+
* Execute DDL/DML without returning results
|
|
56
|
+
*/
|
|
57
|
+
execute(userId: string, sql: string): Promise<MCPResult<void>>;
|
|
58
|
+
/**
|
|
59
|
+
* Get statistics about a user's database
|
|
60
|
+
*/
|
|
61
|
+
getUserStats(userId: string): Promise<MCPResult<UserStats>>;
|
|
62
|
+
/**
|
|
63
|
+
* Check if a user is currently cached
|
|
64
|
+
*/
|
|
65
|
+
isAttached(userId: string): MCPResult<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Manually detach a user from the cache
|
|
68
|
+
*/
|
|
69
|
+
detachUser(userId: string): Promise<MCPResult<void>>;
|
|
70
|
+
/**
|
|
71
|
+
* List all currently cached users
|
|
72
|
+
*/
|
|
73
|
+
listUsers(): MCPResult<{
|
|
74
|
+
users: string[];
|
|
75
|
+
count: number;
|
|
76
|
+
maxActiveUsers: number;
|
|
77
|
+
utilizationPercent: number;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Close the DuckPond instance
|
|
81
|
+
*/
|
|
82
|
+
close(): Promise<MCPResult<void>>;
|
|
83
|
+
/**
|
|
84
|
+
* Start DuckDB UI for a specific user
|
|
85
|
+
* Only one user's UI can be active at a time
|
|
86
|
+
*/
|
|
87
|
+
startUI(userId: string): Promise<MCPResult<{
|
|
88
|
+
port: number;
|
|
89
|
+
}>>;
|
|
90
|
+
/**
|
|
91
|
+
* Start keepalive timer to prevent UI user from being evicted
|
|
92
|
+
*/
|
|
93
|
+
private startUIKeepalive;
|
|
94
|
+
/**
|
|
95
|
+
* Stop the keepalive timer
|
|
96
|
+
*/
|
|
97
|
+
private stopUIKeepalive;
|
|
98
|
+
/**
|
|
99
|
+
* Stop the currently running DuckDB UI
|
|
100
|
+
*/
|
|
101
|
+
stopUI(): Promise<MCPResult<void>>;
|
|
102
|
+
/**
|
|
103
|
+
* Get the user ID for the currently running UI
|
|
104
|
+
*/
|
|
105
|
+
getCurrentUIUser(): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* Set the internal port for DuckDB UI
|
|
108
|
+
*/
|
|
109
|
+
setUIPort(port: number): void;
|
|
110
|
+
/**
|
|
111
|
+
* Get the internal port for DuckDB UI
|
|
112
|
+
*/
|
|
113
|
+
getUIPort(): number;
|
|
114
|
+
/**
|
|
115
|
+
* Convert Either<Error, T> to MCPResult<T>
|
|
116
|
+
*/
|
|
117
|
+
private handleEither;
|
|
118
|
+
/**
|
|
119
|
+
* Map DuckPond ErrorCode to MCP error code
|
|
120
|
+
*/
|
|
121
|
+
private mapErrorCode;
|
|
122
|
+
/**
|
|
123
|
+
* Helper for not initialized error
|
|
124
|
+
*/
|
|
125
|
+
private notInitializedError;
|
|
124
126
|
}
|
|
125
|
-
|
|
126
|
-
export { DuckPondServer,
|
|
127
|
+
//#endregion
|
|
128
|
+
export { DuckPondServer, DuckPondServerConfig, MCPResult };
|
|
129
|
+
//# sourceMappingURL=server-core.d.ts.map
|
package/dist/server-core.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//# sourceMappingURL=server-core.js.map
|
|
1
|
+
import"./logger-CX5FKgvo.js";import{t as e}from"./server-core-B66uY2zm.js";export{e as DuckPondServer};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import 'duckpond';
|
|
1
|
+
import { DuckPondServer, DuckPondServerConfig } from "./server-core.js";
|
|
2
|
+
import { FastMCP } from "@jordanburke/fastmcp";
|
|
4
3
|
|
|
4
|
+
//#region src/server.d.ts
|
|
5
5
|
type OAuthConfig = {
|
|
6
|
-
|
|
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?: {
|
|
7
20
|
username: string;
|
|
8
21
|
password: string;
|
|
9
|
-
userId
|
|
22
|
+
userId?: string;
|
|
10
23
|
email?: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
oauth?: OAuthConfig;
|
|
19
|
-
basicAuth?: {
|
|
20
|
-
username: string;
|
|
21
|
-
password: string;
|
|
22
|
-
userId?: string;
|
|
23
|
-
email?: string;
|
|
24
|
-
};
|
|
25
|
-
ui?: {
|
|
26
|
-
enabled: boolean;
|
|
27
|
-
port: number;
|
|
28
|
-
internalPort?: number;
|
|
29
|
-
autoStartUser?: string;
|
|
30
|
-
};
|
|
24
|
+
};
|
|
25
|
+
ui?: {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
port: number;
|
|
28
|
+
internalPort?: number;
|
|
29
|
+
autoStartUser?: string;
|
|
30
|
+
};
|
|
31
31
|
};
|
|
32
32
|
declare function createFastMCPServer(options: FastMCPServerOptions): {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
server: FastMCP;
|
|
34
|
+
duckpond: DuckPondServer;
|
|
35
35
|
};
|
|
36
36
|
declare function startServer(options: FastMCPServerOptions, transport: "stdio" | "http"): Promise<void>;
|
|
37
|
-
|
|
38
|
-
export {
|
|
37
|
+
//#endregion
|
|
38
|
+
export { FastMCPServerOptions, OAuthConfig, createFastMCPServer, startServer };
|
|
39
|
+
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,48 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{n as e}from"./logger-CX5FKgvo.js";import{detachUserSchema as t,executeSchema as n,getUserStatsSchema as r,isAttachedSchema as i,listUsersSchema as a,querySchema as o,resolveUserId as s}from"./tools/index.js";import{t as c}from"./server-core-B66uY2zm.js";import{t as l}from"./ui-server-BoQ-kekq.js";import{createHash as u,randomBytes as d,webcrypto as f}from"crypto";import{createRequire as p}from"module";import{FastMCP as m}from"@jordanburke/fastmcp";import*as h from"jsonwebtoken";import{URL as g}from"url";globalThis.crypto||(globalThis.crypto=f);const _=p(import.meta.url)(`../package.json`),v=e.fastmcp,y=process.env.DUCKPOND_JWT_SECRET||d(32).toString(`hex`),b=process.env.DUCKPOND_JWT_EXPIRES_IN?parseInt(process.env.DUCKPOND_JWT_EXPIRES_IN,10):365*24*60*60,x=new Map,S=new Map;function C(e){v(`🚀 Initializing FastMCP server...`);let l=new c(e.config),u={name:`duckpond`,version:_.version,health:{enabled:!0,path:`/health`,status:200,message:JSON.stringify({status:`healthy`,service:`duckpond-mcp-server`,version:_.version,timestamp:new Date().toISOString()})}},d=e.oauth?.enabled||e.basicAuth?new m({...u,oauth:{enabled:!0,authorizationServer:{issuer:e.oauth?.issuer||`http://localhost:${e.port||3e3}`,authorizationEndpoint:`${e.oauth?.issuer||`http://localhost:${e.port||3e3}`}/oauth/authorize`,tokenEndpoint:`${e.oauth?.issuer||`http://localhost:${e.port||3e3}`}/oauth/token`,jwksUri:`${e.oauth?.issuer||`http://localhost:${e.port||3e3}`}/oauth/jwks`,registrationEndpoint:`${e.oauth?.issuer||`http://localhost:${e.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||e.oauth?.resource||`${e.oauth?.issuer||`http://localhost:${e.port||3e3}`}/mcp`,authorizationServers:[e.oauth?.issuer||`http://localhost:${e.port||3e3}`]}},authenticate:t=>{let n=t.headers?.authorization,r=e.oauth?.issuer||`http://localhost:${e.port||3e3}`;if(!n)throw e.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="${r}/oauth/authorize", resource="${r}/.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(e.basicAuth&&n.startsWith(`Basic `)){let[t,r]=Buffer.from(n.slice(6),`base64`).toString(`utf-8`).split(`:`);if(t===e.basicAuth.username&&r===e.basicAuth.password)return Promise.resolve({userId:e.basicAuth.userId||t,email:e.basicAuth.email||`${t}@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(e.oauth?.enabled&&n.startsWith(`Bearer `)){let t=n.slice(7);try{let n=h.verify(t,y);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 i=e.oauth?.resource||`${r}/mcp`;if(n.aud&&n.aud!==i)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(e){throw e instanceof Response?e: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="${r}/oauth/authorize", resource="${r}/.well-known/oauth-protected-resource"`}})}}):new m(u),f=(e,t)=>{if(typeof t==`bigint`)return Number.isSafeInteger(Number(t))?Number(t):t.toString();if(t&&typeof t==`object`&&`micros`in t){let e=t.micros,n=typeof e==`bigint`?Number(e/1000n):Number(e)/1e3;return new Date(n).toISOString()}return t};d.addTool({name:`query`,description:`Execute a SQL query for a specific user and return results`,parameters:o,execute:async e=>{try{let t=s(e.userId),n=await l.query(t,e.sql);return n.success?JSON.stringify({rows:n.data,rowCount:n.data.length,executionTime:n.executionTime},f,2):`ERROR: ${n.error.message}`}catch(e){v(`Error in query tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),d.addTool({name:`execute`,description:`Execute SQL statement (DDL/DML) for a specific user without returning results`,parameters:n,execute:async e=>{try{let t=s(e.userId),n=await l.execute(t,e.sql);return n.success?JSON.stringify({success:!0,message:`Statement executed successfully`,executionTime:n.executionTime},null,2):`ERROR: ${n.error.message}`}catch(e){v(`Error in execute tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),d.addTool({name:`getUserStats`,description:`Get statistics about a user's database (memory usage, query count, etc.)`,parameters:r,execute:async e=>{try{let t=s(e.userId),n=await l.getUserStats(t);return n.success?JSON.stringify({...n.data,lastAccess:n.data.lastAccess.toISOString()},null,2):`ERROR: ${n.error.message}`}catch(e){v(`Error in getUserStats tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),d.addTool({name:`isAttached`,description:`Check if a user's database is currently cached in memory`,parameters:i,execute:async e=>{try{let t=s(e.userId),n=l.isAttached(t);return n.success?JSON.stringify({attached:n.data,userId:t},null,2):`ERROR: ${n.error.message}`}catch(e){v(`Error in isAttached tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),d.addTool({name:`detachUser`,description:`Manually detach a user's database from the cache to free resources`,parameters:t,execute:async e=>{try{let t=s(e.userId),n=await l.detachUser(t);return n.success?JSON.stringify({success:!0,message:`User ${t} detached successfully`},null,2):`ERROR: ${n.error.message}`}catch(e){v(`Error in detachUser tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),d.addTool({name:`listUsers`,description:`List all currently cached users and cache statistics`,parameters:a,execute:async()=>{try{let e=l.listUsers();return e.success?JSON.stringify(e.data,null,2):`ERROR: ${e.error.message}`}catch(e){v(`Error in listUsers tool:`,e);let t=e instanceof Error?e.message:String(e);return`ERROR: ${JSON.stringify({error:t},null,2)}`}}}),e.oauth?.enabled&&w(d,e);let p=d.getApp();return p.get(`/`,t=>{let n=e.oauth?.issuer||`http://localhost:${e.port||3e3}`,r={name:`DuckPond MCP Server`,version:_.version,description:`Model Context Protocol server for multi-tenant DuckDB with R2/S3 storage`,service:`duckpond-mcp-server`,capabilities:{tools:[`query`,`execute`,`getUserStats`,`isAttached`,`detachUser`,`listUsers`],transports:[`stdio`,`http`],authentication:{oauth:e.oauth?.enabled||!1,basicAuth:!!e.basicAuth}},endpoints:{mcp:`${n}${e.endpoint||`/mcp`}`,health:`${n}/health`,ui:`${n}/ui/:userId`,...e.oauth?.enabled&&{oauth:{authorization:`${n}/oauth/authorize`,token:`${n}/oauth/token`,jwks:`${n}/oauth/jwks`,register:`${n}/oauth/register`}}},timestamp:new Date().toISOString()};return t.json(r)}),T(p,l,e),v(`✓ FastMCP server created`),{server:d,duckpond:l}}function w(e,t){let n=e.getApp();setInterval(()=>{let e=Date.now();for(let[t,n]of x.entries())e-n.createdAt>6e5&&x.delete(t);for(let[t,n]of S.entries())e-n.createdAt>2592e6&&S.delete(t)},6e4),n.get(`/oauth/authorize`,e=>{let t=e.req.query(),n=t.response_type,r=t.redirect_uri,i=t.state,a=t.code_challenge,o=t.code_challenge_method,s=t.client_id;if(n!==`code`)return e.json({error:`unsupported_response_type`,error_description:`Only 'code' response type is supported`},400);if(!r)return e.json({error:`invalid_request`,error_description:`redirect_uri is required`},400);if(a&&(!o||![`S256`,`plain`].includes(o)))return e.json({error:`invalid_request`,error_description:`Invalid code_challenge_method. Only 'S256' and 'plain' are supported`},400);let c=`
|
|
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>🔐 OAuth Authorization</h3>
|
|
19
|
+
<p><strong>Application:</strong> ${s||`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="${n}">
|
|
25
|
+
<input type="hidden" name="redirect_uri" value="${r}">
|
|
26
|
+
<input type="hidden" name="state" value="${i||``}">
|
|
27
|
+
<input type="hidden" name="code_challenge" value="${a||``}">
|
|
28
|
+
<input type="hidden" name="code_challenge_method" value="${o||``}">
|
|
29
|
+
<input type="hidden" name="client_id" value="${s||``}">
|
|
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 e.html(c)}),n.post(`/oauth/authorize`,async e=>{try{let n=await e.req.text(),r=new URLSearchParams(n),i=r.get(`username`),a=r.get(`password`),o=r.get(`redirect_uri`),s=r.get(`state`),c=r.get(`code_challenge`),l=r.get(`code_challenge_method`);if(i!==t.oauth?.username||a!==t.oauth?.password)return e.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">❌ Invalid username or password</div><a href="javascript:history.back()">← Try Again</a></body></html>`,401);let u=d(16).toString(`hex`);x.set(u,{createdAt:Date.now(),redirectUri:o||``,codeChallenge:c||void 0,codeChallengeMethod:l||void 0,userId:t.oauth?.userId||i||`oauth-user`});let f=new g(o||``);return f.searchParams.set(`code`,u),s&&f.searchParams.set(`state`,s),e.redirect(f.toString(),302)}catch{return e.json({error:`invalid_request`,error_description:`Failed to process authorization request`},400)}}),n.post(`/oauth/token`,async e=>{let n=await e.req.text(),r=new URLSearchParams(n),i=r.get(`grant_type`),a=r.get(`code`),o=r.get(`redirect_uri`),s=r.get(`code_verifier`),c=r.get(`refresh_token`);if(i===`refresh_token`){if(!c)return e.json({error:`invalid_request`,error_description:`refresh_token is required for refresh_token grant type`},400);let n=S.get(c);if(!n)return e.json({error:`invalid_grant`,error_description:`Invalid or expired refresh token`},400);S.delete(c);let r={sub:n.userId,email:n.email||``,scope:`read write`,iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+b,iss:t.oauth?.issuer||`http://localhost:${t.port||3e3}`,aud:t.oauth?.resource||`${t.oauth?.issuer||`http://localhost:${t.port||3e3}`}/mcp`},i=d(32).toString(`hex`);S.set(i,{createdAt:Date.now(),userId:n.userId,email:n.email});let a=h.sign(r,y);return e.json({access_token:a,token_type:`Bearer`,expires_in:b,scope:`read write`,refresh_token:i})}if(i!==`authorization_code`)return e.json({error:`unsupported_grant_type`,error_description:`Only 'authorization_code' and 'refresh_token' grant types are supported`},400);let l=x.get(a||``);if(!l)return e.json({error:`invalid_grant`,error_description:`Invalid or expired authorization code`},400);if(l.redirectUri&&l.redirectUri!==o)return e.json({error:`invalid_grant`,error_description:`redirect_uri mismatch`},400);if(l.codeChallenge){if(!s)return e.json({error:`invalid_grant`,error_description:`code_verifier is required when code_challenge was used`},400);let t;if(t=l.codeChallengeMethod===`S256`?u(`sha256`).update(s).digest().toString(`base64url`):s,t!==l.codeChallenge)return e.json({error:`invalid_grant`,error_description:`Invalid code_verifier`},400)}x.delete(a);let f={sub:l.userId,email:t.oauth?.email||``,scope:`read write`,iat:Math.floor(Date.now()/1e3),exp:Math.floor(Date.now()/1e3)+b,iss:t.oauth?.issuer||`http://localhost:${t.port||3e3}`,aud:t.oauth?.resource||`${t.oauth?.issuer||`http://localhost:${t.port||3e3}`}/mcp`},p=d(32).toString(`hex`);S.set(p,{createdAt:Date.now(),userId:l.userId,email:t.oauth?.email});let m=h.sign(f,y);return e.json({access_token:m,token_type:`Bearer`,expires_in:b,scope:`read write`,refresh_token:p})}),n.get(`/oauth/jwks`,e=>e.json({keys:[{kty:`oct`,use:`sig`,kid:`duckpond-hmac-key`,alg:`HS256`}]})),n.post(`/oauth/register`,async e=>{try{let t={};try{let n=await e.req.text();if(n&&n!==`[object Object]`)try{t=JSON.parse(n)}catch{t=Object.fromEntries(new URLSearchParams(n))}}catch(e){v(`Error parsing request body:`,e)}let n={client_id:`client-${d(8).toString(`hex`)}`,client_secret:d(16).toString(`hex`),client_id_issued_at:Math.floor(Date.now()/1e3),client_secret_expires_at:0,grant_types:t.grant_types||[`authorization_code`],response_types:t.response_types||[`code`],redirect_uris:t.redirect_uris||[],token_endpoint_auth_method:t.token_endpoint_auth_method||`client_secret_post`};return t.client_name&&(n.client_name=t.client_name),t.scope&&(n.scope=t.scope),e.json(n,201)}catch(t){return e.json({error:`invalid_client_metadata`,error_description:`Invalid client registration request: `+(t instanceof Error?t.message:String(t))},400)}}),v(`✓ OAuth flow endpoints added`)}function T(e,t,n){let r=n.oauth?.issuer||`http://localhost:${n.port||3e3}`,i=t.getUIPort();e.get(`/ui`,e=>{let n=t.getCurrentUIUser(),a=t.listUsers();return e.json({message:n?`UI active for user: ${n}. Access directly at http://localhost:${i}`:`No UI active. Visit /ui/:userId to start DuckDB UI for a user.`,currentUser:n,uiUrl:n?`http://localhost:${i}`:null,availableUsers:a.success?a.data.users:[],endpoints:{startUI:`${r}/ui/:userId`}})}),e.get(`/ui/:userId`,async e=>{let n=e.req.param(`userId`);v(`Starting UI for user: ${n}`);let r=await t.startUI(n);return r.success?e.json({success:!0,message:`UI started for user: ${n}`,uiUrl:`http://localhost:${i}`,hint:`Access the DuckDB UI directly at the uiUrl above`}):e.json({error:`Failed to start UI`,message:r.error.message,details:r.error.details},500)}),v(`✓ UI endpoints added`)}async function E(e,t){let{server:n,duckpond:r}=C(e),i=await r.init();if(!i.success)throw Error(`Failed to initialize DuckPond: ${i.error.message}`);if(v(`DuckPond initialized successfully`),e.ui?.internalPort&&r.setUIPort(e.ui.internalPort),t===`stdio`){if(await n.start({transportType:`stdio`}),v(`✓ FastMCP server running with stdio transport`),e.ui?.enabled)if(e.ui.autoStartUser){v(`Auto-starting UI for user: ${e.ui.autoStartUser}`);let t=await r.startUI(e.ui.autoStartUser);t.success?console.error(`🖥️ DuckDB UI running at http://localhost:${r.getUIPort()}`):v(`Failed to auto-start UI: ${t.error.message}`)}else await l({port:e.ui.port,duckpond:r})}else await n.start({transportType:`httpStream`,httpStream:{port:e.port||3e3,endpoint:e.endpoint||`/mcp`}}),v(`✓ FastMCP server running on http://0.0.0.0:${e.port||3e3}${e.endpoint||`/mcp`}`),v(`🔌 Connect with StreamableHTTPClientTransport`);process.on(`SIGINT`,async()=>{v(`Received SIGINT, closing server...`),await r.close(),process.exit(0)}),process.on(`SIGTERM`,async()=>{v(`Received SIGTERM, closing server...`),await r.close(),process.exit(0)})}export{C as createFastMCPServer,E as startServer};
|
|
2
48
|
//# sourceMappingURL=server.js.map
|