duckpond-mcp-server 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # DuckPond MCP Server
2
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)
3
+ [![CI](https://github.com/jordanburke/duckpond/actions/workflows/ci.yml/badge.svg)](https://github.com/jordanburke/duckpond/actions/workflows/ci.yml)
4
+ [![CodeQL](https://github.com/jordanburke/duckpond/actions/workflows/codeql.yml/badge.svg)](https://github.com/jordanburke/duckpond/actions/workflows/codeql.yml)
5
5
 
6
6
  **Model Context Protocol (MCP) server for multi-tenant DuckDB management with R2/S3 cloud storage.**
7
7
 
@@ -506,7 +506,7 @@ The MCP server is a **thin transport layer** over the [duckpond](https://github.
506
506
  └───────┬────────┘
507
507
 
508
508
  ┌───────▼────────┐
509
- │ DuckPond │ npm: duckpond@^0.1.0
509
+ │ DuckPond │ workspace:^ duckpond
510
510
  │ - Multi-tenant │
511
511
  │ - LRU Cache │
512
512
  │ - R2/S3 │
@@ -520,11 +520,12 @@ The MCP server is a **thin transport layer** over the [duckpond](https://github.
520
520
 
521
521
  ### Key Components
522
522
 
523
- - **`src/index.ts`** - CLI entry point, transport selection
524
- - **`src/server-core.ts`** - DuckPond wrapper with MCP result types
525
- - **`src/server-stdio.ts`** - stdio transport for Claude Desktop
526
- - **`src/server-fastmcp.ts`** - HTTP transport with FastMCP
527
- - **`src/tools/index.ts`** - MCP tool schemas and implementations
523
+ - **`src/index.ts`** - CLI entry point (commander), transport selection
524
+ - **`src/server-core.ts`** - DuckPond wrapper exposing `MCPResult<T>` tool result types
525
+ - **`src/server.ts`** - FastMCP-based HTTP transport with OAuth 2.0 / JWT auth
526
+ - **`src/ui-server.ts`** - Hono server bridging to the built-in DuckDB UI
527
+ - **`src/tools/index.ts`** - MCP tool schemas and implementations (zod)
528
+ - **`src/lib.ts`** - Library exports (`startServer`, hooks) for extending the server (`duckpond-mcp-server/lib`)
528
529
 
529
530
  ### Error Handling
530
531
 
@@ -636,7 +637,7 @@ console.log(`Memory: ${stats.memoryUsage} bytes`)
636
637
 
637
638
  ## Contributing
638
639
 
639
- Contributions welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
640
+ Contributions welcome! This package lives in the [duckpond monorepo](https://github.com/jordanburke/duckpond) — see the monorepo README for development setup. Please ensure `pnpm validate` passes before opening a PR.
640
641
 
641
642
  ## License
642
643
 
@@ -652,4 +653,4 @@ MIT
652
653
 
653
654
  - **Issues**: [GitHub Issues](https://github.com/jordanburke/duckpond-mcp-server/issues)
654
655
  - **Discussions**: [GitHub Discussions](https://github.com/jordanburke/duckpond-mcp-server/discussions)
655
- - **Documentation**: [docs/](./docs/)
656
+ - **Documentation**: [docs/](https://github.com/jordanburke/duckpond/tree/main/packages/duckpond-mcp-server/docs)
@@ -0,0 +1,23 @@
1
+ import { DuckPondServerConfig } from "../server-core.js";
2
+
3
+ //#region src/config/path-resolver.d.ts
4
+ /**
5
+ * Default local data directory, used only when DUCKPOND_DATA_DIR is unset.
6
+ */
7
+ declare function getDefaultDataDir(): string;
8
+ /**
9
+ * Resolve a raw DUCKPOND_DATA_DIR value to an absolute path.
10
+ *
11
+ * Expands `~`, then platform tokens (`$OneDrive`, `$WINHOME`, …), then any
12
+ * remaining real `$VAR`/`${VAR}` env references. Throws with a descriptive
13
+ * message if a platform token is present but cannot be resolved — failing fast
14
+ * is preferable to silently creating a database in the wrong location.
15
+ */
16
+ declare function resolveDataDir(raw: string): string;
17
+ /**
18
+ * Parse environment variables into DuckPond configuration.
19
+ */
20
+ declare function getConfigFromEnv(): DuckPondServerConfig;
21
+ //#endregion
22
+ export { getConfigFromEnv, getDefaultDataDir, resolveDataDir };
23
+ //# sourceMappingURL=path-resolver.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{basename as e}from"node:path";import{Env as t,Path as n,Platform as r}from"functype-os";function i(){return`${process.env.HOME??process.env.USERPROFILE??`.`}/.duckpond/data`}function a(e){for(let n of e){let e=t.get(n).orElse(``);if(e!==``)return e}}function o(t){return/\bOneDrive\s+-\s+/i.test(e(t))}function s(e,t){let n=r.cloudStorageDirs().toArray().filter(t=>t.provider===e).map(e=>e.path),i=t===void 0?n:n.filter(t),a=i.length>0?i:n;if(a.length===0)return;let o=r.windowsHomeDir().orUndefined();return(o===void 0?void 0:a.find(e=>e.startsWith(o)))??a[0]}function c(){return s(`onedrive`,e=>!o(e))??a([`OneDrive`,`OneDriveConsumer`])}function l(){return s(`onedrive`,o)??a([`OneDriveCommercial`])}const u=[{token:`WINHOME`,resolve:()=>r.windowsHomeDir().orElse(r.homeDir())},{token:`OneDriveCommercial`,resolve:l},{token:`OneDriveConsumer`,resolve:c},{token:`OneDrive`,resolve:c},{token:`GOOGLE_DRIVE`,resolve:()=>s(`gdrive`)??a([`GOOGLE_DRIVE`])},{token:`DROPBOX`,resolve:()=>s(`dropbox`)??a([`DROPBOX`,`DROPBOX_PATH`])}];function d(e,t){return RegExp(`\\$\\{?${t}\\b`,`i`).test(e)}function f(e,t,n){return e.replace(RegExp(`\\$\\{${t}\\}`,`gi`),n).replace(RegExp(`\\$${t}\\b`,`gi`),n)}function p(){return`homeDirs=[${r.homeDirs().toArray().join(`, `)}] cloudStorageDirs=[${r.cloudStorageDirs().toArray().map(e=>`${e.provider}:${e.path}`).join(`, `)}]`}function m(e){let t=n.expandTilde(e),r=u.reduce((t,{token:n,resolve:r})=>{if(!d(t,n))return t;let i=r();if(i===void 0||i===``)throw Error(`Cannot resolve "$${n}" in DUCKPOND_DATA_DIR="${e}". Searched ${p()}. Set the $${n} environment variable or use an explicit absolute path.`);return f(t,n,i)},t);return n.expandVars(r).fold(()=>r,e=>e)}function h(){let e=m(process.env.DUCKPOND_DATA_DIR??i()),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}export{h as getConfigFromEnv,i as getDefaultDataDir,m as resolveDataDir};
2
+ //# sourceMappingURL=path-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-resolver.js","names":[],"sources":["../../src/config/path-resolver.ts"],"sourcesContent":["/**\n * Cross-platform resolution of DUCKPOND_DATA_DIR.\n *\n * Beyond plain `~` expansion, this understands platform \"tokens\" that let a\n * single config value resolve correctly on macOS, native Windows, and WSL:\n *\n * $WINHOME → the Windows home dir (on WSL, discovered under /mnt/c/Users)\n * $OneDrive → the OneDrive root (auto-discovered, else the OneDrive* env var)\n * $OneDriveCommercial → \"\n * $OneDriveConsumer → \"\n * $GOOGLE_DRIVE → the Google Drive root\n * $DROPBOX → the Dropbox root\n *\n * These tokens are NOT standard env vars on WSL, so functype-os's plain\n * `expandVars` can't resolve them — we map them to `Platform` discovery here.\n * Tokens survive Claude Code's `.mcp.json` expansion (it only expands `${VAR}`),\n * so `DUCKPOND_DATA_DIR=$OneDrive/Apps/duckdb` reaches the server intact.\n *\n * Mirrors the discovery convention in envpkt (src/core/config.ts).\n */\nimport { basename } from \"node:path\"\n\nimport { type CloudProvider, Env, Path, Platform } from \"functype-os\"\n\nimport type { DuckPondServerConfig } from \"../server-core\"\n\n/**\n * Default local data directory, used only when DUCKPOND_DATA_DIR is unset.\n */\nexport function getDefaultDataDir(): string {\n const home = process.env.HOME ?? process.env.USERPROFILE ?? \".\"\n return `${home}/.duckpond/data`\n}\n\n/** First non-empty value among the given env var names. */\nfunction firstEnv(names: ReadonlyArray<string>): string | undefined {\n for (const name of names) {\n const value = Env.get(name).orElse(\"\")\n if (value !== \"\") {\n return value\n }\n }\n return undefined\n}\n\n/** A commercial/work OneDrive has an org suffix, e.g. \"OneDrive - Contoso\". */\nfunction isCommercialOneDrive(p: string): boolean {\n return /\\bOneDrive\\s+-\\s+/i.test(basename(p))\n}\n\n/**\n * Pick the best discovered dir for a cloud provider. `homeDirs()` is scanned in\n * order (on WSL: Linux home first, then Windows home), so a provider can appear\n * multiple times. We prefer the copy under the Windows home on WSL — that's the\n * real synced location — and otherwise take the first match.\n */\nfunction selectCloud(provider: CloudProvider, filter?: (p: string) => boolean): string | undefined {\n const all = Platform.cloudStorageDirs()\n .toArray()\n .filter((d) => d.provider === provider)\n .map((d) => d.path)\n const matched = filter === undefined ? all : all.filter(filter)\n const pool = matched.length > 0 ? matched : all\n if (pool.length === 0) {\n return undefined\n }\n const winHome = Platform.windowsHomeDir().orUndefined()\n const winPreferred = winHome === undefined ? undefined : pool.find((p) => p.startsWith(winHome))\n return winPreferred ?? pool[0]\n}\n\nfunction oneDrivePersonal(): string | undefined {\n return selectCloud(\"onedrive\", (p) => !isCommercialOneDrive(p)) ?? firstEnv([\"OneDrive\", \"OneDriveConsumer\"])\n}\n\nfunction oneDriveCommercial(): string | undefined {\n return selectCloud(\"onedrive\", isCommercialOneDrive) ?? firstEnv([\"OneDriveCommercial\"])\n}\n\n/**\n * Platform tokens, ordered longest-first so a prefix token (e.g. `$OneDrive`)\n * never partially matches a longer one (`$OneDriveCommercial`). Each resolver\n * returns the absolute path, or undefined when it cannot be located.\n */\nconst PLATFORM_TOKENS: ReadonlyArray<{ token: string; resolve: () => string | undefined }> = [\n { token: \"WINHOME\", resolve: () => Platform.windowsHomeDir().orElse(Platform.homeDir()) },\n { token: \"OneDriveCommercial\", resolve: oneDriveCommercial },\n { token: \"OneDriveConsumer\", resolve: oneDrivePersonal },\n { token: \"OneDrive\", resolve: oneDrivePersonal },\n { token: \"GOOGLE_DRIVE\", resolve: () => selectCloud(\"gdrive\") ?? firstEnv([\"GOOGLE_DRIVE\"]) },\n { token: \"DROPBOX\", resolve: () => selectCloud(\"dropbox\") ?? firstEnv([\"DROPBOX\", \"DROPBOX_PATH\"]) },\n]\n\nfunction hasToken(input: string, token: string): boolean {\n return new RegExp(`\\\\$\\\\{?${token}\\\\b`, \"i\").test(input)\n}\n\nfunction replaceToken(input: string, token: string, value: string): string {\n return input.replace(new RegExp(`\\\\$\\\\{${token}\\\\}`, \"gi\"), value).replace(new RegExp(`\\\\$${token}\\\\b`, \"gi\"), value)\n}\n\nfunction describeSearch(): string {\n const homes = Platform.homeDirs().toArray().join(\", \")\n const clouds = Platform.cloudStorageDirs()\n .toArray()\n .map((d) => `${d.provider}:${d.path}`)\n .join(\", \")\n return `homeDirs=[${homes}] cloudStorageDirs=[${clouds}]`\n}\n\n/**\n * Resolve a raw DUCKPOND_DATA_DIR value to an absolute path.\n *\n * Expands `~`, then platform tokens (`$OneDrive`, `$WINHOME`, …), then any\n * remaining real `$VAR`/`${VAR}` env references. Throws with a descriptive\n * message if a platform token is present but cannot be resolved — failing fast\n * is preferable to silently creating a database in the wrong location.\n */\nexport function resolveDataDir(raw: string): string {\n const tildeExpanded = Path.expandTilde(raw)\n\n const withTokens = PLATFORM_TOKENS.reduce((acc, { token, resolve }) => {\n if (!hasToken(acc, token)) {\n return acc\n }\n const value = resolve()\n if (value === undefined || value === \"\") {\n throw new Error(\n `Cannot resolve \"$${token}\" in DUCKPOND_DATA_DIR=\"${raw}\". Searched ${describeSearch()}. ` +\n `Set the $${token} environment variable or use an explicit absolute path.`,\n )\n }\n return replaceToken(acc, token, value)\n }, tildeExpanded)\n\n // Expand any remaining real env vars; keep the literal if expansion fails.\n return Path.expandVars(withTokens).fold(\n () => withTokens,\n (expanded) => expanded,\n )\n}\n\n/**\n * Parse environment variables into DuckPond configuration.\n */\nexport function getConfigFromEnv(): DuckPondServerConfig {\n // Default to local disk storage (resolve ~, platform tokens, and env vars)\n const dataDir = resolveDataDir(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"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{loggers as e}from"./utils/logger.js";import{getDefaultUserId as t}from"./tools/index.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;process.env.DUCKPOND_BEARER_TOKEN&&(c={token:process.env.DUCKPOND_BEARER_TOKEN,userId:process.env.DUCKPOND_BEARER_TOKEN_USER_ID},console.error(`🔐 Bearer token authentication enabled`),c.userId&&console.error(` User ID: ${c.userId}`));let l=e.ui||process.env.DUCKPOND_UI_ENABLED===`true`,d=parseInt(e.uiPort)||4e3,f=parseInt(e.uiInternalPort)||4213;l&&e.transport===`stdio`&&(i?console.error(`🖥️ DuckDB UI will start at http://localhost:${f}`):(console.error(`🖥️ UI management server at http://localhost:${d}/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,bearerToken:c,ui:l?{enabled:!0,port:d,internalPort:f,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{};
2
+ import{loggers as e}from"./utils/logger.js";import{getDefaultUserId as t}from"./tools/index.js";import{getConfigFromEnv as n}from"./config/path-resolver.js";import{startServer as r}from"./server.js";import{webcrypto as i}from"crypto";import{Command as a}from"commander";import{createRequire as o}from"module";globalThis.crypto||(globalThis.crypto=i);const s=o(import.meta.url)(`../package.json`),c=e.main,l=new a;l.name(`duckpond-mcp-server`).description(`MCP server for multi-tenant DuckDB management with R2/S3 storage`).version(s.version).option(`-t, --transport <type>`,`Transport mode: stdio or http`,`stdio`).option(`-p, --port <port>`,`HTTP port (when using http transport)`,`3000`).option(`--ui`,`Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)`).option(`--ui-port <port>`,`UI management server port, only used when no default user (default: 4000)`,`4000`).option(`--ui-internal-port <port>`,`DuckDB UI port (default: 4213)`,`4213`).action(async e=>{try{let i=n(),a=t();c(`Starting DuckPond MCP Server with ${e.transport} transport`),c(`Configuration:`,{memoryLimit:i.memoryLimit,threads:i.threads,maxActiveUsers:i.maxActiveUsers,strategy:i.strategy,dataDir:i.dataDir,tempDir:i.tempDir,cacheDir:i.cacheDir,cacheType:i.cacheType,hasR2:!!i.r2,hasS3:!!i.s3,defaultUser:a||`(not set)`}),i.r2?console.error(`☁️ Storage: Cloudflare R2`):i.s3?console.error(`☁️ Storage: AWS S3`):console.error(`💾 Storage: Local disk (${i.dataDir})`),a&&console.error(`👤 Default user: ${a}`);let o;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)),o={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: ${o.username}`),console.error(` User ID: ${o.userId}`),console.error(` ✓ 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(`🔐 Basic authentication enabled`),console.error(` Username: ${s.username}`),console.error(` User ID: ${s.userId||s.username}`));let l;process.env.DUCKPOND_BEARER_TOKEN&&(l={token:process.env.DUCKPOND_BEARER_TOKEN,userId:process.env.DUCKPOND_BEARER_TOKEN_USER_ID},console.error(`🔐 Bearer token authentication enabled`),l.userId&&console.error(` User ID: ${l.userId}`));let u=e.ui||process.env.DUCKPOND_UI_ENABLED===`true`,d=parseInt(e.uiPort)||4e3,f=parseInt(e.uiInternalPort)||4213;u&&e.transport===`stdio`&&(a?console.error(`🖥️ DuckDB UI will start at http://localhost:${f}`):(console.error(`🖥️ UI management server at http://localhost:${d}/ui`),console.error(` Visit /ui/:userId to start DuckDB UI for a user`))),e.transport===`stdio`||e.transport===`http`?await r({config:i,port:parseInt(e.port)||3e3,endpoint:`/mcp`,oauth:o,basicAuth:s,bearerToken:l,ui:u?{enabled:!0,port:d,internalPort:f,autoStartUser:a}:void 0},e.transport===`stdio`?`stdio`:`http`):(c(`Unknown transport: ${e.transport}`),process.exit(1))}catch(e){c(`Fatal error:`,e),console.error(`Fatal error:`,e),process.exit(1)}}),l.parse();export{};
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"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 // Load Bearer Token configuration from environment variables\n let bearerTokenConfig: { token: string; userId?: string } | undefined\n if (process.env.DUCKPOND_BEARER_TOKEN) {\n bearerTokenConfig = {\n token: process.env.DUCKPOND_BEARER_TOKEN,\n userId: process.env.DUCKPOND_BEARER_TOKEN_USER_ID,\n }\n\n console.error(\"🔐 Bearer token authentication enabled\")\n if (bearerTokenConfig.userId) {\n console.error(` User ID: ${bearerTokenConfig.userId}`)\n }\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 bearerToken: bearerTokenConfig,\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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"}
1
+ {"version":3,"file":"index.js","names":[],"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 { getConfigFromEnv } from \"./config/path-resolver\"\nimport { startServer } from \"./server\"\nimport { startUIServer } from \"./ui-server\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\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 // Load Bearer Token configuration from environment variables\n let bearerTokenConfig: { token: string; userId?: string } | undefined\n if (process.env.DUCKPOND_BEARER_TOKEN) {\n bearerTokenConfig = {\n token: process.env.DUCKPOND_BEARER_TOKEN,\n userId: process.env.DUCKPOND_BEARER_TOKEN_USER_ID,\n }\n\n console.error(\"🔐 Bearer token authentication enabled\")\n if (bearerTokenConfig.userId) {\n console.error(` User ID: ${bearerTokenConfig.userId}`)\n }\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 bearerToken: bearerTokenConfig,\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 CHANGED
@@ -1,4 +1,5 @@
1
1
  import { DuckPondServer, DuckPondServerConfig } from "./server-core.js";
2
+ import { getConfigFromEnv, getDefaultDataDir, resolveDataDir } from "./config/path-resolver.js";
2
3
  import { BearerTokenConfig, FastMCPServerOptions, StartServerOptions, createFastMCPServer, startServer } from "./server.js";
3
4
  import { getDefaultUserId } from "./tools/index.js";
4
- export { type BearerTokenConfig, DuckPondServer, type DuckPondServerConfig, type FastMCPServerOptions, type StartServerOptions, createFastMCPServer, getDefaultUserId, startServer };
5
+ export { type BearerTokenConfig, DuckPondServer, type DuckPondServerConfig, type FastMCPServerOptions, type StartServerOptions, createFastMCPServer, getConfigFromEnv, getDefaultDataDir, getDefaultUserId, resolveDataDir, startServer };
package/dist/lib.js CHANGED
@@ -1 +1 @@
1
- import{getDefaultUserId as e}from"./tools/index.js";import{t}from"./server-core-Cj3cIk43.js";import{createFastMCPServer as n,startServer as r}from"./server.js";export{t as DuckPondServer,n as createFastMCPServer,e as getDefaultUserId,r as startServer};
1
+ import{getDefaultUserId as e}from"./tools/index.js";import{getConfigFromEnv as t,getDefaultDataDir as n,resolveDataDir as r}from"./config/path-resolver.js";import{t as i}from"./server-core-Cj3cIk43.js";import{createFastMCPServer as a,startServer as o}from"./server.js";export{i as DuckPondServer,a as createFastMCPServer,t as getConfigFromEnv,n as getDefaultDataDir,e as getDefaultUserId,r as resolveDataDir,o as startServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duckpond-mcp-server",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for multi-tenant DuckDB management with R2/S3 storage",
5
5
  "keywords": [
6
6
  "mcp",
@@ -16,51 +16,36 @@
16
16
  ],
17
17
  "author": "jordan.burke@gmail.com",
18
18
  "license": "MIT",
19
- "homepage": "https://github.com/jordanburke/duckpond-mcp-server",
19
+ "homepage": "https://github.com/jordanburke/duckpond/tree/main/packages/duckpond-mcp-server",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "https://github.com/jordanburke/duckpond-mcp-server"
22
+ "url": "https://github.com/jordanburke/duckpond",
23
+ "directory": "packages/duckpond-mcp-server"
23
24
  },
24
25
  "type": "module",
25
26
  "bin": {
26
27
  "duckpond-mcp-server": "./dist/index.js"
27
28
  },
28
- "scripts": {
29
- "validate": "ts-builds validate",
30
- "format": "ts-builds format",
31
- "format:check": "ts-builds format:check",
32
- "lint": "ts-builds lint",
33
- "lint:check": "ts-builds lint:check",
34
- "typecheck": "ts-builds typecheck",
35
- "test": "ts-builds test",
36
- "test:watch": "ts-builds test:watch",
37
- "test:coverage": "ts-builds test:coverage",
38
- "build": "ts-builds build",
39
- "dev": "ts-builds dev",
40
- "serve:test": "tsx src/index.ts",
41
- "serve:test:http": "tsx src/index.ts --transport http",
42
- "prepublishOnly": "pnpm validate"
43
- },
44
29
  "dependencies": {
45
30
  "@hono/node-server": "^1.19.14",
46
31
  "@types/express": "^5.0.6",
47
32
  "@types/jsonwebtoken": "^9.0.10",
48
33
  "commander": "^15.0.0",
49
34
  "debug": "^4.4.3",
50
- "duckpond": "^0.5.1",
51
35
  "express": "^5.2.1",
52
36
  "fastmcp": "4.0.2",
37
+ "functype-os": "^1.3.1",
53
38
  "hono": "^4.12.23",
54
39
  "jsonwebtoken": "^9.0.3",
55
- "zod": "^4.4.3"
40
+ "zod": "^4.4.3",
41
+ "duckpond": "^0.6.0"
56
42
  },
57
43
  "devDependencies": {
58
44
  "@types/node": "~24.10.15",
59
- "ts-builds": "^2.8.2",
45
+ "ts-builds": "^3.0.1",
60
46
  "tsdown": "^0.22.2",
61
47
  "tsx": "^4.22.4",
62
- "typescript": "^6.0.3",
63
- "vite": "^7.3.5"
48
+ "typescript": "^6.0.3"
64
49
  },
65
50
  "main": "./dist/index.js",
66
51
  "module": "./dist/index.js",
@@ -80,5 +65,19 @@
80
65
  "dist"
81
66
  ],
82
67
  "prettier": "ts-builds/prettier",
83
- "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc"
84
- }
68
+ "scripts": {
69
+ "validate": "ts-builds validate",
70
+ "format": "ts-builds format",
71
+ "format:check": "ts-builds format:check",
72
+ "lint": "ts-builds lint",
73
+ "lint:check": "ts-builds lint:check",
74
+ "typecheck": "ts-builds typecheck",
75
+ "test": "ts-builds test",
76
+ "test:watch": "ts-builds test:watch",
77
+ "test:coverage": "ts-builds test:coverage",
78
+ "build": "ts-builds build",
79
+ "dev": "ts-builds dev",
80
+ "serve:test": "tsx src/index.ts",
81
+ "serve:test:http": "tsx src/index.ts --transport http"
82
+ }
83
+ }