feat: add mcp server (#303)

* feat: added the mcp server

* feat: added the mcp server

* fix: api for database not linked to any projects

* fix: added variable to disable MCP

* chore: refactoring
This commit is contained in:
Charles GTE
2026-06-01 23:06:21 +02:00
committed by GitHub
parent 7afcf0f76b
commit 2d7a5ce264
13 changed files with 780 additions and 12 deletions
+2 -1
View File
@@ -76,4 +76,5 @@ RETENTION_CRON="* * * * *"
TRUSTED_DOMAINS="http://localhost:8887, http://localhost:3055, http://localhost:3056"
#OPENAPI_ENABLED=true
#API_ENABLED=true
#API_ENABLED=true
#MCP_ENABLED=true
+31
View File
@@ -0,0 +1,31 @@
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { NextResponse } from "next/server";
import { withApiKey } from "@/lib/api-v1/middleware";
import { createPortabaseMcpServer } from "@/lib/mcp/server";
import type { ApiKeyContext } from "@/lib/api-v1/types";
import { env } from "@/env.mjs";
/**
* MCP endpoint — Streamable HTTP transport, stateless mode.
*
* Auth is handled by withApiKey before MCP is ever touched.
* The validated key is forwarded by MCP tools to downstream /api/v1 REST calls.
*/
const apiEnabled = String(env.API_ENABLED) === "true";
const mcpEnabled = String(env.MCP_ENABLED) === "true";
export const POST = apiEnabled && mcpEnabled
? withApiKey(async (req: Request, ctx: ApiKeyContext) => {
const apiKey = req.headers.get("x-api-key")!;
const server = createPortabaseMcpServer(ctx, apiKey);
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
return transport.handleRequest(req);
})
: () => NextResponse.json({ error: "Not found" }, { status: 404 });
+1
View File
@@ -21,6 +21,7 @@
"@better-auth/passkey": "1.6.11",
"@better-auth/sso": "1.6.11",
"@hookform/resolvers": "^5.4.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
+412
View File
@@ -26,6 +26,9 @@ importers:
'@hookform/resolvers':
specifier: ^5.4.0
version: 5.4.0(react-hook-form@7.76.1(react@19.2.6))
'@modelcontextprotocol/sdk':
specifier: ^1.29.0
version: 1.29.0(zod@4.3.6)
'@radix-ui/react-accordion':
specifier: ^1.2.12
version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -1454,6 +1457,12 @@ packages:
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@hono/node-server@1.19.14':
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@hookform/resolvers@5.4.0':
resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==}
peerDependencies:
@@ -1950,6 +1959,16 @@ packages:
'@lottiefiles/dotlottie-web@0.42.0':
resolution: {integrity: sha512-Zr2LCaOAoPCsdAQgeLyCSiQ1+xrAJtRCyuEYDj0qR5heUwpc+Pxbb88JyTVumcXFfKOBMOMmrlsTScLz2mrvQQ==}
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@napi-rs/wasm-runtime@1.1.4':
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
peerDependencies:
@@ -3820,6 +3839,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-import-phases@1.0.4:
resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==}
engines: {node: '>=10.13.0'}
@@ -3852,6 +3875,14 @@ packages:
ajv:
optional: true
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv-keywords@5.1.0:
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
peerDependencies:
@@ -4113,6 +4144,10 @@ packages:
block-stream2@2.1.0:
resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==}
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -4162,6 +4197,10 @@ packages:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
c12@3.1.0:
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
peerDependencies:
@@ -4379,6 +4418,14 @@ packages:
constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
content-disposition@1.1.0:
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
engines: {node: '>=18'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
content-type@2.0.0:
resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
engines: {node: '>=18'}
@@ -4422,6 +4469,10 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
@@ -4617,6 +4668,10 @@ packages:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
@@ -4789,6 +4844,9 @@ packages:
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
effect@3.20.0:
resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==}
@@ -4827,6 +4885,10 @@ packages:
emphasize@4.2.0:
resolution: {integrity: sha512-yGKvcFUHlBsUPwlxTlzKLR8+zhpbitkFOMCUxN8fTJng9bdH3WNzUGkhdaGdjndSUgqmMPBN7umfwnUdLz5Axg==}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
encoding-sniffer@0.2.1:
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
@@ -5084,6 +5146,10 @@ packages:
resolution: {integrity: sha512-qifAYjuW5AM1eEEIsFnOwB+TGqu6ynU3OKj9WbUTOtUBHFPZqL03XUW34kbp3zm19Ald+U8dEyRXaVsUck+Y1g==}
engines: {node: '>=20'}
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@@ -5094,10 +5160,28 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
eventsource-parser@3.1.0:
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
engines: {node: '>=18.0.0'}
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
express-rate-limit@8.5.2:
resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
engines: {node: '>= 16'}
peerDependencies:
express: '>= 4.11'
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@@ -5189,6 +5273,10 @@ packages:
resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
engines: {node: '>=0.10.0'}
finalhandler@2.1.1:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -5216,6 +5304,10 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -5247,6 +5339,10 @@ packages:
react-dom:
optional: true
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -5446,6 +5542,10 @@ packages:
highlight.js@10.4.1:
resolution: {integrity: sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==}
hono@4.12.23:
resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==}
engines: {node: '>=16.9.0'}
hosted-git-info@8.1.0:
resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==}
engines: {node: ^18.17.0 || >=20.5.0}
@@ -5460,6 +5560,10 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
@@ -5537,6 +5641,10 @@ packages:
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
ipaddr.js@2.4.0:
resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==}
engines: {node: '>= 10'}
@@ -5642,6 +5750,9 @@ packages:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -5770,6 +5881,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -6025,10 +6139,18 @@ packages:
peerDependencies:
react: ^18.0 || ^19.0
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
meow@13.2.0:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
merge-descriptors@2.0.0:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -6159,6 +6281,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -6381,6 +6507,10 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -6478,6 +6608,10 @@ packages:
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
@@ -6510,6 +6644,9 @@ packages:
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
engines: {node: 18 || 20 || >=22}
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -6589,6 +6726,10 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -6727,6 +6868,10 @@ packages:
protocols@2.0.2:
resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
proxy-agent@6.5.0:
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
engines: {node: '>= 14'}
@@ -6771,6 +6916,14 @@ packages:
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
@@ -6992,6 +7145,10 @@ packages:
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -7068,9 +7225,17 @@ packages:
engines: {node: '>=10'}
hasBin: true
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
set-cookie-parser@3.1.0:
resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
@@ -7086,6 +7251,9 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.34.4:
resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7228,6 +7396,10 @@ packages:
resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==}
engines: {node: '>=6'}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
@@ -7486,6 +7658,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -7555,6 +7731,10 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-is@2.1.0:
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
engines: {node: '>= 18'}
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -7623,6 +7803,10 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
unrs-resolver@1.12.2:
resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==}
@@ -7957,6 +8141,11 @@ packages:
peerDependencies:
prisma: 5.0.0 - 6.7.x
zod-to-json-schema@3.25.2:
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
peerDependencies:
zod: ^3.25.28 || ^4
zod-validation-error@1.5.0:
resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==}
engines: {node: '>=16.0.0'}
@@ -8757,6 +8946,10 @@ snapshots:
'@hexagon/base64@1.1.28': {}
'@hono/node-server@1.19.14(hono@4.12.23)':
dependencies:
hono: 4.12.23
'@hookform/resolvers@5.4.0(react-hook-form@7.76.1(react@19.2.6))':
dependencies:
'@standard-schema/utils': 0.3.0
@@ -9131,6 +9324,28 @@ snapshots:
'@lottiefiles/dotlottie-web@0.42.0': {}
'@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.23)
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
content-type: 1.0.5
cors: 2.8.6
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.1.0
express: 5.2.1
express-rate-limit: 8.5.2(express@5.2.1)
hono: 4.12.23
jose: 6.2.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 4.3.6
zod-to-json-schema: 3.25.2(zod@4.3.6)
transitivePeerDependencies:
- supports-color
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies:
'@emnapi/core': 1.10.0
@@ -11595,6 +11810,11 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
negotiator: 1.0.0
acorn-import-phases@1.0.4(acorn@8.16.0):
dependencies:
acorn: 8.16.0
@@ -11617,6 +11837,10 @@ snapshots:
optionalDependencies:
ajv: 8.20.0
ajv-formats@3.0.1(ajv@8.20.0):
optionalDependencies:
ajv: 8.20.0
ajv-keywords@5.1.0(ajv@8.20.0):
dependencies:
ajv: 8.20.0
@@ -11868,6 +12092,20 @@ snapshots:
dependencies:
readable-stream: 3.6.2
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
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
transitivePeerDependencies:
- supports-color
boolbase@1.0.0: {}
brace-expansion@1.1.14:
@@ -11920,6 +12158,8 @@ snapshots:
dependencies:
run-applescript: 7.1.0
bytes@3.1.2: {}
c12@3.1.0:
dependencies:
chokidar: 4.0.3
@@ -12184,6 +12424,10 @@ snapshots:
tslib: 2.8.1
upper-case: 2.0.2
content-disposition@1.1.0: {}
content-type@1.0.5: {}
content-type@2.0.0: {}
conventional-changelog-angular@8.3.1:
@@ -12235,6 +12479,8 @@ snapshots:
convert-source-map@2.0.0: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
copy-anything@3.0.5:
@@ -12405,6 +12651,8 @@ snapshots:
escodegen: 2.1.0
esprima: 4.0.1
depd@2.0.0: {}
destr@2.0.5: {}
detect-indent@7.0.1: {}
@@ -12508,6 +12756,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
ee-first@1.1.1: {}
effect@3.20.0:
dependencies:
'@standard-schema/spec': 1.1.0
@@ -12546,6 +12796,8 @@ snapshots:
highlight.js: 10.4.1
lowlight: 1.17.0
encodeurl@2.0.0: {}
encoding-sniffer@0.2.1:
dependencies:
iconv-lite: 0.6.3
@@ -13086,12 +13338,20 @@ snapshots:
eta@4.5.0: {}
etag@1.8.1: {}
eventemitter3@4.0.7: {}
eventemitter3@5.0.4: {}
events@3.3.0: {}
eventsource-parser@3.1.0: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.1.0
execa@8.0.1:
dependencies:
cross-spawn: 7.0.6
@@ -13104,6 +13364,44 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
express-rate-limit@8.5.2(express@5.2.1):
dependencies:
express: 5.2.1
ip-address: 10.2.0
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.2
content-disposition: 1.1.0
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
mime-types: 3.0.2
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.15.2
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
type-is: 2.1.0
vary: 1.1.2
transitivePeerDependencies:
- supports-color
exsolve@1.0.8: {}
extend@3.0.2: {}
@@ -13194,6 +13492,17 @@ snapshots:
filter-obj@1.1.0: {}
finalhandler@2.1.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -13221,6 +13530,8 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
forwarded@0.2.0: {}
fraction.js@4.3.7: {}
framer-motion@12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
@@ -13241,6 +13552,8 @@ snapshots:
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
fresh@2.0.0: {}
fs-constants@1.0.0: {}
fs-extra@11.3.0:
@@ -13464,6 +13777,8 @@ snapshots:
highlight.js@10.4.1: {}
hono@4.12.23: {}
hosted-git-info@8.1.0:
dependencies:
lru-cache: 10.4.3
@@ -13490,6 +13805,14 @@ snapshots:
domutils: 3.2.2
entities: 4.5.0
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
@@ -13565,6 +13888,8 @@ snapshots:
ip-address@10.2.0: {}
ipaddr.js@1.9.1: {}
ipaddr.js@2.4.0: {}
is-array-buffer@3.0.5:
@@ -13660,6 +13985,8 @@ snapshots:
is-plain-object@5.0.0: {}
is-promise@4.0.0: {}
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@@ -13776,6 +14103,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
json-stable-stringify-without-jsonify@1.0.1: {}
json-with-bigint@3.5.8: {}
@@ -13993,8 +14322,12 @@ snapshots:
marked: 7.0.4
react: 19.2.6
media-typer@1.1.0: {}
meow@13.2.0: {}
merge-descriptors@2.0.0: {}
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -14108,6 +14441,8 @@ snapshots:
negotiator@0.6.3: {}
negotiator@1.0.0: {}
neo-async@2.6.2: {}
netmask@2.1.1: {}
@@ -14313,6 +14648,10 @@ snapshots:
on-exit-leak-free@2.1.2: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -14464,6 +14803,8 @@ snapshots:
leac: 0.6.0
peberminta: 0.9.0
parseurl@1.3.3: {}
pascal-case@3.1.2:
dependencies:
no-case: 3.0.4
@@ -14491,6 +14832,8 @@ snapshots:
lru-cache: 11.5.0
minipass: 7.1.3
path-to-regexp@8.4.2: {}
pathe@2.0.3: {}
peberminta@0.9.0: {}
@@ -14580,6 +14923,8 @@ snapshots:
pirates@4.0.7: {}
pkce-challenge@5.0.1: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@@ -14718,6 +15063,11 @@ snapshots:
protocols@2.0.2: {}
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
proxy-agent@6.5.0:
dependencies:
agent-base: 7.1.4
@@ -14767,6 +15117,15 @@ snapshots:
quick-format-unescaped@4.0.4: {}
range-parser@1.2.1: {}
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.7.2
unpipe: 1.0.0
rc9@2.1.2:
dependencies:
defu: 6.1.7
@@ -15069,6 +15428,16 @@ snapshots:
rou3@0.7.12: {}
router@2.2.0:
dependencies:
debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.4.2
transitivePeerDependencies:
- supports-color
run-applescript@7.1.0: {}
run-async@4.0.6: {}
@@ -15147,12 +15516,37 @@ snapshots:
semver@7.8.1: {}
send@1.2.1:
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
transitivePeerDependencies:
- supports-color
sentence-case@3.0.4:
dependencies:
no-case: 3.0.4
tslib: 2.8.1
upper-case-first: 2.0.2
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.1
transitivePeerDependencies:
- supports-color
set-cookie-parser@3.1.0: {}
set-function-length@1.2.2:
@@ -15177,6 +15571,8 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.2
setprototypeof@1.2.0: {}
sharp@0.34.4:
dependencies:
'@img/colour': 1.1.0
@@ -15410,6 +15806,8 @@ snapshots:
dependencies:
type-fest: 0.7.1
statuses@2.0.2: {}
stdin-discarder@0.2.2: {}
stop-iteration-iterator@1.1.0:
@@ -15687,6 +16085,8 @@ snapshots:
dependencies:
is-number: 7.0.0
toidentifier@1.0.1: {}
tr46@0.0.3: {}
traverse@0.6.11:
@@ -15754,6 +16154,12 @@ snapshots:
type-fest@2.19.0: {}
type-is@2.1.0:
dependencies:
content-type: 2.0.0
media-typer: 1.1.0
mime-types: 3.0.2
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -15837,6 +16243,8 @@ snapshots:
universalify@2.0.1: {}
unpipe@1.0.0: {}
unrs-resolver@1.12.2:
dependencies:
napi-postinstall: 0.3.4
@@ -16241,6 +16649,10 @@ snapshots:
- supports-color
- typescript
zod-to-json-schema@3.25.2(zod@4.3.6):
dependencies:
zod: 4.3.6
zod-validation-error@1.5.0(zod@3.25.76):
dependencies:
zod: 3.25.76
+1
View File
@@ -105,6 +105,7 @@ function checkRouteExists(pathname: string) {
/^\/api\/health\/?$/,
/^\/api\/google\/drive\/callback\/?$/,
// v1 external API
/^\/api\/v1\/mcp\/?$/,
/^\/api\/v1\/docs\/?$/,
/^\/api\/v1\/openapi\/?$/,
/^\/api\/v1\/agents\/?$/,
+7
View File
@@ -108,6 +108,11 @@ export const env = createEnv({
.enum(["true", "false"])
.transform((val) => val === "true")
.default("false"),
MCP_ENABLED: z
.enum(["true", "false"])
.transform((val) => val === "true")
.default("false"),
},
client: {
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
@@ -183,5 +188,7 @@ export const env = createEnv({
OPENAPI_ENABLED: process.env.OPENAPI_ENABLED,
API_ENABLED: process.env.API_ENABLED,
MCP_ENABLED: process.env.MCP_ENABLED,
},
});
+36 -11
View File
@@ -66,6 +66,7 @@ export async function requireDatabaseAccess(
const access = await resolveDatabaseAccess(id, user);
if (access === "ok") {
return {
ok: true,
@@ -80,31 +81,55 @@ export async function requireDatabaseAccess(
};
}
if (access === "no_project_link") {
return {
ok: false,
response: jsonError("Database is not linked to any project", 403),
};
}
return {
ok: false,
response: jsonError("Not found", 404),
};
}
export type DatabaseAccessResult = "ok" | "forbidden" | "not_found";
export type DatabaseAccessResult =
| "ok"
| "forbidden"
| "not_found"
| "no_project_link";
export async function resolveDatabaseAccess(
id: string,
user: ApiKeyContext["user"]
): Promise<DatabaseAccessResult> {
const accessibleIds = await getAccessibleDatabaseIds(user);
const [accessibleIds, database] = await Promise.all([
getAccessibleDatabaseIds(user),
db.query.database.findFirst({
where: and(
eq(drizzleDb.schemas.database.id, id),
isNull(drizzleDb.schemas.database.deletedAt)
),
columns: {
id: true,
projectId: true,
},
}),
]);
if (!database) {
return "not_found";
}
if (database.projectId === null) {
return "no_project_link";
}
if (accessibleIds.includes(id)) {
return "ok";
}
const exists = await db.query.database.findFirst({
where: and(
eq(drizzleDb.schemas.database.id, id),
isNull(drizzleDb.schemas.database.deletedAt)
),
columns: { id: true },
});
return exists ? "forbidden" : "not_found";
return "forbidden";
}
+63
View File
@@ -0,0 +1,63 @@
import { getServerUrl } from "@/utils/get-server-url";
export type ApiV1Result =
| { ok: true; status: number; data: unknown }
| { ok: false; status: number; error: string };
/**
* Thin fetch wrapper that proxies calls to the existing /api/v1 REST layer.
* Forwards the caller's API key so the REST route runs its own auth + permission checks.
*/
export async function apiV1Fetch(
path: string,
options: RequestInit,
apiKey: string
): Promise<ApiV1Result> {
const url = `${getServerUrl()}${path}`;
let res: Response;
try {
res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
"x-api-key": apiKey,
},
});
} catch {
return { ok: false, status: 0, error: "Failed to reach internal API" };
}
// 204 No Content has no body
if (res.status === 204) {
return { ok: true, status: 204, data: null };
}
let body: unknown;
try {
body = await res.json();
} catch {
body = null;
}
if (!res.ok) {
const message =
typeof body === "object" &&
body !== null &&
"error" in body &&
typeof (body as Record<string, unknown>).error === "string"
? (body as { error: string }).error
: `Request failed with status ${res.status}`;
return { ok: false, status: res.status, error: message };
}
const data =
typeof body === "object" &&
body !== null &&
"data" in body
? (body as { data: unknown }).data
: body;
return { ok: true, status: res.status, data };
}
+29
View File
@@ -0,0 +1,29 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { ApiKeyContext } from "@/lib/api-v1/types";
import { registerAgentTools } from "./tools/agents";
import { registerDatabaseTools } from "./tools/databases";
import { registerBackupTools } from "./tools/backups";
/**
* Creates a configured McpServer for a single request.
* A new instance is created per request (stateless transport pattern).
*
* @param _ctx - The validated API key context (user + org memberships). Reserved for
* future tools that need server-side context beyond forwarded HTTP calls.
* @param apiKey - The raw API key forwarded to all /api/v1 REST calls.
*/
export function createPortabaseMcpServer(
_ctx: ApiKeyContext,
apiKey: string
): McpServer {
const server = new McpServer({
name: "portabase",
version: process.env.npm_package_version ?? "1.0.0",
});
registerAgentTools(server, apiKey);
registerDatabaseTools(server, apiKey);
registerBackupTools(server, apiKey);
return server;
}
+70
View File
@@ -0,0 +1,70 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {apiV1Fetch} from "@/lib/mcp/http-client";
import {err, ok} from "@/lib/mcp/tools/response";
export function registerAgentTools(server: McpServer, apiKey: string) {
server.tool(
"list_agents",
"List all agents accessible to the authenticated user",
{},
async () => {
const result = await apiV1Fetch("/api/v1/agents", { method: "GET" }, apiKey);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"get_agent",
"Get details for a specific agent, including its associated databases",
{ id: z.string().describe("Agent ID") },
async ({ id }) => {
const result = await apiV1Fetch(`/api/v1/agents/${id}`, { method: "GET" }, apiKey);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"create_agent",
"Create a new agent, optionally scoped to an organization",
{
name: z.string().min(1).describe("Agent name"),
organizationId: z
.string()
.uuid()
.optional()
.describe("Organization ID to scope the agent to (optional)"),
},
async ({ name, organizationId }) => {
const result = await apiV1Fetch(
"/api/v1/agents",
{ method: "POST", body: JSON.stringify({ name, organizationId }) },
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"delete_agent",
"Delete an agent by ID",
{ id: z.string().describe("Agent ID") },
async ({ id }) => {
const result = await apiV1Fetch(`/api/v1/agents/${id}`, { method: "DELETE" }, apiKey);
return result.ok
? ok({ message: `Agent ${id} deleted successfully` })
: err(result.error);
}
);
server.tool(
"get_agent_key",
"Get the edge key for an agent (used by the agent to authenticate with Portabase)",
{ id: z.string().describe("Agent ID") },
async ({ id }) => {
const result = await apiV1Fetch(`/api/v1/agents/${id}/key`, { method: "GET" }, apiKey);
return result.ok ? ok(result.data) : err(result.error);
}
);
}
+75
View File
@@ -0,0 +1,75 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {err, ok} from "@/lib/mcp/tools/response";
import {apiV1Fetch} from "@/lib/mcp/http-client";
export function registerBackupTools(server: McpServer, apiKey: string) {
server.tool(
"list_backups",
"List all backups for a specific database, ordered by most recent first",
{ databaseId: z.string().describe("Database ID") },
async ({ databaseId }) => {
const result = await apiV1Fetch(
`/api/v1/databases/${databaseId}/backup`,
{ method: "GET" },
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"get_backup",
"Get details for a specific backup, including its storage locations",
{
databaseId: z.string().describe("Database ID"),
backupId: z.string().describe("Backup ID"),
},
async ({ databaseId, backupId }) => {
const result = await apiV1Fetch(
`/api/v1/databases/${databaseId}/backup/${backupId}`,
{ method: "GET" },
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"trigger_backup",
"Trigger an immediate backup for a database. Returns 409 if a backup is already running.",
{ databaseId: z.string().describe("Database ID") },
async ({ databaseId }) => {
const result = await apiV1Fetch(
`/api/v1/databases/${databaseId}/backup`,
{ method: "POST", body: JSON.stringify({}) },
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"trigger_restore",
"Trigger a database restore from a specific backup storage. Use get_backup to find available backupStorageId values. Returns 409 if a restore is already running.",
{
databaseId: z.string().describe("Database ID"),
backupId: z.string().uuid().describe("Backup ID"),
backupStorageId: z
.string()
.uuid()
.describe("Backup storage ID (from get_backup storages list)"),
},
async ({ databaseId, backupId, backupStorageId }) => {
const result = await apiV1Fetch(
`/api/v1/databases/${databaseId}/restore`,
{
method: "POST",
body: JSON.stringify({ backupId, backupStorageId }),
},
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
}
+41
View File
@@ -0,0 +1,41 @@
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {z} from "zod";
import {err, ok} from "@/lib/mcp/tools/response";
import {apiV1Fetch} from "@/lib/mcp/http-client";
export function registerDatabaseTools(server: McpServer, apiKey: string) {
server.tool(
"list_databases",
"List all databases accessible to the authenticated user",
{},
async () => {
const result = await apiV1Fetch("/api/v1/databases", {method: "GET"}, apiKey);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"get_database",
"Get details for a specific database",
{id: z.string().describe("Database ID")},
async ({id}) => {
const result = await apiV1Fetch(`/api/v1/databases/${id}`, {method: "GET"}, apiKey);
return result.ok ? ok(result.data) : err(result.error);
}
);
server.tool(
"get_database_status",
"Get the current status of a database, including latest backup and restoration state",
{id: z.string().describe("Database ID")},
async ({id}) => {
const result = await apiV1Fetch(
`/api/v1/databases/${id}/status`,
{method: "GET"},
apiKey
);
return result.ok ? ok(result.data) : err(result.error);
}
);
}
+12
View File
@@ -0,0 +1,12 @@
export function ok(data: unknown) {
return {
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
};
}
export function err(message: string) {
return {
content: [{ type: "text" as const, text: message }],
isError: true as const,
};
}