This is an automated email from the ASF dual-hosted git repository.

sciabarracom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/openserverless-mcp.git


The following commit(s) were added to refs/heads/main by this push:
     new bb9fc61  converted tool
bb9fc61 is described below

commit bb9fc614196b9feb0e381841ff90bfe7a2439b62
Author: Trustable User <[email protected]>
AuthorDate: Mon Jun 22 05:35:04 2026 +0200

    converted tool
---
 .gitignore                  |    1 +
 README.md                   |   73 +-
 package-lock.json           | 1729 +++++++++++++++++++++++++++++++++++++++++++
 package.json                |   22 +
 src/index.ts                |   36 +
 src/lib.ts                  |  105 +++
 src/tools/add-milvus.ts     |   41 +
 src/tools/add-postgresql.ts |   34 +
 src/tools/add-redis.ts      |   35 +
 src/tools/add-s3.ts         |   50 ++
 src/tools/add-secret.ts     |   45 ++
 src/tools/invoke.ts         |   48 ++
 src/tools/new.ts            |   78 ++
 src/tools/requirements.ts   |   64 ++
 tsconfig.json               |   15 +
 15 files changed, 2375 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/README.md b/README.md
index afeb614..30ca4db 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,73 @@
 # openserverless mcp
-This is an mcp server for openserverless
+
+An MCP server for OpenServerless. It exposes the OpenServerless action tools
+(previously the `@opencode-ai/plugin` tools under `trustable-app/tools/`) over 
the
+Model Context Protocol so any MCP-capable agent can drive them.
+
+## Tools
+
+| Tool | Description |
+|---|---|
+| `action_new` | Create a new API endpoint (`__main__.py` + module file). 
`public` defaults to true. |
+| `action_invoke` | Run `ops action invoke <endpoint>` with `key=value` 
params. |
+| `action_requirements` | Add a library to an endpoint's `requirements.txt` 
(skips preinstalled libs). |
+| `action_add_secret` | Wire a `.env` secret into an endpoint's context as 
`ctx.<SECRET>`. |
+| `action_add_s3` | Add S3 to an endpoint's context (`ctx.S3_CLIENT`, 
`ctx.S3_DATA`, `ctx.S3_WEB`, `ctx.S3_PUBLIC`). |
+| `action_add_postgresql` | Add PostgreSQL (`ctx.POSTGRESQL`). |
+| `action_add_redis` | Add Redis (`ctx.REDIS`, `ctx.REDIS_PREFIX`). |
+| `action_add_milvus` | Add Milvus vector DB (`ctx.MILVUS`). |
+
+The `endpoint` argument is either `name` (uses the `v1` package) or 
`package/name`.
+
+## Working directory
+
+All path-based tools operate on paths **relative to the process working
+directory**: they read/write `packages/<pkg>/<name>/...` and `.env`. The server
+must therefore be launched with the user's app checkout as its CWD (this is how
+opencode launches `type: "local"` MCP servers).
+
+## Layout
+
+```
+src/
+  index.ts            entrypoint — registers every tool over stdio
+  lib.ts              shared helpers (endpoint parsing, connector injection, 
types)
+  tools/
+    new.ts            action_new
+    invoke.ts         action_invoke
+    requirements.ts   action_requirements
+    add-secret.ts     action_add_secret
+    add-s3.ts         action_add_s3
+    add-postgresql.ts action_add_postgresql
+    add-redis.ts      action_add_redis
+    add-milvus.ts     action_add_milvus
+```
+
+Each file under `tools/` default-exports a `Tool` (`defineTool({ name, config, 
handler })`);
+`index.ts` imports them all and registers them in a loop. To add a tool, drop a
+file in `tools/` and add it to the array in `index.ts`.
+
+## Run
+
+Requires Node 18+ and `ops` on the `PATH`. TypeScript runs directly via `tsx`.
+
+```bash
+npm install
+npm start          # tsx src/index.ts — speaks MCP over stdio
+```
+
+## Wire into opencode
+
+Add to the app's `opencode.json`:
+
+```json
+{
+  "mcp": {
+    "openserverless": {
+      "type": "local",
+      "command": ["npx", "tsx", "/path/to/mcp/src/index.ts"],
+      "enabled": true
+    }
+  }
+}
+```
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..17c1295
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1729 @@
+{
+  "name": "openserverless-mcp",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "openserverless-mcp",
+      "version": "0.1.0",
+      "dependencies": {
+        "@modelcontextprotocol/sdk": "^1.12.0",
+        "zod": "^3.23.8"
+      },
+      "bin": {
+        "openserverless-mcp": "src/index.ts"
+      },
+      "devDependencies": {
+        "@types/node": "^22.0.0",
+        "tsx": "^4.19.0",
+        "typescript": "^5.5.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz";,
+      "integrity": 
"sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz";,
+      "integrity": 
"sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz";,
+      "integrity": 
"sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz";,
+      "integrity": 
"sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz";,
+      "integrity": 
"sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz";,
+      "integrity": 
"sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz";,
+      "integrity": 
"sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz";,
+      "integrity": 
"sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz";,
+      "integrity": 
"sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz";,
+      "integrity": 
"sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz";,
+      "integrity": 
"sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.28.1",
+      "resolved": 
"https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz";,
+      "integrity": 
"sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@hono/node-server": {
+      "version": "1.19.14",
+      "resolved": 
"https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz";,
+      "integrity": 
"sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.14.1"
+      },
+      "peerDependencies": {
+        "hono": "^4"
+      }
+    },
+    "node_modules/@modelcontextprotocol/sdk": {
+      "version": "1.29.0",
+      "resolved": 
"https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz";,
+      "integrity": 
"sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@hono/node-server": "^1.19.9",
+        "ajv": "^8.17.1",
+        "ajv-formats": "^3.0.1",
+        "content-type": "^1.0.5",
+        "cors": "^2.8.5",
+        "cross-spawn": "^7.0.5",
+        "eventsource": "^3.0.2",
+        "eventsource-parser": "^3.0.0",
+        "express": "^5.2.1",
+        "express-rate-limit": "^8.2.1",
+        "hono": "^4.11.4",
+        "jose": "^6.1.3",
+        "json-schema-typed": "^8.0.2",
+        "pkce-challenge": "^5.0.0",
+        "raw-body": "^3.0.0",
+        "zod": "^3.25 || ^4.0",
+        "zod-to-json-schema": "^3.25.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@cfworker/json-schema": "^4.1.1",
+        "zod": "^3.25 || ^4.0"
+      },
+      "peerDependenciesMeta": {
+        "@cfworker/json-schema": {
+          "optional": true
+        },
+        "zod": {
+          "optional": false
+        }
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "22.20.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz";,
+      "integrity": 
"sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz";,
+      "integrity": 
"sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "^3.0.0",
+        "negotiator": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz";,
+      "integrity": 
"sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin";
+      }
+    },
+    "node_modules/ajv-formats": {
+      "version": "3.0.1",
+      "resolved": 
"https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz";,
+      "integrity": 
"sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "ajv": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "2.3.0",
+      "resolved": 
"https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz";,
+      "integrity": 
"sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "^3.1.2",
+        "content-type": "^2.0.0",
+        "debug": "^4.4.3",
+        "http-errors": "^2.0.1",
+        "iconv-lite": "^0.7.2",
+        "on-finished": "^2.4.1",
+        "qs": "^6.15.2",
+        "raw-body": "^3.0.2",
+        "type-is": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/body-parser/node_modules/content-type": {
+      "version": "2.0.0",
+      "resolved": 
"https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz";,
+      "integrity": 
"sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz";,
+      "integrity": 
"sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": 
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz";,
+      "integrity": 
"sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": 
"https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz";,
+      "integrity": 
"sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz";,
+      "integrity": 
"sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": 
"https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz";,
+      "integrity": 
"sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz";,
+      "integrity": 
"sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.2.2",
+      "resolved": 
"https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz";,
+      "integrity": 
"sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.6.0"
+      }
+    },
+    "node_modules/cors": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz";,
+      "integrity": 
"sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": 
"https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz";,
+      "integrity": 
"sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz";,
+      "integrity": 
"sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz";,
+      "integrity": 
"sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz";,
+      "integrity": 
"sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz";,
+      "integrity": 
"sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz";,
+      "integrity": 
"sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz";,
+      "integrity": 
"sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz";,
+      "integrity": 
"sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.2",
+      "resolved": 
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz";,
+      "integrity": 
"sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.28.1",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz";,
+      "integrity": 
"sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.28.1",
+        "@esbuild/android-arm": "0.28.1",
+        "@esbuild/android-arm64": "0.28.1",
+        "@esbuild/android-x64": "0.28.1",
+        "@esbuild/darwin-arm64": "0.28.1",
+        "@esbuild/darwin-x64": "0.28.1",
+        "@esbuild/freebsd-arm64": "0.28.1",
+        "@esbuild/freebsd-x64": "0.28.1",
+        "@esbuild/linux-arm": "0.28.1",
+        "@esbuild/linux-arm64": "0.28.1",
+        "@esbuild/linux-ia32": "0.28.1",
+        "@esbuild/linux-loong64": "0.28.1",
+        "@esbuild/linux-mips64el": "0.28.1",
+        "@esbuild/linux-ppc64": "0.28.1",
+        "@esbuild/linux-riscv64": "0.28.1",
+        "@esbuild/linux-s390x": "0.28.1",
+        "@esbuild/linux-x64": "0.28.1",
+        "@esbuild/netbsd-arm64": "0.28.1",
+        "@esbuild/netbsd-x64": "0.28.1",
+        "@esbuild/openbsd-arm64": "0.28.1",
+        "@esbuild/openbsd-x64": "0.28.1",
+        "@esbuild/openharmony-arm64": "0.28.1",
+        "@esbuild/sunos-x64": "0.28.1",
+        "@esbuild/win32-arm64": "0.28.1",
+        "@esbuild/win32-ia32": "0.28.1",
+        "@esbuild/win32-x64": "0.28.1"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": 
"https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz";,
+      "integrity": 
"sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz";,
+      "integrity": 
"sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/eventsource": {
+      "version": "3.0.7",
+      "resolved": 
"https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz";,
+      "integrity": 
"sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+      "license": "MIT",
+      "dependencies": {
+        "eventsource-parser": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/eventsource-parser": {
+      "version": "3.1.0",
+      "resolved": 
"https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz";,
+      "integrity": 
"sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/express": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz";,
+      "integrity": 
"sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "accepts": "^2.0.0",
+        "body-parser": "^2.2.1",
+        "content-disposition": "^1.0.0",
+        "content-type": "^1.0.5",
+        "cookie": "^0.7.1",
+        "cookie-signature": "^1.2.1",
+        "debug": "^4.4.0",
+        "depd": "^2.0.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "finalhandler": "^2.1.0",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.0",
+        "merge-descriptors": "^2.0.0",
+        "mime-types": "^3.0.0",
+        "on-finished": "^2.4.1",
+        "once": "^1.4.0",
+        "parseurl": "^1.3.3",
+        "proxy-addr": "^2.0.7",
+        "qs": "^6.14.0",
+        "range-parser": "^1.2.1",
+        "router": "^2.2.0",
+        "send": "^1.1.0",
+        "serve-static": "^2.2.0",
+        "statuses": "^2.0.1",
+        "type-is": "^2.0.1",
+        "vary": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/express-rate-limit": {
+      "version": "8.5.2",
+      "resolved": 
"https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz";,
+      "integrity": 
"sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
+      "license": "MIT",
+      "dependencies": {
+        "ip-address": "^10.2.0"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/express-rate-limit";
+      },
+      "peerDependencies": {
+        "express": ">= 4.11"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": 
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz";,
+      "integrity": 
"sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "license": "MIT"
+    },
+    "node_modules/fast-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz";,
+      "integrity": 
"sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fastify";
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/fastify";
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/finalhandler": {
+      "version": "2.1.1",
+      "resolved": 
"https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz";,
+      "integrity": 
"sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.0",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "on-finished": "^2.4.1",
+        "parseurl": "^1.3.3",
+        "statuses": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz";,
+      "integrity": 
"sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz";,
+      "integrity": 
"sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz";,
+      "integrity": 
"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": 
"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz";,
+      "integrity": 
"sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": 
"https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz";,
+      "integrity": 
"sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz";,
+      "integrity": 
"sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz";,
+      "integrity": 
"sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz";,
+      "integrity": 
"sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz";,
+      "integrity": 
"sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hono": {
+      "version": "4.12.26",
+      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz";,
+      "integrity": 
"sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==",
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=16.9.0"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": 
"https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz";,
+      "integrity": 
"sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.7.2",
+      "resolved": 
"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz";,
+      "integrity": 
"sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz";,
+      "integrity": 
"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ip-address": {
+      "version": "10.2.0",
+      "resolved": 
"https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz";,
+      "integrity": 
"sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz";,
+      "integrity": 
"sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-promise": {
+      "version": "4.0.0",
+      "resolved": 
"https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz";,
+      "integrity": 
"sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+      "license": "MIT"
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz";,
+      "integrity": 
"sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "license": "ISC"
+    },
+    "node_modules/jose": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz";,
+      "integrity": 
"sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/panva";
+      }
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz";,
+      "integrity": 
"sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "license": "MIT"
+    },
+    "node_modules/json-schema-typed": {
+      "version": "8.0.2",
+      "resolved": 
"https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz";,
+      "integrity": 
"sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz";,
+      "integrity": 
"sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz";,
+      "integrity": 
"sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "2.0.0",
+      "resolved": 
"https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz";,
+      "integrity": 
"sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus";
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.54.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz";,
+      "integrity": 
"sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "3.0.2",
+      "resolved": 
"https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz";,
+      "integrity": 
"sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "^1.54.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz";,
+      "integrity": 
"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz";,
+      "integrity": 
"sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": 
"https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz";,
+      "integrity": 
"sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": 
"https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz";,
+      "integrity": 
"sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": 
"https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz";,
+      "integrity": 
"sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz";,
+      "integrity": 
"sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz";,
+      "integrity": 
"sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz";,
+      "integrity": 
"sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "8.4.2",
+      "resolved": 
"https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz";,
+      "integrity": 
"sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/pkce-challenge": {
+      "version": "5.0.1",
+      "resolved": 
"https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz";,
+      "integrity": 
"sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16.20.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": 
"https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz";,
+      "integrity": 
"sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.15.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz";,
+      "integrity": 
"sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": 
"https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz";,
+      "integrity": 
"sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz";,
+      "integrity": 
"sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.7.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/require-from-string": {
+      "version": "2.0.2",
+      "resolved": 
"https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz";,
+      "integrity": 
"sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/router": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz";,
+      "integrity": 
"sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.0",
+        "depd": "^2.0.0",
+        "is-promise": "^4.0.0",
+        "parseurl": "^1.3.3",
+        "path-to-regexp": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": 
"https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz";,
+      "integrity": 
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz";,
+      "integrity": 
"sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.4.3",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.1",
+        "mime-types": "^3.0.2",
+        "ms": "^2.1.3",
+        "on-finished": "^2.4.1",
+        "range-parser": "^1.2.1",
+        "statuses": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/serve-static": {
+      "version": "2.2.1",
+      "resolved": 
"https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz";,
+      "integrity": 
"sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": 
"https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz";,
+      "integrity": 
"sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": 
"https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz";,
+      "integrity": 
"sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "license": "MIT",
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": 
"https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz";,
+      "integrity": 
"sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.1",
+      "resolved": 
"https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz";,
+      "integrity": 
"sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4",
+        "side-channel-list": "^1.0.1",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz";,
+      "integrity": 
"sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz";,
+      "integrity": 
"sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": 
"https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz";,
+      "integrity": 
"sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz";,
+      "integrity": 
"sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz";,
+      "integrity": 
"sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/tsx": {
+      "version": "4.22.4",
+      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz";,
+      "integrity": 
"sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "~0.28.0"
+      },
+      "bin": {
+        "tsx": "dist/cli.mjs"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz";,
+      "integrity": 
"sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+      "license": "MIT",
+      "dependencies": {
+        "content-type": "^2.0.0",
+        "media-typer": "^1.1.0",
+        "mime-types": "^3.0.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/type-is/node_modules/content-type": {
+      "version": "2.0.0",
+      "resolved": 
"https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz";,
+      "integrity": 
"sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express";
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": 
"https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz";,
+      "integrity": 
"sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": 
"https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz";,
+      "integrity": 
"sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz";,
+      "integrity": 
"sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz";,
+      "integrity": 
"sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz";,
+      "integrity": 
"sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz";,
+      "integrity": 
"sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/zod": {
+      "version": "3.25.76",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz";,
+      "integrity": 
"sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+      "license": "MIT",
+      "peer": true,
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks";
+      }
+    },
+    "node_modules/zod-to-json-schema": {
+      "version": "3.25.2",
+      "resolved": 
"https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz";,
+      "integrity": 
"sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+      "license": "ISC",
+      "peerDependencies": {
+        "zod": "^3.25.28 || ^4"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9c6ff51
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "openserverless-mcp",
+  "version": "0.1.0",
+  "description": "MCP server exposing OpenServerless action tools 
(new/invoke/requirements and service connectors)",
+  "type": "module",
+  "bin": {
+    "openserverless-mcp": "./src/index.ts"
+  },
+  "scripts": {
+    "start": "tsx src/index.ts",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@modelcontextprotocol/sdk": "^1.12.0",
+    "zod": "^3.23.8"
+  },
+  "devDependencies": {
+    "@types/node": "^22.0.0",
+    "tsx": "^4.19.0",
+    "typescript": "^5.5.0"
+  }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..a267530
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,36 @@
+#!/usr/bin/env -S npx tsx
+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
+import { StdioServerTransport } from 
"@modelcontextprotocol/sdk/server/stdio.js"
+import type { Tool } from "./lib.ts"
+
+import actionNew from "./tools/new.ts"
+import actionInvoke from "./tools/invoke.ts"
+import actionRequirements from "./tools/requirements.ts"
+import actionAddSecret from "./tools/add-secret.ts"
+import actionAddS3 from "./tools/add-s3.ts"
+import actionAddPostgresql from "./tools/add-postgresql.ts"
+import actionAddRedis from "./tools/add-redis.ts"
+import actionAddMilvus from "./tools/add-milvus.ts"
+
+const tools: Tool[] = [
+  actionNew,
+  actionInvoke,
+  actionRequirements,
+  actionAddSecret,
+  actionAddS3,
+  actionAddPostgresql,
+  actionAddRedis,
+  actionAddMilvus,
+]
+
+const server = new McpServer({
+  name: "openserverless-mcp",
+  version: "0.1.0",
+})
+
+for (const t of tools) {
+  server.registerTool(t.name, t.config, t.handler)
+}
+
+const transport = new StdioServerTransport()
+await server.connect(transport)
diff --git a/src/lib.ts b/src/lib.ts
new file mode 100644
index 0000000..3f919d4
--- /dev/null
+++ b/src/lib.ts
@@ -0,0 +1,105 @@
+import { readFileSync, writeFileSync, existsSync } from "node:fs"
+import { join } from "node:path"
+import { z, type ZodRawShape } from "zod"
+
+export const BUILD_CONTEXT_MARKER = "## build-context ##"
+
+/** Shared `endpoint` argument used by every action tool. */
+export const endpointArg = z
+  .string()
+  .describe("The endpoint path: 'name' (uses v1 package) or 'package/name'")
+
+/** Shape of a tool result returned to the MCP client. */
+export interface ToolResult {
+  [x: string]: unknown
+  content: { type: "text"; text: string }[]
+}
+
+/**
+ * A self-contained tool definition. Each file under `tools/` default-exports 
one
+ * of these; `index.ts` registers them all in a loop.
+ */
+export interface Tool<Args = any> {
+  name: string
+  config: {
+    description: string
+    inputSchema: ZodRawShape
+  }
+  handler: (args: Args) => Promise<ToolResult> | ToolResult
+}
+
+/** Helper that preserves the inferred argument type while declaring a Tool. */
+export function defineTool<Shape extends ZodRawShape>(t: {
+  name: string
+  config: { description: string; inputSchema: Shape }
+  handler: (args: { [K in keyof Shape]: import("zod").infer<Shape[K]> }) => 
Promise<ToolResult> | ToolResult
+}): Tool {
+  return t as Tool
+}
+
+/** Result of resolving an endpoint string into a package + action name. */
+export interface Endpoint {
+  pkg: string
+  name: string
+  /** packages/<pkg>/<name> */
+  dir: string
+  /** packages/<pkg>/<name>/__main__.py */
+  mainPath: string
+}
+
+/**
+ * Parse an endpoint path. Accepts "name" (defaults to the v1 package) or
+ * "package/name". Throws on malformed input (more than one "/").
+ */
+export function parseEndpoint(endpoint: string): Endpoint {
+  const parts = endpoint.trim().split("/")
+  if (parts.length > 2) {
+    throw new Error("endpoint must have at most one '/' (format: 'name' or 
'package/name')")
+  }
+  const pkg = parts.length === 1 ? "v1" : parts[0]
+  const name = parts.length === 1 ? parts[0] : parts[1]
+  const dir = join("packages", pkg, name)
+  return { pkg, name, dir, mainPath: join(dir, "__main__.py") }
+}
+
+/** A single MCP text tool result. */
+export function text(message: string) {
+  return { content: [{ type: "text" as const, text: message }] }
+}
+
+/**
+ * Inject a service connector into an endpoint's __main__.py at the
+ * `## build-context ##` marker.
+ *
+ * - guard: a substring whose presence means the connector is already wired in
+ * - injection: the python snippet to append after the marker
+ *
+ * Returns a human-readable status string describing what happened.
+ */
+export function injectConnector(opts: {
+  endpoint: Endpoint
+  label: string
+  guard: string
+  injection: string
+  available: string
+}): string {
+  const { endpoint, label, guard, injection, available } = opts
+  const { pkg, name, mainPath } = endpoint
+
+  if (!existsSync(mainPath)) {
+    return `Error: endpoint not found at ${mainPath}`
+  }
+
+  let content = readFileSync(mainPath, "utf-8")
+  if (!content.includes(BUILD_CONTEXT_MARKER)) {
+    return `Error: marker '${BUILD_CONTEXT_MARKER}' not found in ${mainPath}`
+  }
+  if (content.includes(guard)) {
+    return `${label} is already configured in endpoint '${pkg}/${name}'.`
+  }
+
+  content = content.replace(BUILD_CONTEXT_MARKER, BUILD_CONTEXT_MARKER + 
injection)
+  writeFileSync(mainPath, content)
+
+  return `Added ${label} to endpoint '${pkg}/${name}'.\nAvailable in 
context:\n${available}`
+}
diff --git a/src/tools/add-milvus.ts b/src/tools/add-milvus.ts
new file mode 100644
index 0000000..2951e64
--- /dev/null
+++ b/src/tools/add-milvus.ts
@@ -0,0 +1,41 @@
+import { parseEndpoint, injectConnector, text, defineTool, endpointArg } from 
"../lib.ts"
+
+export default defineTool({
+  name: "action_add_milvus",
+  config: {
+    description: "Add Milvus vector DB connection to an endpoint's context. 
Provides ctx.MILVUS.",
+    inputSchema: { endpoint: endpointArg },
+  },
+  handler({ endpoint }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const injection = `
+#--param MILVUS_HOST "$MILVUS_HOST"
+#--param MILVUS_PORT "$MILVUS_PORT"
+#--param MILVUS_DB_NAME "$MILVUS_DB_NAME"
+#--param MILVUS_TOKEN "$MILVUS_TOKEN"
+from pymilvus import MilvusClient
+def init_milvus(args, ctx):
+  host = args.get('MILVUS_HOST', os.getenv('MILVUS_HOST'))
+  port = args.get('MILVUS_PORT', os.getenv('MILVUS_PORT'))
+  uri = f"http://{host}:{port}";
+  token = args.get("MILVUS_TOKEN", os.getenv("MILVUS_TOKEN"))
+  db_name = args.get("MILVUS_DB_NAME", os.getenv("MILVUS_DB_NAME"))
+  ctx.MILVUS = MilvusClient(uri=uri, token=token, db_name=db_name)
+builder.append(init_milvus)`
+
+    return text(
+      injectConnector({
+        endpoint: ep,
+        label: "Milvus",
+        guard: "init_milvus",
+        injection,
+        available: "  ctx.MILVUS — the MilvusClient instance",
+      }),
+    )
+  },
+})
diff --git a/src/tools/add-postgresql.ts b/src/tools/add-postgresql.ts
new file mode 100644
index 0000000..e3adbb3
--- /dev/null
+++ b/src/tools/add-postgresql.ts
@@ -0,0 +1,34 @@
+import { parseEndpoint, injectConnector, text, defineTool, endpointArg } from 
"../lib.ts"
+
+export default defineTool({
+  name: "action_add_postgresql",
+  config: {
+    description: "Add PostgreSQL connection to an endpoint's context. Provides 
ctx.POSTGRESQL.",
+    inputSchema: { endpoint: endpointArg },
+  },
+  handler({ endpoint }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const injection = `
+#--param POSTGRES_URL "$POSTGRES_URL"
+import psycopg
+def init_postgresql(args, ctx):
+  dburl = args.get("POSTGRES_URL", os.getenv("POSTGRES_URL"))
+  ctx.POSTGRESQL = psycopg.connect(dburl)
+builder.append(init_postgresql)`
+
+    return text(
+      injectConnector({
+        endpoint: ep,
+        label: "PostgreSQL",
+        guard: "init_postgresql",
+        injection,
+        available: "  ctx.POSTGRESQL — the psycopg connection",
+      }),
+    )
+  },
+})
diff --git a/src/tools/add-redis.ts b/src/tools/add-redis.ts
new file mode 100644
index 0000000..4dd439d
--- /dev/null
+++ b/src/tools/add-redis.ts
@@ -0,0 +1,35 @@
+import { parseEndpoint, injectConnector, text, defineTool, endpointArg } from 
"../lib.ts"
+
+export default defineTool({
+  name: "action_add_redis",
+  config: {
+    description: "Add Redis connection to an endpoint's context. Provides 
ctx.REDIS and ctx.REDIS_PREFIX.",
+    inputSchema: { endpoint: endpointArg },
+  },
+  handler({ endpoint }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const injection = `
+#--param REDIS_URL "$REDIS_URL"
+#--param REDIS_PREFIX "$REDIS_PREFIX"
+import redis
+def init_redis(args, ctx):
+  ctx.REDIS = redis.from_url(args.get("REDIS_URL", os.getenv("REDIS_URL")))
+  ctx.REDIS_PREFIX = args.get("REDIS_PREFIX", os.getenv("REDIS_PREFIX"))
+builder.append(init_redis)`
+
+    return text(
+      injectConnector({
+        endpoint: ep,
+        label: "Redis",
+        guard: "init_redis",
+        injection,
+        available: "  ctx.REDIS — the Redis client\n  ctx.REDIS_PREFIX — the 
key prefix",
+      }),
+    )
+  },
+})
diff --git a/src/tools/add-s3.ts b/src/tools/add-s3.ts
new file mode 100644
index 0000000..aeb7198
--- /dev/null
+++ b/src/tools/add-s3.ts
@@ -0,0 +1,50 @@
+import { parseEndpoint, injectConnector, text, defineTool, endpointArg } from 
"../lib.ts"
+
+export default defineTool({
+  name: "action_add_s3",
+  config: {
+    description: "Add S3 connection to an endpoint's context. Provides 
ctx.S3_CLIENT, ctx.S3_DATA, ctx.S3_WEB, ctx.S3_PUBLIC.",
+    inputSchema: { endpoint: endpointArg },
+  },
+  handler({ endpoint }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const injection = `
+#--param S3_HOST "$S3_HOST"
+#--param S3_PORT "$S3_PORT"
+#--param S3_ACCESS_KEY "$S3_ACCESS_KEY"
+#--param S3_SECRET_KEY "$S3_SECRET_KEY"
+#--param S3_BUCKET_DATA "$S3_BUCKET_DATA"
+#--param S3_BUCKET_STATIC "$S3_BUCKET_STATIC"
+#--param S3_PUBLIC "$OPSDEV_S3"
+import boto3
+from botocore.client import Config
+def init_s3(args, ctx):
+  host = args.get("S3_HOST", os.getenv("S3_HOST"))
+  port = args.get("S3_PORT", os.getenv("S3_PORT"))
+  url = f"http://{host}:{port}";
+  key = args.get("S3_ACCESS_KEY", os.getenv("S3_ACCESS_KEY"))
+  sec = args.get("S3_SECRET_KEY", os.getenv("S3_SECRET_KEY"))
+  cfg = Config(signature_version='s3v4')
+  ctx.S3_CLIENT = boto3.client('s3', region_name='us-east-1', 
endpoint_url=url, aws_access_key_id=key, aws_secret_access_key=sec, config=cfg)
+  ctx.S3_DATA = args.get("S3_BUCKET_DATA", os.getenv("S3_BUCKET_DATA"))
+  ctx.S3_WEB = args.get("S3_BUCKET_STATIC", os.getenv("S3_BUCKET_STATIC"))
+  ctx.S3_PUBLIC = args.get("S3_PUBLIC", os.getenv("OPSDEV_S3"))
+builder.append(init_s3)`
+
+    return text(
+      injectConnector({
+        endpoint: ep,
+        label: "S3",
+        guard: "init_s3",
+        injection,
+        available:
+          "  ctx.S3_CLIENT — the S3 client\n  ctx.S3_DATA — the S3 data bucket 
(private)\n  ctx.S3_WEB — the S3 web bucket (public)\n  ctx.S3_PUBLIC — the 
public URL to access S3",
+      }),
+    )
+  },
+})
diff --git a/src/tools/add-secret.ts b/src/tools/add-secret.ts
new file mode 100644
index 0000000..2552fd7
--- /dev/null
+++ b/src/tools/add-secret.ts
@@ -0,0 +1,45 @@
+import { readFileSync, existsSync } from "node:fs"
+import { z } from "zod"
+import { parseEndpoint, injectConnector, text, defineTool, endpointArg } from 
"../lib.ts"
+
+export default defineTool({
+  name: "action_add_secret",
+  config: {
+    description: "Add a secret to an endpoint's context. The secret must exist 
in .env.",
+    inputSchema: {
+      endpoint: endpointArg,
+      secret: z.string().describe("The secret name (e.g. MY_SECRET)"),
+    },
+  },
+  handler({ endpoint, secret }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const sec = secret.trim()
+
+    let envContent = ""
+    if (existsSync(".env")) {
+      envContent = readFileSync(".env", "utf-8")
+    }
+    if (!envContent.includes(`${sec}=`)) {
+      return text(`Warning: secret '${sec}' not found in .env. Please add it 
to your environment before deploying.`)
+    }
+
+    const injection = `
+#--param ${sec} "$${sec}"
+builder.append(lambda args, ctx: setattr(ctx, '${sec}', args.get("${sec}", 
os.getenv("${sec}"))))`
+
+    return text(
+      injectConnector({
+        endpoint: ep,
+        label: `Secret '${sec}'`,
+        guard: `#--param ${sec} `,
+        injection,
+        available: `  ctx.${sec} — the secret value`,
+      }),
+    )
+  },
+})
diff --git a/src/tools/invoke.ts b/src/tools/invoke.ts
new file mode 100644
index 0000000..b935dbc
--- /dev/null
+++ b/src/tools/invoke.ts
@@ -0,0 +1,48 @@
+import { execFile } from "node:child_process"
+import { z } from "zod"
+import { text, defineTool } from "../lib.ts"
+
+export default defineTool({
+  name: "action_invoke",
+  config: {
+    description: "Invoke an API action. Executes `ops action invoke` with the 
given endpoint and key=value parameters.",
+    inputSchema: {
+      endpoint: z.string().describe("The endpoint path: 'package/action'"),
+      params: z
+        .array(z.string())
+        .optional()
+        .describe("Key=value pairs to pass as parameters (e.g. ['key1=value1', 
'key2=value2'])"),
+    },
+  },
+  async handler({ endpoint, params }) {
+    const paramArgs: string[] = []
+    if (params) {
+      for (const kv of params) {
+        const eqIdx = kv.indexOf("=")
+        if (eqIdx === -1) {
+          return text(`Error: invalid parameter '${kv}', expected key=value 
format`)
+        }
+        paramArgs.push("-p", kv.substring(0, eqIdx), kv.substring(eqIdx + 1))
+      }
+    }
+
+    const argv = ["action", "invoke", endpoint.trim(), ...paramArgs]
+
+    const { stdout, stderr, exitCode } = await new Promise<{
+      stdout: string
+      stderr: string
+      exitCode: number
+    }>((resolve) => {
+      execFile("ops", argv, { maxBuffer: 1024 * 1024 * 64 }, (err, out, 
errOut) => {
+        const code = err && typeof (err as any).code === "number" ? (err as 
any).code : err ? 1 : 0
+        resolve({ stdout: out ?? "", stderr: errOut ?? "", exitCode: code })
+      })
+    })
+
+    let result = ""
+    if (stdout) result += stdout
+    if (stderr) result += `\nSTDERR:\n${stderr}`
+    if (exitCode !== 0) result += `\nExit code: ${exitCode}`
+    return text(result || "(no output)")
+  },
+})
diff --git a/src/tools/new.ts b/src/tools/new.ts
new file mode 100644
index 0000000..0ca2144
--- /dev/null
+++ b/src/tools/new.ts
@@ -0,0 +1,78 @@
+import { mkdirSync, writeFileSync, existsSync } from "node:fs"
+import { join } from "node:path"
+import { z } from "zod"
+import { parseEndpoint, text, defineTool, endpointArg } from "../lib.ts"
+
+export default defineTool({
+  name: "action_new",
+  config: {
+    description: "Create a new API endpoint. Creates the action folder with 
__main__.py and module file.",
+    inputSchema: {
+      endpoint: endpointArg,
+      public: z
+        .boolean()
+        .optional()
+        .describe("Whether the action is public (API endpoint). Defaults to 
true. Set false for private actions (e.g. init package)."),
+    },
+  },
+  handler({ endpoint, public: isPublicArg }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const { pkg, name, dir } = ep
+
+    const validPattern = /^[a-zA-Z][a-zA-Z0-9-]*$/
+    if (!validPattern.test(pkg)) {
+      return text(`Error: package '${pkg}' must only contain letters, numbers, 
'-' and start with a letter`)
+    }
+    if (!validPattern.test(name)) {
+      return text(`Error: name '${name}' must only contain letters, numbers, 
'-' and start with a letter`)
+    }
+
+    const isPublic = isPublicArg !== false
+    const moduleName = name.replace(/-/g, "_")
+
+    if (existsSync(dir)) {
+      return text(`Error: endpoint already exists at ${dir}`)
+    }
+
+    mkdirSync(dir, { recursive: true })
+
+    const mainPy = `#--kind python:default
+#--web ${isPublic}
+# Note: this timeout is 5 minutes - 10 minutes is max allowed
+#--timeout 300000
+import types, os, ${moduleName}
+
+builder = []
+## build-context ##
+
+def main(args):
+  try:
+    ctx = types.SimpleNamespace()
+    for fn in builder: fn(args, ctx)
+    return { "body": ${moduleName}.main(args, ctx=ctx) }
+  except Exception as e:
+    import traceback
+    traceback.print_exc()
+    return {
+      "body": {"error": str(e) },
+      "statusCode": 500
+    }
+`
+
+    const modulePy = `def main(args, ctx=None):
+  inp = args.get("input", "${moduleName}")
+  out = inp
+  return out
+`
+
+    writeFileSync(join(dir, "__main__.py"), mainPy)
+    writeFileSync(join(dir, `${moduleName}.py`), modulePy)
+
+    return text(`Created endpoint at /api/my/${pkg}/${name}\nFiles:\n  
${dir}/__main__.py\n  ${dir}/${moduleName}.py`)
+  },
+})
diff --git a/src/tools/requirements.ts b/src/tools/requirements.ts
new file mode 100644
index 0000000..698a71c
--- /dev/null
+++ b/src/tools/requirements.ts
@@ -0,0 +1,64 @@
+import { readFileSync, writeFileSync, existsSync } from "node:fs"
+import { join } from "node:path"
+import { z } from "zod"
+import { parseEndpoint, text, defineTool, endpointArg } from "../lib.ts"
+
+const PREINSTALLED = [
+  "requests", "ollama", "openai", "pymilvus", "redis", "pyyaml", "boto3",
+  "psycopg", "beautifulsoup4", "pillow", "nltk", "httplib2", "kafka_python",
+  "python-dateutil", "scrapy", "simplejson", "twisted", "netifaces", "pymongo",
+  "minio", "langdetect", "plotly", "joblib", "lightgbm", "feedparser", "numpy",
+  "scikit-learn", "langchain", "langchain-ollama", "langchain-openai", 
"bcrypt",
+]
+
+export default defineTool({
+  name: "action_requirements",
+  config: {
+    description: "Add a library to an endpoint's requirements.txt. Skips if 
the library is preinstalled.",
+    inputSchema: {
+      endpoint: endpointArg,
+      library: z.string().describe("The Python library name to add"),
+    },
+  },
+  handler({ endpoint, library }) {
+    let ep
+    try {
+      ep = parseEndpoint(endpoint)
+    } catch (e) {
+      return text(`Error: ${(e as Error).message}`)
+    }
+    const lib = library.trim()
+    const { dir } = ep
+
+    if (!existsSync(dir)) {
+      return text(`Error: endpoint not found at ${dir}`)
+    }
+
+    const normalizedLib = lib.toLowerCase().replace(/-/g, "_")
+    const isPreinstalled = PREINSTALLED.some(
+      (p) => p.toLowerCase().replace(/-/g, "_") === normalizedLib,
+    )
+    if (isPreinstalled) {
+      return text(`Library '${lib}' is preinstalled and available. No action 
needed.`)
+    }
+
+    const reqPath = join(dir, "requirements.txt")
+    let existing = ""
+    if (existsSync(reqPath)) {
+      existing = readFileSync(reqPath, "utf-8")
+    }
+
+    const lines = existing.split("\n").map((l) => l.trim()).filter(Boolean)
+    const alreadyAdded = lines.some(
+      (l) => l.toLowerCase().replace(/-/g, "_") === normalizedLib,
+    )
+    if (alreadyAdded) {
+      return text(`Library '${lib}' is already in requirements.txt.`)
+    }
+
+    lines.push(lib)
+    writeFileSync(reqPath, lines.join("\n") + "\n")
+
+    return text(`Added '${lib}' to ${reqPath}`)
+  },
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..fc6a29f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "lib": ["ESNext"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "types": ["node"],
+    "strict": true,
+    "skipLibCheck": true,
+    "noEmit": true,
+    "esModuleInterop": true,
+    "allowImportingTsExtensions": true
+  },
+  "include": ["src"]
+}

Reply via email to