diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index 671e52021b5..7173dd42942 100644 --- a/.github/workflows/dev-build.yaml +++ b/.github/workflows/dev-build.yaml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: ['3157-feat-prompt-variables'] # put your current branch to create a build. Core team only. + branches: ['3000-mcp-compatibility'] # put your current branch to create a build. Core team only. paths-ignore: - '**.md' - 'cloud-deployments/*' @@ -43,6 +43,10 @@ jobs: fi id: dockerhub + # Uncomment this + add linux/arm64 to platforms if you want to build for arm64 as well + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: @@ -74,6 +78,7 @@ jobs: sbom: true provenance: mode=max platforms: linux/amd64 + # platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/docker/Dockerfile b/docker/Dockerfile index 1f7ed96dddf..221d2e400d7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,10 +24,16 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ apt-get update && \ + # Install node and yarn apt-get install -yq --no-install-recommends nodejs && \ curl -LO https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn_1.22.19_all.deb \ && dpkg -i yarn_1.22.19_all.deb \ && rm yarn_1.22.19_all.deb && \ + # Install uvx (pinned to 0.6.10) for MCP support + curl -LsSf https://astral.sh/uv/0.6.10/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx && \ + echo "Installed uvx! $(uv --version)" && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -84,10 +90,16 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ apt-get update && \ + # Install node and yarn apt-get install -yq --no-install-recommends nodejs && \ curl -LO https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn_1.22.19_all.deb \ && dpkg -i yarn_1.22.19_all.deb \ && rm yarn_1.22.19_all.deb && \ + # Install uvx (pinned to 0.6.10) for MCP support + curl -LsSf https://astral.sh/uv/0.6.10/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx && \ + echo "Installed uvx! $(uv --version)" && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -154,12 +166,6 @@ RUN chown -R anythingllm:anythingllm /app/server && \ chown -R anythingllm:anythingllm /app/collector USER anythingllm -# No longer needed? (deprecated) -# WORKDIR /app/server -# RUN npx prisma generate --schema=./prisma/schema.prisma && \ -# npx prisma migrate deploy --schema=./prisma/schema.prisma -# WORKDIR /app - # Setup the environment ENV NODE_ENV=production ENV ANYTHING_LLM_RUNTIME=docker diff --git a/frontend/src/media/agents/mcp-logo.svg b/frontend/src/media/agents/mcp-logo.svg new file mode 100644 index 00000000000..c47d4d933b7 --- /dev/null +++ b/frontend/src/media/agents/mcp-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/models/mcpServers.js b/frontend/src/models/mcpServers.js new file mode 100644 index 00000000000..40e660cc456 --- /dev/null +++ b/frontend/src/models/mcpServers.js @@ -0,0 +1,76 @@ +import { API_BASE } from "@/utils/constants"; +import { baseHeaders } from "@/utils/request"; + +const MCPServers = { + /** + * Forces a reload of the MCP Hypervisor and its servers + * @returns {Promise<{success: boolean, error: string | null, servers: Array<{name: string, running: boolean, tools: Array<{name: string, description: string, inputSchema: Object}>, error: string | null, process: {pid: number, cmd: string} | null}>}>} + */ + forceReload: async () => { + return await fetch(`${API_BASE}/mcp-servers/force-reload`, { + method: "GET", + headers: baseHeaders(), + }) + .then((res) => res.json()) + .catch((e) => ({ + servers: [], + success: false, + error: e.message, + })); + }, + + /** + * List all available MCP servers in the system + * @returns {Promise<{success: boolean, error: string | null, servers: Array<{name: string, running: boolean, tools: Array<{name: string, description: string, inputSchema: Object}>, error: string | null, process: {pid: number, cmd: string} | null}>}>} + */ + listServers: async () => { + return await fetch(`${API_BASE}/mcp-servers/list`, { + method: "GET", + headers: baseHeaders(), + }) + .then((res) => res.json()) + .catch((e) => ({ + success: false, + error: e.message, + servers: [], + })); + }, + + /** + * Toggle the MCP server (start or stop) + * @param {string} name - The name of the MCP server to toggle + * @returns {Promise<{success: boolean, error: string | null}>} + */ + toggleServer: async (name) => { + return await fetch(`${API_BASE}/mcp-servers/toggle`, { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify({ name }), + }) + .then((res) => res.json()) + .catch((e) => ({ + success: false, + error: e.message, + })); + }, + + /** + * Delete the MCP server - will also remove it from the config file + * @param {string} name - The name of the MCP server to delete + * @returns {Promise<{success: boolean, error: string | null}>} + */ + deleteServer: async (name) => { + return await fetch(`${API_BASE}/mcp-servers/delete`, { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify({ name }), + }) + .then((res) => res.json()) + .catch((e) => ({ + success: false, + error: e.message, + })); + }, +}; + +export default MCPServers; diff --git a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx index 75b70efcc0c..4bb7d17e514 100644 --- a/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx +++ b/frontend/src/pages/Admin/Agents/AgentFlows/index.jsx @@ -22,7 +22,7 @@ export default function AgentFlowsList({ } return ( -
+
{flows.map((flow, index) => (
{skills.map((config, index) => (
{ + const handleClickOutside = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target)) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+ + {open && ( +
+ + +
+ )} +
+ ); +} + +export default function ServerPanel({ server, toggleServer, onDelete }) { + return ( + <> +
+
+
+
+ + + {server.tools.length > 0 && ( +

+ {server.tools.length} {pluralize("tool", server.tools.length)}{" "} + available +

+ )} +
+ +
+ + + +
+
+ + ); +} + +function RenderServerConfig({ config = null }) { + if (!config) return null; + return ( +
+

Startup Command

+
+

+ Command: {config.command} +

+

+ Arguments:{" "} + {config.args ? config.args.join(" ") : "None"} +

+
+
+ ); +} + +function RenderServerStatus({ server }) { + if (server.running || !server.error) return null; + return ( +
+

+ This MCP server is not running - it may be stopped or experiencing an + error on startup. +

+
+

{server.error}

+
+
+ ); +} + +function RenderServerTools({ tools = [] }) { + if (tools.length === 0) return null; + return ( +
+
+ {tools.map((tool) => ( + + ))} +
+
+ ); +} + +function ServerTool({ tool }) { + const [open, setOpen] = useState(false); + + return ( + + ); +} diff --git a/frontend/src/pages/Admin/Agents/MCPServers/index.jsx b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx new file mode 100644 index 00000000000..e2af7f37378 --- /dev/null +++ b/frontend/src/pages/Admin/Agents/MCPServers/index.jsx @@ -0,0 +1,147 @@ +import { useState, useEffect } from "react"; +import { titleCase } from "text-case"; +import { Link } from "react-router-dom"; +import { BookOpenText, ArrowClockwise } from "@phosphor-icons/react"; +import MCPLogo from "@/media/agents/mcp-logo.svg"; +import MCPServers from "@/models/mcpServers"; + +export function MCPServerHeader({ setMcpServers, children }) { + const [loadingMcpServers, setLoadingMcpServers] = useState(false); + useEffect(() => { + async function fetchMCPServers() { + setLoadingMcpServers(true); + const { servers = [] } = await MCPServers.listServers(); + setMcpServers(servers); + setLoadingMcpServers(false); + } + fetchMCPServers(); + }, []); + + // Refresh the list of MCP servers + const refreshMCPServers = () => { + if ( + window.confirm( + "Are you sure you want to refresh the list of MCP servers? This will restart all MCP servers and reload their tools." + ) + ) { + setLoadingMcpServers(true); + MCPServers.forceReload() + .then(({ servers = [] }) => { + setSelectedMcpServer(null); + setMcpServers(servers); + }) + .catch((err) => { + console.error(err); + showToast(`Failed to refresh MCP servers.`, "error", { clear: true }); + }) + .finally(() => { + setLoadingMcpServers(false); + }); + } + }; + + return ( + <> +
+
+ +

MCP Servers

+
+
+ + + + +
+
+ {children({ loadingMcpServers })} + + ); +} + +export function MCPServersList({ + isLoading = false, + servers = [], + selectedServer, + handleClick, +}) { + if (isLoading) { + return ( +
+

Loading MCP Servers from configuration file...

+ + Learn more about MCP Servers. + +
+ ); + } + + if (servers.length === 0) { + return ( +
+

No MCP servers found

+ + Learn more about MCP Servers. + +
+ ); + } + + return ( +
+ {servers.map((server, index) => ( +
handleClick?.(server)} + > +
+ {titleCase(server.name.replace(/[_-]/g, " "))} +
+
+
+ {server.running ? "On" : "Stopped"} +
+
+
+ ))} +
+ ); +} diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index dada8c38892..69781dd34ba 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -11,7 +11,6 @@ import { Robot, Hammer, FlowArrow, - PlusCircle, } from "@phosphor-icons/react"; import ContextualSaveBar from "@/components/ContextualSaveBar"; import { castToType } from "@/utils/types"; @@ -23,6 +22,8 @@ import ImportedSkillConfig from "./Imported/ImportedSkillConfig"; import { Tooltip } from "react-tooltip"; import AgentFlowsList from "./AgentFlows"; import FlowPanel from "./AgentFlows/FlowPanel"; +import { MCPServersList, MCPServerHeader } from "./MCPServers"; +import ServerPanel from "./MCPServers/ServerPanel"; import { Link } from "react-router-dom"; import paths from "@/utils/paths"; import AgentFlows from "@/models/agentFlows"; @@ -43,6 +44,10 @@ export default function AdminAgents() { const [selectedFlow, setSelectedFlow] = useState(null); const [activeFlowIds, setActiveFlowIds] = useState([]); + // MCP Servers are lazy loaded to not block the UI thread + const [mcpServers, setMcpServers] = useState([]); + const [selectedMcpServer, setSelectedMcpServer] = useState(null); + // Alert user if they try to leave the page with unsaved changes useEffect(() => { const handleBeforeUnload = (event) => { @@ -67,6 +72,7 @@ export default function AdminAgents() { "active_agent_flows", ]); const { flows = [] } = await AgentFlows.listFlows(); + setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); setAgentSkills(_preferences.settings?.default_agent_skills ?? []); setDisabledAgentSkills( @@ -109,6 +115,15 @@ export default function AdminAgents() { }); }; + const toggleMCP = (serverName) => { + setMcpServers((prev) => { + return prev.map((server) => { + if (server.name !== serverName) return server; + return { ...server, running: !server.running }; + }); + }); + }; + const handleSubmit = async (e) => { e.preventDefault(); const data = { @@ -159,23 +174,46 @@ export default function AdminAgents() { setHasChanges(false); }; - const SelectedSkillComponent = selectedFlow - ? FlowPanel - : selectedSkill?.imported - ? ImportedSkillConfig - : configurableSkills[selectedSkill]?.component || - defaultSkills[selectedSkill]?.component; + let SelectedSkillComponent = null; + if (selectedFlow) { + SelectedSkillComponent = FlowPanel; + } else if (selectedMcpServer) { + SelectedSkillComponent = ServerPanel; + } else if (selectedSkill?.imported) { + SelectedSkillComponent = ImportedSkillConfig; + } else if (configurableSkills[selectedSkill]) { + SelectedSkillComponent = configurableSkills[selectedSkill]?.component; + } else { + SelectedSkillComponent = defaultSkills[selectedSkill]?.component; + } // Update the click handlers to clear the other selection + const handleDefaultSkillClick = (skill) => { + setSelectedFlow(null); + setSelectedMcpServer(null); + setSelectedSkill(skill); + if (isMobile) setShowSkillModal(true); + }; + const handleSkillClick = (skill) => { setSelectedFlow(null); + setSelectedMcpServer(null); setSelectedSkill(skill); if (isMobile) setShowSkillModal(true); }; const handleFlowClick = (flow) => { setSelectedSkill(null); + setSelectedMcpServer(null); setSelectedFlow(flow); + if (isMobile) setShowSkillModal(true); + }; + + const handleMCPClick = (server) => { + setSelectedSkill(null); + setSelectedFlow(null); + setSelectedMcpServer(server); + if (isMobile) setShowSkillModal(true); }; const handleFlowDelete = (flowId) => { @@ -184,6 +222,13 @@ export default function AdminAgents() { setAgentFlows((prev) => prev.filter((flow) => flow.uuid !== flowId)); }; + const handleMCPServerDelete = (serverName) => { + setSelectedMcpServer(null); + setMcpServers((prev) => + prev.filter((server) => server.name !== serverName) + ); + }; + if (loading) { return (
{/* Skill settings nav */} - {/* Selected agent skill setting panel */}
-
+
{SelectedSkillComponent ? ( <> - {selectedFlow ? ( + {selectedMcpServer ? ( + + ) : selectedFlow ? ( -

Select an agent skill or flow

+

+ Select an Agent Skill, Agent Flow, or MCP Server +

)}
diff --git a/server/.gitignore b/server/.gitignore index b49fc0b32ba..4efa8eb5122 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -10,6 +10,7 @@ storage/exports storage/imports storage/plugins/agent-skills/* storage/plugins/agent-flows/* +storage/plugins/anythingllm_mcp_servers.json !storage/documents/DOCUMENTS.md logs/server.log *.db diff --git a/server/endpoints/mcpServers.js b/server/endpoints/mcpServers.js new file mode 100644 index 00000000000..3cd5a865696 --- /dev/null +++ b/server/endpoints/mcpServers.js @@ -0,0 +1,100 @@ +const { reqBody } = require("../utils/http"); +const MCPCompatibilityLayer = require("../utils/MCP"); +const { + flexUserRoleValid, + ROLES, +} = require("../utils/middleware/multiUserProtected"); +const { validatedRequest } = require("../utils/middleware/validatedRequest"); + +function mcpServersEndpoints(app) { + if (!app) return; + + app.get( + "/mcp-servers/force-reload", + [validatedRequest, flexUserRoleValid([ROLES.admin])], + async (_request, response) => { + try { + const mcp = new MCPCompatibilityLayer(); + await mcp.reloadMCPServers(); + return response.status(200).json({ + success: true, + error: null, + servers: await mcp.servers(), + }); + } catch (error) { + console.error("Error force reloading MCP servers:", error); + return response.status(500).json({ + success: false, + error: error.message, + servers: [], + }); + } + } + ); + + app.get( + "/mcp-servers/list", + [validatedRequest, flexUserRoleValid([ROLES.admin])], + async (_request, response) => { + try { + const servers = await new MCPCompatibilityLayer().servers(); + return response.status(200).json({ + success: true, + servers, + }); + } catch (error) { + console.error("Error listing MCP servers:", error); + return response.status(500).json({ + success: false, + error: error.message, + }); + } + } + ); + + app.post( + "/mcp-servers/toggle", + [validatedRequest, flexUserRoleValid([ROLES.admin])], + async (request, response) => { + try { + const { name } = reqBody(request); + const result = await new MCPCompatibilityLayer().toggleServerStatus( + name + ); + return response.status(200).json({ + success: result.success, + error: result.error, + }); + } catch (error) { + console.error("Error toggling MCP server:", error); + return response.status(500).json({ + success: false, + error: error.message, + }); + } + } + ); + + app.post( + "/mcp-servers/delete", + [validatedRequest, flexUserRoleValid([ROLES.admin])], + async (request, response) => { + try { + const { name } = reqBody(request); + const result = await new MCPCompatibilityLayer().deleteServer(name); + return response.status(200).json({ + success: result.success, + error: result.error, + }); + } catch (error) { + console.error("Error deleting MCP server:", error); + return response.status(500).json({ + success: false, + error: error.message, + }); + } + } + ); +} + +module.exports = { mcpServersEndpoints }; diff --git a/server/index.js b/server/index.js index 78964e78e05..4e79e8fdc31 100644 --- a/server/index.js +++ b/server/index.js @@ -27,6 +27,7 @@ const { experimentalEndpoints } = require("./endpoints/experimental"); const { browserExtensionEndpoints } = require("./endpoints/browserExtension"); const { communityHubEndpoints } = require("./endpoints/communityHub"); const { agentFlowEndpoints } = require("./endpoints/agentFlows"); +const { mcpServersEndpoints } = require("./endpoints/mcpServers"); const app = express(); const apiRouter = express.Router(); const FILE_LIMIT = "3GB"; @@ -63,6 +64,7 @@ experimentalEndpoints(apiRouter); developerEndpoints(app, apiRouter); communityHubEndpoints(apiRouter); agentFlowEndpoints(apiRouter); +mcpServersEndpoints(apiRouter); // Externally facing embedder endpoints embeddedEndpoints(apiRouter); diff --git a/server/package.json b/server/package.json index 55e304e094f..db153eb2145 100644 --- a/server/package.json +++ b/server/package.json @@ -34,6 +34,7 @@ "@langchain/textsplitters": "0.0.0", "@mintplex-labs/bree": "^9.2.5", "@mintplex-labs/express-ws": "^5.0.7", + "@modelcontextprotocol/sdk": "^1.8.0", "@pinecone-database/pinecone": "^2.0.1", "@prisma/client": "5.3.1", "@qdrant/js-client-rest": "^1.9.0", diff --git a/server/utils/MCP/hypervisor/index.js b/server/utils/MCP/hypervisor/index.js new file mode 100644 index 00000000000..0073d205c10 --- /dev/null +++ b/server/utils/MCP/hypervisor/index.js @@ -0,0 +1,322 @@ +const { safeJsonParse } = require("../../http"); +const path = require("path"); +const fs = require("fs"); +const { Client } = require("@modelcontextprotocol/sdk/client/index.js"); +const { + StdioClientTransport, +} = require("@modelcontextprotocol/sdk/client/stdio.js"); + +/** + * @class MCPHypervisor + * @description A class that manages MCP servers found in the storage/plugins/anythingllm_mcp_servers.json file. + * This class is responsible for booting, stopping, and reloading MCP servers - it is the user responsiblity for the MCP server definitions + * to me correct and also functioning tools depending on their deployment (docker vs local) as well as the security of said tools + * since MCP is basically arbitrary code execution. + * + * @notice This class is a singleton. + * @notice Each MCP tool has dependencies specific to it and this call WILL NOT check for them. + * For example, if the tools requires `npx` then the context in which AnythingLLM mains process is running will need to access npx. + * This is typically not common in our pre-built image so may not function. But this is the case anywhere MCP is used. + * + * AnythingLLM will take care of porting MCP servers to agent-callable functions via @agent directive. + * @see MCPCompatibilityLayer.convertServerToolsToPlugins + */ +class MCPHypervisor { + static _instance; + /** + * The path to the JSON file containing the MCP server definitions. + * @type {string} + */ + mcpServerJSONPath; + + /** + * The MCP servers currently running. + * @type { { [key: string]: Client & {transport: {_process: import('child_process').ChildProcess}, aibitatToolIds: string[]} } } + */ + mcps = {}; + /** + * The results of the MCP server loading process. + * @type { { [key: string]: {status: 'success' | 'failed', message: string} } } + */ + mcpLoadingResults = {}; + + constructor() { + if (MCPHypervisor._instance) return MCPHypervisor._instance; + MCPHypervisor._instance = this; + this.log("Initializing MCP Hypervisor - subsequent calls will boot faster"); + this.#setupConfigFile(); + return this; + } + + /** + * Setup the MCP server definitions file. + * Will create the file/directory if it doesn't exist already in storage/plugins with blank options + */ + #setupConfigFile() { + this.mcpServerJSONPath = + process.env.NODE_ENV === "development" + ? path.resolve( + __dirname, + `../../../storage/plugins/anythingllm_mcp_servers.json` + ) + : path.resolve( + process.env.STORAGE_DIR ?? + path.resolve(__dirname, `../../../storage`), + `plugins/anythingllm_mcp_servers.json` + ); + + if (!fs.existsSync(this.mcpServerJSONPath)) { + fs.mkdirSync(path.dirname(this.mcpServerJSONPath), { recursive: true }); + fs.writeFileSync( + this.mcpServerJSONPath, + JSON.stringify({ mcpServers: {} }, null, 2), + { encoding: "utf8" } + ); + } + + this.log(`MCP Config File: ${this.mcpServerJSONPath}`); + } + + log(text, ...args) { + console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args); + } + + /** + * Get the MCP servers from the JSON file. + * @returns { { name: string, server: { command: string, args: string[], env: { [key: string]: string } } }[] } The MCP servers. + */ + get mcpServerConfigs() { + const servers = safeJsonParse( + fs.readFileSync(this.mcpServerJSONPath, "utf8"), + { mcpServers: {} } + ); + return Object.entries(servers.mcpServers).map(([name, server]) => ({ + name, + server, + })); + } + + /** + * Remove the MCP server from the config file + * @param {string} name - The name of the MCP server to remove + * @returns {boolean} - True if the MCP server was removed, false otherwise + */ + removeMCPServerFromConfig(name) { + const servers = safeJsonParse( + fs.readFileSync(this.mcpServerJSONPath, "utf8"), + { mcpServers: {} } + ); + if (!servers.mcpServers[name]) return false; + + delete servers.mcpServers[name]; + fs.writeFileSync( + this.mcpServerJSONPath, + JSON.stringify(servers, null, 2), + "utf8" + ); + this.log(`MCP server ${name} removed from config file`); + return true; + } + + /** + * Reload the MCP servers - can be used to reload the MCP servers without restarting the server or app + * and will also apply changes to the config file if any where made. + */ + async reloadMCPServers() { + this.pruneMCPServers(); + await this.bootMCPServers(); + } + + /** + * Start a single MCP server by its server name - public method + * @param {string} name - The name of the MCP server to start + * @returns {Promise<{success: boolean, error: string | null}>} + */ + async startMCPServer(name) { + if (this.mcps[name]) + return { success: false, error: `MCP server ${name} already running` }; + const config = this.mcpServerConfigs.find((s) => s.name === name); + if (!config) + return { + success: false, + error: `MCP server ${name} not found in config file`, + }; + + try { + await this.#startMCPServer(config); + this.mcpLoadingResults[name] = { + status: "success", + message: `Successfully connected to MCP server: ${name}`, + }; + + return { success: true, message: `MCP server ${name} started` }; + } catch (e) { + this.log(`Failed to start single MCP server: ${name}`, { + error: e.message, + code: e.code, + syscall: e.syscall, + path: e.path, + stack: e.stack, + }); + this.mcpLoadingResults[name] = { + status: "failed", + message: `Failed to start MCP server: ${name} [${e.code || "NO_CODE"}] ${e.message}`, + }; + + // Clean up failed connection + if (this.mcps[name]) { + this.mcps[name].close(); + delete this.mcps[name]; + } + + return { success: false, error: e.message }; + } + } + /** + * Prune a single MCP server by its server name + * @param {string} name - The name of the MCP server to prune + * @returns {boolean} - True if the MCP server was pruned, false otherwise + */ + pruneMCPServer(name) { + if (!name || !this.mcps[name]) return true; + + this.log(`Pruning MCP server: ${name}`); + const mcp = this.mcps[name]; + const childProcess = mcp.transport._process; + if (childProcess) childProcess.kill(1); + mcp.transport.close(); + + delete this.mcps[name]; + this.mcpLoadingResults[name] = { + status: "failed", + message: `Server was stopped manually by the administrator.`, + }; + return true; + } + + /** + * Prune the MCP servers - pkills and forgets all MCP servers + * @returns {void} + */ + pruneMCPServers() { + this.log(`Pruning ${Object.keys(this.mcps).length} MCP servers...`); + + for (const name of Object.keys(this.mcps)) { + if (!this.mcps[name]) continue; + const mcp = this.mcps[name]; + const childProcess = mcp.transport._process; + if (childProcess) + this.log(`Killing MCP ${name} (PID: ${childProcess.pid})`, { + killed: childProcess.kill(1), + }); + + mcp.transport.close(); + mcp.close(); + } + this.mcps = {}; + this.mcpLoadingResults = {}; + } + + /** + * @private Start a single MCP server by its server definition from the JSON file + * @param {string} name - The name of the MCP server to start + * @param {Object} server - The server definition + * @returns {Promise} + */ + async #startMCPServer({ name, server }) { + if (!name) throw new Error("MCP server name is required"); + if (!server) throw new Error("MCP server definition is required"); + if (!server.command) throw new Error("MCP server command is required"); + if (server.hasOwnProperty("args") && !Array.isArray(server.args)) + throw new Error("MCP server args must be an array"); + + this.log(`Attempting to start MCP server: ${name}`); + const mcp = new Client({ name: name, version: "1.0.0" }); + const transport = new StdioClientTransport({ + command: server.command, + args: server?.args ?? [], + }); + + // Add connection event listeners + transport.onclose = () => this.log(`${name} - Transport closed`); + transport.onerror = (error) => + this.log(`${name} - Transport error:`, error); + transport.onmessage = (message) => + this.log(`${name} - Transport message:`, message); + + // Connect and await the connection with a timeout + this.mcps[name] = mcp; + const connectionPromise = mcp.connect(transport); + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error("Connection timeout")), 30_000); // 30 second timeout + }); + await Promise.race([connectionPromise, timeoutPromise]); + return true; + } + + /** + * Boot the MCP servers according to the server definitions. + * This function will skip booting MCP servers if they are already running. + * @returns { Promise<{ [key: string]: {status: string, message: string} }> } The results of the boot process. + */ + async bootMCPServers() { + if (Object.keys(this.mcps).length > 0) { + this.log("MCP Servers already running, skipping boot."); + return this.mcpLoadingResults; + } + + const serverDefinitions = this.mcpServerConfigs; + for (const { name, server } of serverDefinitions) { + if ( + server.anythingllm?.hasOwnProperty("autoStart") && + server.anythingllm.autoStart === false + ) { + this.log( + `MCP server ${name} has anythingllm.autoStart property set to false, skipping boot!` + ); + this.mcpLoadingResults[name] = { + status: "failed", + message: `MCP server ${name} has anythingllm.autoStart property set to false, boot skipped!`, + }; + continue; + } + + try { + await this.#startMCPServer({ name, server }); + // Verify the connection is alive? + // if (!(await mcp.ping())) throw new Error('Connection failed to establish'); + this.mcpLoadingResults[name] = { + status: "success", + message: `Successfully connected to MCP server: ${name}`, + }; + } catch (e) { + this.log(`Failed to start MCP server: ${name}`, { + error: e.message, + code: e.code, + syscall: e.syscall, + path: e.path, + stack: e.stack, // Adding stack trace for better debugging + }); + this.mcpLoadingResults[name] = { + status: "failed", + message: `Failed to start MCP server: ${name} [${e.code || "NO_CODE"}] ${e.message}`, + }; + + // Clean up failed connection + if (this.mcps[name]) { + this.mcps[name].close(); + delete this.mcps[name]; + } + } + } + + const runningServers = Object.keys(this.mcps); + this.log( + `Successfully started ${runningServers.length} MCP servers:`, + runningServers + ); + return this.mcpLoadingResults; + } +} + +module.exports = MCPHypervisor; diff --git a/server/utils/MCP/index.js b/server/utils/MCP/index.js new file mode 100644 index 00000000000..1b4439c9cc2 --- /dev/null +++ b/server/utils/MCP/index.js @@ -0,0 +1,203 @@ +const MCPHypervisor = require("./hypervisor"); + +class MCPCompatibilityLayer extends MCPHypervisor { + static _instance; + + constructor() { + super(); + if (MCPCompatibilityLayer._instance) return MCPCompatibilityLayer._instance; + MCPCompatibilityLayer._instance = this; + } + + /** + * Get all of the active MCP servers as plugins we can load into agents. + * This will also boot all MCP servers if they have not been started yet. + * @returns {Promise} Array of flow names in @@mcp_{name} format + */ + async activeMCPServers() { + await this.bootMCPServers(); + return Object.keys(this.mcps).flatMap((name) => `@@mcp_${name}`); + } + + /** + * Convert an MCP server name to an AnythingLLM Agent plugin + * @param {string} name - The base name of the MCP server to convert - not the tool name. eg: `docker-mcp` not `docker-mcp:list-containers` + * @param {Object} aibitat - The aibitat object to pass to the plugin + * @returns {Promise<{name: string, description: string, plugin: Function}[]|null>} Array of plugin configurations or null if not found + */ + async convertServerToolsToPlugins(name, _aibitat = null) { + const mcp = this.mcps[name]; + if (!mcp) return null; + + const tools = (await mcp.listTools()).tools; + if (!tools.length) return null; + + const plugins = []; + for (const tool of tools) { + plugins.push({ + name: `${name}-${tool.name}`, + description: tool.description, + plugin: function () { + return { + name: `${name}-${tool.name}`, + setup: (aibitat) => { + aibitat.function({ + super: aibitat, + name: `${name}-${tool.name}`, + controller: new AbortController(), + description: tool.description, + examples: [], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + ...tool.inputSchema, + }, + handler: async function (args = {}) { + try { + aibitat.handlerProps.log( + `Executing MCP server: ${name}:${tool.name} with args:`, + args + ); + aibitat.introspect( + `Executing MCP server: ${name} with ${JSON.stringify(args, null, 2)}` + ); + const result = await mcp.callTool({ + name: tool.name, + arguments: args, + }); + aibitat.handlerProps.log( + `MCP server: ${name}:${tool.name} completed successfully`, + result + ); + aibitat.introspect( + `MCP server: ${name}:${tool.name} completed successfully` + ); + return typeof result === "object" + ? JSON.stringify(result) + : String(result); + } catch (error) { + aibitat.handlerProps.log( + `MCP server: ${name}:${tool.name} failed with error:`, + error + ); + aibitat.introspect( + `MCP server: ${name}:${tool.name} failed with error:`, + error + ); + return `The tool ${name}:${tool.name} failed with error: ${error?.message || "An unknown error occurred"}`; + } + }, + }); + }, + }; + }, + toolName: `${name}:${tool.name}`, + }); + } + + return plugins; + } + + /** + * Returns the MCP servers that were loaded or attempted to be loaded + * so that we can display them in the frontend for review or error logging. + * @returns {Promise<{ + * name: string, + * running: boolean, + * tools: {name: string, description: string, inputSchema: Object}[], + * process: {pid: number, cmd: string}|null, + * error: string|null + * }[]>} - The active MCP servers + */ + async servers() { + await this.bootMCPServers(); + const servers = []; + for (const [name, result] of Object.entries(this.mcpLoadingResults)) { + const config = this.mcpServerConfigs.find((s) => s.name === name); + + if (result.status === "failed") { + servers.push({ + name, + config: config?.server || null, + running: false, + tools: [], + error: result.message, + process: null, + }); + continue; + } + + const mcp = this.mcps[name]; + if (!mcp) { + delete this.mcpLoadingResults[name]; + delete this.mcps[name]; + continue; + } + + const online = !!(await mcp.ping()); + const tools = online ? (await mcp.listTools()).tools : []; + servers.push({ + name, + config: config?.server || null, + running: online, + tools, + error: null, + process: { + pid: mcp.transport._process.pid, + }, + }); + } + return servers; + } + + /** + * Toggle the MCP server (start or stop) + * @param {string} name - The name of the MCP server to toggle + * @returns {Promise<{success: boolean, error: string | null}>} + */ + async toggleServerStatus(name) { + const server = this.mcpServerConfigs.find((s) => s.name === name); + if (!server) + return { + success: false, + error: `MCP server ${name} not found in config file.`, + }; + const mcp = this.mcps[name]; + const online = !!mcp ? !!(await mcp.ping()) : false; // If the server is not in the mcps object, it is not running + + if (online) { + const killed = this.pruneMCPServer(name); + return { + success: killed, + error: killed ? null : `Failed to kill MCP server: ${name}`, + }; + } else { + const startupResult = await this.startMCPServer(name); + return { success: startupResult.success, error: startupResult.error }; + } + } + + /** + * Delete the MCP server - will also remove it from the config file + * @param {string} name - The name of the MCP server to delete + * @returns {Promise<{success: boolean, error: string | null}>} + */ + async deleteServer(name) { + const server = this.mcpServerConfigs.find((s) => s.name === name); + if (!server) + return { + success: false, + error: `MCP server ${name} not found in config file.`, + }; + + const mcp = this.mcps[name]; + const online = !!mcp ? !!(await mcp.ping()) : false; // If the server is not in the mcps object, it is not running + if (online) this.pruneMCPServer(name); + this.removeMCPServerFromConfig(name); + + delete this.mcps[name]; + delete this.mcpLoadingResults[name]; + this.log(`MCP server was killed and removed from config file: ${name}`); + return { success: true, error: null }; + } +} +module.exports = MCPCompatibilityLayer; diff --git a/server/utils/agents/defaults.js b/server/utils/agents/defaults.js index c21a6526de9..ee12974cf80 100644 --- a/server/utils/agents/defaults.js +++ b/server/utils/agents/defaults.js @@ -4,6 +4,7 @@ const { safeJsonParse } = require("../http"); const Provider = require("./aibitat/providers/ai-provider"); const ImportedPlugin = require("./imported"); const { AgentFlows } = require("../agentFlows"); +const MCPCompatibilityLayer = require("../MCP"); // This is a list of skills that are built-in and default enabled. const DEFAULT_SKILLS = [ @@ -31,6 +32,7 @@ const WORKSPACE_AGENT = { ...(await agentSkillsFromSystemSettings()), ...ImportedPlugin.activeImportedPlugins(), ...AgentFlows.activeFlowPlugins(), + ...(await new MCPCompatibilityLayer().activeMCPServers()), ], }; }, diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index c06751c6d47..595f233f00e 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -8,6 +8,7 @@ const { safeJsonParse } = require("../http"); const { USER_AGENT, WORKSPACE_AGENT } = require("./defaults"); const ImportedPlugin = require("./imported"); const { AgentFlows } = require("../agentFlows"); +const MCPCompatibilityLayer = require("../MCP"); class AgentHandler { #invocationUUID; @@ -411,6 +412,43 @@ class AgentHandler { continue; } + // Load MCP plugin. This is marked by `@@mcp_` in the array of functions to load. + // All sub-tools are loaded here and are denoted by `pluginName:toolName` as their identifier. + // This will replace the parent MCP server plugin with the sub-tools as child plugins so they + // can be called directly by the agent when invoked. + // Since to get to this point, the `activeMCPServers` method has already been called, we can + // safely assume that the MCP server is running and the tools are available/loaded. + if (name.startsWith("@@mcp_")) { + const mcpPluginName = name.replace("@@mcp_", ""); + const plugins = + await new MCPCompatibilityLayer().convertServerToolsToPlugins( + mcpPluginName, + this.aibitat + ); + if (!plugins) { + this.log( + `MCP ${mcpPluginName} not found in MCP server config. Skipping inclusion to agent cluster.` + ); + continue; + } + + // Remove the old function from the agent functions directly + // and push the new ones onto the end of the array so that they are loaded properly. + this.aibitat.agents.get("@agent").functions = this.aibitat.agents + .get("@agent") + .functions.filter((f) => f.name !== name); + for (const plugin of plugins) + this.aibitat.agents.get("@agent").functions.push(plugin.name); + + plugins.forEach((plugin) => { + this.aibitat.use(plugin.plugin()); + this.log( + `Attached MCP::${plugin.toolName} MCP tool to Agent cluster` + ); + }); + continue; + } + // Load imported plugin. This is marked by `@@` in the array of functions to load. // and is the @@hubID of the plugin. if (name.startsWith("@@")) { diff --git a/server/yarn.lock b/server/yarn.lock index 8ca18821622..d263a84b1b7 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1650,6 +1650,22 @@ dependencies: ws "^7.5.10" +"@modelcontextprotocol/sdk@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz#55cdd65054ec24e53800250c70e07429d669db67" + integrity sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ== + dependencies: + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.3" + eventsource "^3.0.2" + express "^5.0.1" + express-rate-limit "^7.5.0" + pkce-challenge "^4.1.0" + raw-body "^3.0.0" + zod "^3.23.8" + zod-to-json-schema "^3.24.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -2790,6 +2806,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" @@ -3170,6 +3194,21 @@ body-parser@1.20.2, body-parser@^1.20.2: type-is "~1.6.18" unpipe "1.0.0" +body-parser@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.1.0.tgz#2fd84396259e00fa75648835e2d95703bce8e890" + integrity sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.5.2" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + boolbase@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" @@ -3238,11 +3277,19 @@ busboy@^1.0.0: dependencies: streamsearch "^1.1.0" -bytes@3.1.2: +bytes@3.1.2, bytes@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -3254,6 +3301,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -3572,7 +3627,14 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4, content-type@~1.0.5: +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -3582,11 +3644,21 @@ cookie-signature@1.0.6: resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + cookie@0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" @@ -3711,6 +3783,13 @@ debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: dependencies: ms "2.1.2" +debug@4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -3718,6 +3797,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.5, debug@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decamelize@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" @@ -3788,7 +3874,7 @@ depd@2.0.0: resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@1.2.0: +destroy@1.2.0, destroy@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -3864,6 +3950,15 @@ dotenv@^16.0.3: resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" @@ -3903,6 +3998,11 @@ encode32@^1.1.0: resolved "https://registry.npmjs.org/encode32/-/encode32-1.1.0.tgz" integrity sha512-BCmijZ4lWec5+fuGHclf7HSZf+mos2ncQkBjtvomvRWVEGAMI/tw56fuN2x4e+FTgQuTPbZjODPwX80lFy958w== +encodeurl@^2.0.0, encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" @@ -4008,6 +4108,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" @@ -4040,6 +4145,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz" @@ -4070,7 +4182,7 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escape-html@~1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== @@ -4225,7 +4337,7 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +etag@^1.8.1, etag@~1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== @@ -4245,6 +4357,18 @@ events@^3.0.0, events@^3.3.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +eventsource-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.0.tgz#9303e303ef807d279ee210a17ce80f16300d9f57" + integrity sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA== + +eventsource@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.5.tgz#0cae1eee2d2c75894de8b02a91d84e5c57f0cc5a" + integrity sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw== + dependencies: + eventsource-parser "^3.0.0" + execa@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -4270,6 +4394,11 @@ expr-eval@^2.0.2: resolved "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz" integrity sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg== +express-rate-limit@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" + integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== + express@^4.18.2: version "4.19.2" resolved "https://registry.npmjs.org/express/-/express-4.19.2.tgz" @@ -4307,6 +4436,44 @@ express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" +express@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.0.1.tgz#5d359a2550655be33124ecbc7400cd38436457e9" + integrity sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ== + dependencies: + accepts "^2.0.0" + body-parser "^2.0.1" + content-disposition "^1.0.0" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "^1.2.1" + debug "4.3.6" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "^2.0.0" + fresh "2.0.0" + http-errors "2.0.0" + merge-descriptors "^2.0.0" + methods "~1.1.2" + 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.13.0" + range-parser "~1.2.1" + router "^2.0.0" + safe-buffer "5.2.1" + send "^1.1.0" + serve-static "^2.1.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "^2.0.0" + utils-merge "1.0.1" + vary "~1.1.2" + external-editor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" @@ -4411,6 +4578,18 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +finalhandler@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + 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" + find-replace@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" @@ -4532,11 +4711,16 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fresh@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" @@ -4622,6 +4806,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + 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" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -4689,6 +4897,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" @@ -4746,6 +4959,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" @@ -4813,7 +5031,7 @@ htmlparser2@^9.1.0: domutils "^3.1.0" entities "^4.5.0" -http-errors@2.0.0: +http-errors@2.0.0, http-errors@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== @@ -4881,6 +5099,13 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" + integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -5101,6 +5326,11 @@ is-path-inside@^3.0.3: resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-property@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" @@ -5635,6 +5865,11 @@ make-dir@^3.1.0: dependencies: semver "^6.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + md5@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz" @@ -5649,11 +5884,21 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -5669,13 +5914,25 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" +mime-types@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" @@ -5883,6 +6140,11 @@ negotiator@0.6.3: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + node-abi@^3.3.0: version "3.62.0" resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz" @@ -6016,6 +6278,11 @@ object-inspect@^1.13.1: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" @@ -6075,14 +6342,14 @@ ollama@^0.5.0, ollama@^0.5.10: dependencies: whatwg-fetch "^3.6.20" -on-finished@2.4.1: +on-finished@2.4.1, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -6276,7 +6543,7 @@ parse5@^7.0.0, parse5@^7.1.2: dependencies: entities "^4.5.0" -parseurl@~1.3.3: +parseurl@^1.3.3, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -6306,6 +6573,11 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + pg-cloudflare@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz" @@ -6381,6 +6653,11 @@ pirates@^3.0.2: dependencies: node-modules-regexp "^1.0.0" +pkce-challenge@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-4.1.0.tgz#95027d7750c3c0f21676a345b48f481786f9acdb" + integrity sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ== + platform@^1.3.6: version "1.3.6" resolved "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz" @@ -6574,6 +6851,20 @@ qs@6.11.2: dependencies: side-channel "^1.0.4" +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -6584,7 +6875,7 @@ queue-tick@^1.0.1: resolved "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz" integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== -range-parser@~1.2.1: +range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -6599,6 +6890,16 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" @@ -6744,6 +7045,15 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +router@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.1.0.tgz#f256ca2365afb4d386ba4f7a9ee0aa0827c962fa" + integrity sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA== + dependencies: + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -6841,6 +7151,24 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@^1.0.0, send@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.1.0.tgz#4efe6ff3bb2139b0e5b2648d8b18d4dec48fc9c5" + integrity sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA== + dependencies: + debug "^4.3.5" + destroy "^1.2.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^0.5.2" + http-errors "^2.0.0" + mime-types "^2.1.35" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + seq-queue@^0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz" @@ -6856,6 +7184,16 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +serve-static@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.1.0.tgz#1b4eacbe93006b79054faa4d6d0a501d7f0e84e2" + integrity sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.0.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -6914,6 +7252,35 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + 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" + side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" @@ -6924,6 +7291,17 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.0, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -6987,7 +7365,7 @@ stack-trace@0.0.x: resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== -statuses@2.0.1: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -7361,6 +7739,15 @@ type-is@^1.6.4, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type-is@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.0.tgz#7d249c2e2af716665cc149575dadb8b3858653af" + integrity sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz" @@ -7780,7 +8167,17 @@ zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.4, zod-to-json-schema@^3.22 resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.0.tgz" integrity sha512-az0uJ243PxsRIa2x1WmNE/pnuA05gUq/JB8Lwe1EDCCL/Fz9MgjYQ0fPlyc2Tcv6aF2ZA7WM5TWaRZVEFaAIag== +zod-to-json-schema@^3.24.1: + version "3.24.5" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" + integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== + zod@^3.22.3, zod@^3.22.4: version "3.23.5" resolved "https://registry.npmjs.org/zod/-/zod-3.23.5.tgz" integrity sha512-fkwiq0VIQTksNNA131rDOsVJcns0pfVUjHzLrNBiF/O/Xxb5lQyEXkhZWcJ7npWsYlvs+h0jFWXXy4X46Em1JA== + +zod@^3.23.8: + version "3.24.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" + integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==