From 1c71d9211e5b9026db673c1917cec8e80773f816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=91=E8=BD=BB=E7=8B=82?= <1677568218@qq.com> Date: Mon, 24 Mar 2025 17:59:37 +0800 Subject: [PATCH 1/3] feat: add MCP server with commands and tree view --- packages/vscode/package.json | 70 +- packages/vscode/src/commands.ts | 29 +- .../vscode/src/composables/useMcpServer.ts | 285 ++++++ packages/vscode/src/configs.ts | 10 +- packages/vscode/src/index.ts | 2 + packages/vscode/src/views/mcpTreeView.ts | 40 + pnpm-lock.yaml | 815 ++++++++++++++++++ 7 files changed, 1248 insertions(+), 3 deletions(-) create mode 100644 packages/vscode/src/composables/useMcpServer.ts create mode 100644 packages/vscode/src/views/mcpTreeView.ts diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 430d8fdbb7..6f460cc827 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -239,6 +239,36 @@ "title": "Unsync preview window with editor cursor", "icon": "$(lock)", "enablement": "slidev:preview:sync" + }, + { + "command": "slidev.mcp.start", + "category": "Slidev", + "title": "Start MCP server", + "icon": "$(play)" + }, + { + "command": "slidev.mcp.stop", + "category": "Slidev", + "title": "Stop MCP server", + "icon": "$(stop-circle)" + }, + { + "command": "slidev.open-mcp-settings", + "category": "Slidev", + "title": "Open MCP settings", + "icon": "$(settings)" + }, + { + "command": "slidev.mcp.copy-tool", + "title": "Copy MCP Tool Name", + "category": "Slidev", + "icon": "$(copy)" + }, + { + "command": "slidev.mcp.copy-url", + "title": "Copy MCP URL", + "category": "Slidev", + "icon": "$(link)" } ], "menus": { @@ -319,6 +349,21 @@ "command": "slidev.disable-preview-sync", "when": "view =~ /slidev-preview/ && slidev:preview:sync", "group": "navigation@7" + }, + { + "command": "slidev.mcp.stop", + "when": "view =~ /slidev-mcp-tree/ && slidev:mcp:status", + "group": "navigation@1" + }, + { + "command": "slidev.mcp.start", + "when": "view =~ /slidev-mcp-tree/ && !slidev:mcp:status", + "group": "navigation@1" + }, + { + "command": "slidev.mcp.copy-url", + "when": "view =~ /slidev-mcp-tree/ && slidev:mcp:status", + "group": "navigation@2" } ], "view/item/context": [ @@ -391,6 +436,17 @@ "scope": "window", "description": "The command to start Slidev dev server. See https://sli.dev/features/vscode-extension#dev-command", "default": "npm exec -c 'slidev ${args}'" + }, + "slidev.mcp.port": { + "type": "number", + "scope": "window", + "description": "The port of Model Context Protocol (MCP) server.", + "default": 4000 + }, + "slidev.mcp.ide": { + "type": "string", + "scope": "window", + "description": "The IDE name to be used in MCP server." } } }, @@ -426,6 +482,12 @@ "visibility": "visible", "initialSize": 3, "when": "slidev:enabled" + }, + { + "id": "slidev-mcp-tree", + "name": "MCP Server", + "visibility": "collapsed", + "when": "slidev:enabled" } ] }, @@ -433,6 +495,10 @@ { "view": "slidev-slides-tree", "contents": "No active slides entry.\n[Choose one](command:slidev.choose-entry)" + }, + { + "view": "slidev-mcp-tree", + "contents": "MCP server is not running.\n[Start MCP server](command:slidev.mcp.start)\n[Open MCP settings](command:slidev.open-mcp-settings)" } ] }, @@ -452,6 +518,7 @@ "@types/vscode": "^1.89.0", "@volar/language-server": "catalog:", "@volar/vscode": "catalog:", + "fastmcp": "^1.20.5", "get-port-please": "catalog:", "mlly": "catalog:", "ovsx": "catalog:", @@ -459,6 +526,7 @@ "reactive-vscode": "catalog:", "tm-grammars": "catalog:", "volar-service-prettier": "catalog:", - "volar-service-yaml": "catalog:" + "volar-service-yaml": "catalog:", + "zod": "^3.24.2" } } diff --git a/packages/vscode/src/commands.ts b/packages/vscode/src/commands.ts index 02757f8563..5daf386132 100644 --- a/packages/vscode/src/commands.ts +++ b/packages/vscode/src/commands.ts @@ -1,10 +1,11 @@ import { relative } from 'node:path' import { slash } from '@antfu/utils' import { useCommand } from 'reactive-vscode' -import { Position, Range, Selection, TextEditorRevealType, Uri, window, workspace } from 'vscode' +import { commands, env, Position, Range, Selection, TextEditorRevealType, Uri, window, workspace } from 'vscode' import { useDevServer } from './composables/useDevServer' import { useEditingSlideSource } from './composables/useEditingSlideSource' import { useFocusedSlideNo } from './composables/useFocusedSlideNo' +import { useMcpServer } from './composables/useMcpServer' import { configuredPort, forceEnabled, include, previewSync } from './configs' import { activeEntry, activeProject, activeSlidevData, addProject, projects, rescanProjects } from './projects' import { findPossibleEntries } from './utils/findPossibleEntries' @@ -155,4 +156,30 @@ export function useCommands() { useCommand('slidev.enable-preview-sync', () => (previewSync.value = true)) useCommand('slidev.disable-preview-sync', () => (previewSync.value = false)) + useCommand('slidev.mcp.start', () => { + const { start } = useMcpServer() + start() + }) + useCommand('slidev.mcp.stop', () => { + const { stop } = useMcpServer() + stop() + }) + useCommand('slidev.open-mcp-settings', () => { + return commands.executeCommand('workbench.action.openSettings', 'slidev.mcp') + }) + useCommand('slidev.mcp.copy-tool', (tool: string) => { + env.clipboard.writeText(tool).then(() => { + window.showInformationMessage(`Tool Name "${tool}" copied to clipboard.`) + }, (error) => { + window.showErrorMessage(`Copy Error: ${error}`) + }) + }) + useCommand('slidev.mcp.copy-url', () => { + const { url } = useMcpServer() + env.clipboard.writeText(url.value).then(() => { + window.showInformationMessage(`URL "${url.value}" copied to clipboard.`) + }, (error) => { + window.showErrorMessage(`Copy Error: ${error}`) + }) + }) } diff --git a/packages/vscode/src/composables/useMcpServer.ts b/packages/vscode/src/composables/useMcpServer.ts new file mode 100644 index 0000000000..dfce9dd49b --- /dev/null +++ b/packages/vscode/src/composables/useMcpServer.ts @@ -0,0 +1,285 @@ +import type { LoadedSlidevData } from '@slidev/parser/fs' +import type { FastMCP } from 'fastmcp' +import { Buffer } from 'node:buffer' +import { computed, createSingletonComposable, reactive, useActiveTextEditor, useTextEditorSelection, useVscodeContext } from 'reactive-vscode' +import { TextEditorSelectionChangeKind, Uri, window, workspace } from 'vscode' +import { z } from 'zod' +import { mcpIDE, mcpPort, mcpUrl } from '../configs' +import { activeSlidevData } from '../projects' +import { getFirstDisplayedChild } from '../utils/getFirstDisplayedChild' +import { logger } from '../views/logger' +import { getProjectFromDoc } from './useProjectFromDoc' + +function generateGlobalSlidesInfo(activeSlidevData: LoadedSlidevData) { + return { + totalSlides: activeSlidevData.slides.length, + headmatter: activeSlidevData.headmatter, + features: activeSlidevData.features, + allSlideTitles: activeSlidevData.slides.map(s => s.title), + allSlideNotes: activeSlidevData.slides.map(s => s.note), + } +} + +const tools = [ + { + name: 'get-current-slide-no', + description: 'Get current slide number', + parameters: z.object({}), + execute: async () => { + const editor = useActiveTextEditor() + const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) + const projectInfo = getProjectFromDoc(editor?.value?.document) + if (!activeSlidevData.value || !projectInfo || !editor.value) + return String('Error: No active slidev data or project info') + const line = selection.value.active.line + 1 + const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) + if (slide) { + const source = getFirstDisplayedChild(slide) + const no = activeSlidevData.value.slides.findIndex(s => s.source === source) + 1 + if (no) + return JSON.stringify({ slideNo: no, totalSlides: activeSlidevData.value.slides.length }) + } + return 'Error: No slide number found' + }, + }, + { + name: 'get-current-slide', + description: 'Get current slide info', + parameters: z.object({}), + execute: async () => { + const editor = useActiveTextEditor() + const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) + const projectInfo = getProjectFromDoc(editor?.value?.document) + if (!activeSlidevData.value || !projectInfo || !editor.value) + return String('Error: No active slidev data or project info') + const line = selection.value.active.line + 1 + const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) + if (slide) { + const source = getFirstDisplayedChild(slide) + const no = activeSlidevData.value.slides.findIndex(s => s.source === source) + 1 + return JSON.stringify({ + slideNo: no, + title: slide.title, + frontmatter: slide.frontmatter, + note: slide.note, + content: slide.content, + globalSlidesInfo: generateGlobalSlidesInfo(activeSlidevData.value), + }) + } + return 'Error: No slide found' + }, + }, + { + name: 'get-slide-by-no', + description: 'Get slide info by number', + parameters: z.object({ + slideNo: z.number(), + }), + execute: async (params: { slideNo: number }) => { + if (!activeSlidevData.value) + return String('Error: No active slidev data or project info') + const no = params.slideNo + if (no > 0 && no <= activeSlidevData.value.slides.length) { + const slide = activeSlidevData.value.slides[no - 1] + return JSON.stringify({ + slideNo: no, + title: slide.title, + frontmatter: slide.frontmatter, + note: slide.note, + content: slide.content, + globalSlidesInfo: generateGlobalSlidesInfo(activeSlidevData.value), + }) + } + return 'Error: No slide found' + }, + }, + { + name: 'get-all-slides', + description: 'Get all slides info', + parameters: z.object({}), + execute: async () => { + const editor = useActiveTextEditor() + const projectInfo = getProjectFromDoc(editor?.value?.document) + if (!activeSlidevData.value || !projectInfo || !editor.value) + return String('Error: No active slidev data or project info') + const slides = activeSlidevData.value.slides.map(slide => ({ + title: slide.title, + frontmatter: slide.frontmatter, + note: slide.note, + content: slide.content, + })) + return JSON.stringify({ + slides, + globalSlidesInfo: { + totalSlides: activeSlidevData.value.slides.length, + headmatter: activeSlidevData.value.headmatter, + features: activeSlidevData.value.features, + }, + }) + }, + }, +] + +/** + * Update the MCP config for IDEs. + * This is used to set the MCP server URL for IDEs. + * + * @param root Project Root + * @param type IDE type "cursor" | "vscode" + * @param url MCP Server URL + */ +export async function updateCursorMcpConfig(root: string, url: string, type: 'cursor' | 'vscode' = 'cursor'): Promise { + try { + const cursorDirUri = Uri.file(`${root}/.${type}`) + const mcpConfigUri = Uri.file(`${root}/.${type}/mcp.json`) + + try { + await workspace.fs.stat(cursorDirUri) + } + catch { + logger.info(`No .${type} directory found, skipping MCP config update`) + return + } + + let mcpConfig: any = {} + try { + const mcpData = await workspace.fs.readFile(mcpConfigUri) + mcpConfig = JSON.parse(Buffer.from(mcpData).toString('utf-8')) + } + catch { + logger.info('Creating new MCP config') + mcpConfig = {} + } + + mcpConfig.mcpServers = mcpConfig.mcpServers || {} + + mcpConfig.mcpServers.slidev = { url } + + const configContent = `${JSON.stringify(mcpConfig, null, 2)}\n` + await workspace.fs.writeFile( + mcpConfigUri, + Buffer.from(configContent, 'utf-8'), + ) + + logger.info(`Updated Cursor MCP config at ${mcpConfigUri.fsPath}`) + } + catch (error) { + logger.error('Failed to update Cursor MCP config:', error) + } +} + +// Fastmcp does not support ESM, so we need to use dynamic import +let FastMCPModule: any = null + +/** + * Create a default MCP server + * @returns {FastMCP} + */ +export async function createMcpServerDefault(): Promise { + if (!FastMCPModule) { + try { + FastMCPModule = await import('fastmcp') + } + catch (error) { + logger.error('Failed to load FastMCP module:', error) + window.showErrorMessage(`Failed to load MCP module: ${error}`) + throw error + } + } + + const { FastMCP } = FastMCPModule + const server = new FastMCP( + { + name: 'slidev-vscode', + version: '1.0.0', + }, + ) + for (const tool of tools) { + server.addTool(tool) + } + + return server +} + +export const useMcpServer = createSingletonComposable(() => { + const state = reactive({ + status: false, + tools, + }) + useVscodeContext('slidev:mcp:status', () => state.status) + + let serverInstance: FastMCP | null = null + + async function initServer() { + if (!serverInstance) { + serverInstance = await createMcpServerDefault() + + serverInstance.on('connect', (event: any) => { + logger.info('Client connected:', event.session) + }) + + serverInstance.on('disconnect', (event: any) => { + logger.info('Client disconnected:', event.session) + }) + } + + return serverInstance + } + + /** + * Start MCP service + */ + async function start() { + if (state.status) { + window.showInformationMessage('MCP Server is already running.') + return + } + try { + const server = await initServer() + server.start({ + transportType: 'sse', + sse: { + endpoint: '/sse', + port: mcpPort.value, + }, + }) + state.status = true + if (!!mcpIDE.value && workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { + const rootPath = workspace.workspaceFolders[0].uri.fsPath + await updateCursorMcpConfig( + rootPath, + mcpUrl.value, + mcpIDE.value, + ) + } + window.showInformationMessage(`Slidev MCP Server is started, url: ${mcpUrl.value}`) + } + catch (error) { + window.showErrorMessage(`MCP Server Error: ${error}`) + state.status = false + } + } + + /** + * Stop MCP server + */ + function stop() { + if (state.status && serverInstance) { + serverInstance.stop() + state.status = false + window.showInformationMessage('Slidev MCP Server is stopped.') + logger.info('Slidev MCP Server is stopped.') + } + else { + window.showInformationMessage('Slidev MCP Server is not running.') + } + } + + return { + state, + server: computed(() => serverInstance), + url: computed(() => `${mcpUrl.value}/sse`), + start, + stop, + } +}) diff --git a/packages/vscode/src/configs.ts b/packages/vscode/src/configs.ts index 4d6d2841da..18638d9fc4 100644 --- a/packages/vscode/src/configs.ts +++ b/packages/vscode/src/configs.ts @@ -1,5 +1,5 @@ import type { ConfigType } from 'reactive-vscode' -import { defineConfigs, ref } from 'reactive-vscode' +import { computed, defineConfigs, ref } from 'reactive-vscode' export const { 'force-enabled': forceEnabled, @@ -9,6 +9,8 @@ export const { include, exclude, 'dev-command': devCommand, + 'mcp.port': mcpPort, + 'mcp.ide': mcpIDE, } = defineConfigs('slidev', { 'force-enabled': Boolean, 'port': Number, @@ -17,7 +19,13 @@ export const { 'include': Object as ConfigType, 'exclude': String, 'dev-command': String, + 'mcp.port': Number, + 'mcp.ide': Object as ConfigType<'vscode' | 'cursor' | undefined>, }) export const configuredPort = ref(configuredPortInitial) export const previewSync = ref(previewSyncInitial) +export const mcpUrl = computed(() => { + const port = mcpPort.value + return `http://localhost:${port}` +}) diff --git a/packages/vscode/src/index.ts b/packages/vscode/src/index.ts index d212a94f06..3df6ad0f56 100644 --- a/packages/vscode/src/index.ts +++ b/packages/vscode/src/index.ts @@ -5,6 +5,7 @@ import { activeEntry, useProjects } from './projects' import { useAnnotations } from './views/annotations' import { useFoldings } from './views/foldings' import { logger } from './views/logger' +import { useMcpTreeView } from './views/mcpTreeView' import { usePreviewWebview } from './views/previewWebview' import { useProjectsTree } from './views/projectsTree' import { useSlidesTree } from './views/slidesTree' @@ -22,6 +23,7 @@ const { activate, deactivate } = defineExtension(() => { usePreviewWebview() useAnnotations() useFoldings() + useMcpTreeView() // language server const labsInfo = useLanguageClient() diff --git a/packages/vscode/src/views/mcpTreeView.ts b/packages/vscode/src/views/mcpTreeView.ts new file mode 100644 index 0000000000..7b0d87bff4 --- /dev/null +++ b/packages/vscode/src/views/mcpTreeView.ts @@ -0,0 +1,40 @@ +import { computed, createSingletonComposable, useTreeView } from 'reactive-vscode' +import { ThemeIcon } from 'vscode' +import { useMcpServer } from '../composables/useMcpServer' + +export const useMcpTreeView = createSingletonComposable(() => { + const { state } = useMcpServer() + const treeData = computed(() => { + if (!state.status) { + return [] + } + + return [ + ...state.tools.map(tool => ({ + treeItem: { + label: tool.name, + description: tool.description, + iconPath: new ThemeIcon('symbol-method'), + tooltip: tool.description, + command: { + command: 'slidev.mcp.copy-tool', + title: 'Copy MCP Tool Name', + arguments: [tool.name], + }, + }, + })), + ] + }) + + const treeView = useTreeView('slidev-mcp-tree', treeData, { + showCollapseAll: false, + title: computed(() => { + if (!state.status) + return 'MCP Server' + + return `MCP Server (running)` + }), + }) + + return treeView +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c80b06283e..46836ddf18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1154,6 +1154,9 @@ importers: '@volar/vscode': specifier: 'catalog:' version: 2.4.12 + fastmcp: + specifier: ^1.20.5 + version: 1.20.5 get-port-please: specifier: 'catalog:' version: 3.1.2 @@ -1178,6 +1181,9 @@ importers: volar-service-yaml: specifier: 'catalog:' version: 0.0.63(@volar/language-service@2.4.12) + zod: + specifier: ^3.24.2 + version: 3.24.2 packages: @@ -2018,6 +2024,10 @@ packages: '@mermaid-js/parser@0.3.0': resolution: {integrity: sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==} + '@modelcontextprotocol/sdk@1.7.0': + resolution: {integrity: sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==} + engines: {node: '>=18'} + '@mrdrogdrog/optional@1.2.1': resolution: {integrity: sha512-8JdrQautBZ+nxTC29Sp7z/plyONdgPDjCbFTf6Iih5spZKW18EmP2D4zd48wG9Nn0Qpe8f0p9f8/94SlZFl4tQ==} @@ -2169,6 +2179,9 @@ packages: cpu: [x64] os: [win32] + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@shikijs/core@3.1.0': resolution: {integrity: sha512-1ppAOyg3F18N8Ge9DmJjGqRVswihN33rOgPovR6gUHW17Hw1L4RlRhnmVQcsacSHh0A8IO1FIgNbtTxUFwodmg==} @@ -2220,6 +2233,10 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@slidev/parser@0.47.5': resolution: {integrity: sha512-KvqOEhIFuMDu8CjAsehMNKlAnnmAn2TRHiEymc3CMadid05oWa5zgTxqEyMxUl2rjWpYs3A1yDiendZsGYs3HA==} engines: {node: '>=18.0.0'} @@ -2249,6 +2266,13 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/cli-progress@3.11.6': resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} @@ -2886,6 +2910,10 @@ packages: peerDependencies: vue: ^3.5.0 + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3041,6 +3069,10 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + body-parser@2.1.0: + resolution: {integrity: sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3087,6 +3119,10 @@ packages: peerDependencies: esbuild: '>=0.18' + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + c12@1.11.2: resolution: {integrity: sha512-oBs8a4uvSDO9dm8b7OCFW7+dgtVrwmwnrVXYzLm43ta7ep2jCn/0MhoUFygIWtxhyy6+/MG7/agvpY0U1Iemew==} peerDependencies: @@ -3119,10 +3155,18 @@ packages: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3331,6 +3375,14 @@ packages: resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-hrtime@5.0.0: resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} engines: {node: '>=12'} @@ -3338,6 +3390,14 @@ 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.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -3351,6 +3411,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -3572,6 +3636,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -3633,6 +3706,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3640,6 +3717,10 @@ packages: destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -3685,6 +3766,10 @@ packages: drauu@0.4.3: resolution: {integrity: sha512-3pk6ZdfgElrEW+L4C03Xtrr7VVdSmcWlBb8cUj+WUWree2hEN8IE9fxRBL9HYG5gr8hAEXFNB0X263Um1WlYwA==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -3719,6 +3804,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding-sniffer@0.2.0: resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} @@ -3752,6 +3841,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -3759,6 +3852,10 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} @@ -4002,6 +4099,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter2@6.4.7: resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} @@ -4012,6 +4113,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.0: + resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.5: + resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} + engines: {node: '>=18.0.0'} + execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} @@ -4020,6 +4129,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + execa@9.5.2: + resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} + engines: {node: ^18.19.0 || >=20.5.0} + executable@4.1.1: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} @@ -4032,6 +4145,16 @@ packages: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + + express@5.0.1: + resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} + engines: {node: '>= 18'} + exsolve@1.0.2: resolution: {integrity: sha512-ZEcIMbthn2zeX4/wD/DLxDUjuCltHXT8Htvm/JFlTkdYgWh2+HGppgwwNUnIVxzxP7yJOPtuBAec0dLx6lVY8w==} @@ -4070,6 +4193,10 @@ packages: fast-uri@3.0.1: resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + fastmcp@1.20.5: + resolution: {integrity: sha512-jwcPgMF9bcE9qsEG82YMlAG26/n5CSYsr95e60ntqWWd+3kgTBbUIasB3HfpqHLTNaQuoX6/jl18fpDcybBjcQ==} + hasBin: true + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -4084,10 +4211,17 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -4095,6 +4229,10 @@ packages: file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + file-type@20.4.1: + resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} + engines: {node: '>=18'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -4103,6 +4241,10 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -4154,9 +4296,21 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + framesync@6.1.2: resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + 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==} @@ -4207,9 +4361,17 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.1.2: resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -4222,6 +4384,10 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -4286,6 +4452,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + got@13.0.0: resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} engines: {node: '>=16'} @@ -4326,6 +4496,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} @@ -4376,6 +4550,10 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -4403,6 +4581,14 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + human-signals@8.0.0: + resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.5.2: + resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} + engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -4470,6 +4656,10 @@ packages: resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -4549,6 +4739,13 @@ packages: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regexp@3.1.0: resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} engines: {node: '>=12'} @@ -4561,6 +4758,10 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -4568,6 +4769,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} @@ -4901,6 +5106,14 @@ packages: engines: {node: '>= 18'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mcp-proxy@2.11.0: + resolution: {integrity: sha512-+krDmo5dbLR9yOZ+8XjQWNuTCV2mrXawuEbnLvDyL2DaS5a4WwFCBqNpWjuV4Nyv6E7pJxxAVbgq+jN4mZfcZw==} + hasBin: true + mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -4943,6 +5156,14 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + 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==} @@ -4953,6 +5174,10 @@ packages: mermaid@11.4.1: resolution: {integrity: sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.1: resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} @@ -5045,10 +5270,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.0: + resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5143,6 +5376,9 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5173,6 +5409,10 @@ packages: resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} engines: {node: '>=18'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-abi@3.67.0: resolution: {integrity: sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==} engines: {node: '>=10'} @@ -5211,6 +5451,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -5227,6 +5471,10 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} @@ -5240,6 +5488,10 @@ packages: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} + 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==} @@ -5329,6 +5581,10 @@ packages: resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} engines: {node: '>=18'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-semver@1.1.1: resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} @@ -5374,6 +5630,10 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + path-type@5.0.0: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} engines: {node: '>=12'} @@ -5391,6 +5651,10 @@ packages: pdf-lib@1.17.1: resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} + peek-readable@7.0.0: + resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==} + engines: {node: '>=18'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -5424,6 +5688,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkce-challenge@4.1.0: + resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.0: resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} @@ -5536,6 +5804,10 @@ packages: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} + pretty-ms@9.2.0: + resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} + engines: {node: '>=18'} + prism-theme-vars@0.2.5: resolution: {integrity: sha512-/D8gBTScYzi9afwE6v3TC1U/1YFZ6k+ly17mtVRdLpGy7E79YjJJWkXFgUDHJ2gDksV/ZnXF7ydJ4TvoDm2z/Q==} @@ -5553,6 +5825,10 @@ packages: property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} @@ -5577,10 +5853,18 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + qs@6.13.1: resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} engines: {node: '>=0.6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + quansync@0.2.8: resolution: {integrity: sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==} @@ -5594,6 +5878,14 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -5735,6 +6027,10 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.1.0: + resolution: {integrity: sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==} + engines: {node: '>= 18'} + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -5791,6 +6087,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + engines: {node: '>= 18'} + + serve-static@2.1.0: + resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -5798,6 +6102,9 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5829,10 +6136,26 @@ packages: shiki@3.1.0: resolution: {integrity: sha512-LdTNyWQlC5zdCaHdcp1zPA1OVA2ivb+KjGOOnGcy02tGaF5ja+dGibWFH7Ar8YlngUgK/scDqworK18Ys9cbYA==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -5935,6 +6258,10 @@ packages: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} @@ -5942,6 +6269,9 @@ packages: resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} engines: {node: '>=4', npm: '>=6'} + strict-event-emitter-types@2.0.0: + resolution: {integrity: sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -5987,6 +6317,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@4.0.0: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} @@ -6002,6 +6336,10 @@ packages: strip-literal@2.1.0: resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + strtok3@10.2.2: + resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==} + engines: {node: '>=18'} + style-value-types@5.1.2: resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==} @@ -6127,6 +6465,14 @@ 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'} + + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + engines: {node: '>=14.16'} + toml-eslint-parser@0.10.0: resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6239,6 +6585,10 @@ packages: resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==} engines: {node: '>=16'} + type-is@2.0.0: + resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} + engines: {node: '>= 0.6'} + typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} @@ -6256,6 +6606,10 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + unconfig@7.0.0: resolution: {integrity: sha512-G5CJSoG6ZTxgzCJblEfgpdRK2tos9+UdD2WtecDUVfImzQ0hFjwpH5RVvGMhP4pRpC9ML7NrC4qBsBl0Ttj35A==} @@ -6284,6 +6638,10 @@ packages: resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} engines: {node: '>=18.17'} + undici@7.5.0: + resolution: {integrity: sha512-NFQG741e8mJ0fLQk90xKxFdaSM7z4+IQpAgsFI36bCDY9Z2+aXXZjVy2uUksMouWfMI9+w5ejOq5zYYTBCQJDQ==} + engines: {node: '>=20.18.1'} + unhead@1.11.20: resolution: {integrity: sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==} @@ -6291,6 +6649,10 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unimport@3.11.1: resolution: {integrity: sha512-DuB1Uoq01LrrXTScxnwOoMSlTXxyKcULguFxbLrMDFcE/CO0ZWHpEiyhovN0mycPt7K6luAHe8laqvwvuoeUPg==} @@ -6406,6 +6768,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-templates@0.2.0: + resolution: {integrity: sha512-EWkjYEN0L6KOfEoOH6Wj4ghQqU7eBZMJqRHQnxQAq+dSEzRPClkWjf8557HkWQXF6BrAUoLSAyy9i3RVTliaNg==} + url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} @@ -6427,6 +6792,10 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} @@ -6777,9 +7146,21 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + zhead@2.2.4: resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -7666,6 +8047,20 @@ snapshots: dependencies: langium: 3.0.0 + '@modelcontextprotocol/sdk@1.7.0': + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + eventsource: 3.0.5 + express: 5.0.1 + express-rate-limit: 7.5.0(express@5.0.1) + pkce-challenge: 4.1.0 + raw-body: 3.0.0 + zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) + transitivePeerDependencies: + - supports-color + '@mrdrogdrog/optional@1.2.1': {} '@nodelib/fs.scandir@2.1.5': @@ -7814,6 +8209,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.35.0': optional: true + '@sec-ant/readable-stream@0.4.1': {} + '@shikijs/core@3.1.0': dependencies: '@shikijs/types': 3.1.0 @@ -7895,6 +8292,8 @@ snapshots: '@sindresorhus/merge-streams@2.3.0': optional: true + '@sindresorhus/merge-streams@4.0.0': {} + '@slidev/parser@0.47.5': dependencies: '@slidev/types': 0.47.5 @@ -7934,6 +8333,16 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.0(supports-color@8.1.1) + fflate: 0.8.2 + token-types: 6.0.0 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@types/cli-progress@3.11.6': dependencies: '@types/node': 22.13.10 @@ -8784,6 +9193,11 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.8.2) + accepts@2.0.0: + dependencies: + mime-types: 3.0.0 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -8937,6 +9351,20 @@ snapshots: bluebird@3.7.2: {} + body-parser@2.1.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0(supports-color@8.1.1) + 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 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@1.1.11: @@ -8995,6 +9423,8 @@ snapshots: esbuild: 0.25.0 load-tsconfig: 0.2.5 + bytes@3.1.2: {} + c12@1.11.2: dependencies: chokidar: 3.6.0 @@ -9042,6 +9472,11 @@ snapshots: cachedir@2.4.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -9050,6 +9485,11 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001700: {} @@ -9259,10 +9699,20 @@ snapshots: consola@3.4.0: {} + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + convert-hrtime@5.0.0: {} convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.1: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 @@ -9275,6 +9725,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -9560,6 +10015,10 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.3.6: + dependencies: + ms: 2.1.2 + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -9614,10 +10073,14 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} destr@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.0.3: optional: true @@ -9667,6 +10130,12 @@ snapshots: dependencies: '@drauu/core': 0.4.3 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -9694,6 +10163,8 @@ snapshots: encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + encoding-sniffer@0.2.0: dependencies: iconv-lite: 0.6.3 @@ -9725,10 +10196,16 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-module-lexer@1.6.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.23.1: optionalDependencies: '@esbuild/aix-ppc64': 0.23.1 @@ -10108,12 +10585,20 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eventemitter2@6.4.7: {} eventemitter3@5.0.1: {} events@3.3.0: {} + eventsource-parser@3.0.0: {} + + eventsource@3.0.5: + dependencies: + eventsource-parser: 3.0.0 + execa@4.1.0: dependencies: cross-spawn: 7.0.6 @@ -10138,6 +10623,21 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + execa@9.5.2: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.2.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.1 + executable@4.1.1: dependencies: pify: 2.3.0 @@ -10147,6 +10647,47 @@ snapshots: expect-type@1.1.0: {} + express-rate-limit@7.5.0(express@5.0.1): + dependencies: + express: 5.0.1 + + express@5.0.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.1.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.3.6 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + 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.1.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 + transitivePeerDependencies: + - supports-color + exsolve@1.0.2: {} extend-shallow@2.0.1: @@ -10185,6 +10726,22 @@ snapshots: fast-uri@3.0.1: {} + fastmcp@1.20.5: + dependencies: + '@modelcontextprotocol/sdk': 1.7.0 + execa: 9.5.2 + file-type: 20.4.1 + fuse.js: 7.1.0 + mcp-proxy: 2.11.0 + strict-event-emitter-types: 2.0.0 + undici: 7.5.0 + uri-templates: 0.2.0 + yargs: 17.7.2 + zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) + transitivePeerDependencies: + - supports-color + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -10197,16 +10754,31 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 file-saver@2.0.5: {} + file-type@20.4.1: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.2.2 + token-types: 6.0.0 + uint8array-extras: 1.4.0 + transitivePeerDependencies: + - supports-color + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -10223,6 +10795,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.0: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + find-up-simple@1.0.1: {} find-up@5.0.0: @@ -10266,10 +10849,16 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + forwarded@0.2.0: {} + framesync@6.1.2: dependencies: tslib: 2.4.0 + fresh@0.5.2: {} + + fresh@2.0.0: {} + fs-constants@1.0.0: optional: true @@ -10315,8 +10904,26 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.3.0: + 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-port-please@3.1.2: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@5.2.0: dependencies: pump: 3.0.0 @@ -10325,6 +10932,11 @@ snapshots: get-stream@8.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -10407,6 +11019,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + got@13.0.0: dependencies: '@sindresorhus/is': 5.6.0 @@ -10450,6 +11064,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + hash-sum@2.0.0: optional: true @@ -10511,6 +11127,14 @@ snapshots: http-cache-semantics@4.1.1: {} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 @@ -10542,6 +11166,12 @@ snapshots: human-signals@5.0.0: {} + human-signals@8.0.0: {} + + iconv-lite@0.5.2: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -10586,6 +11216,8 @@ snapshots: ip-regex@5.0.0: {} + ipaddr.js@1.9.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -10647,16 +11279,24 @@ snapshots: is-path-inside@4.0.0: {} + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + is-regexp@3.1.0: {} is-stream@2.0.1: {} is-stream@3.0.0: {} + is-stream@4.0.1: {} + is-typedarray@1.0.0: {} is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} + is-what@4.1.16: {} is-wsl@2.2.0: @@ -11006,6 +11646,16 @@ snapshots: marked@13.0.3: {} + math-intrinsics@1.1.0: {} + + mcp-proxy@2.11.0: + dependencies: + '@modelcontextprotocol/sdk': 1.7.0 + eventsource: 3.0.5 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -11123,6 +11773,10 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -11152,6 +11806,8 @@ snapshots: transitivePeerDependencies: - supports-color + methods@1.1.2: {} + micromark-core-commonmark@2.0.1: dependencies: decode-named-character-reference: 1.0.2 @@ -11350,10 +12006,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.0: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mimic-fn@2.1.0: {} @@ -11424,6 +12086,8 @@ snapshots: ms@2.0.0: {} + ms@2.1.2: {} + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -11447,6 +12111,8 @@ snapshots: natural-orderby@5.0.0: {} + negotiator@1.0.0: {} + node-abi@3.67.0: dependencies: semver: 7.7.1 @@ -11490,6 +12156,11 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -11507,6 +12178,8 @@ snapshots: object-inspect@1.13.2: {} + object-inspect@1.13.4: {} + ofetch@1.4.1: dependencies: destr: 2.0.3 @@ -11521,6 +12194,10 @@ snapshots: dependencies: ee-first: 1.1.1 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -11624,6 +12301,8 @@ snapshots: index-to-position: 0.1.2 type-fest: 4.35.0 + parse-ms@4.0.0: {} + parse-semver@1.1.1: dependencies: semver: 5.7.2 @@ -11665,6 +12344,8 @@ snapshots: lru-cache: 11.0.0 minipass: 7.1.2 + path-to-regexp@8.2.0: {} + path-type@5.0.0: optional: true @@ -11681,6 +12362,8 @@ snapshots: pako: 1.0.11 tslib: 1.14.1 + peek-readable@7.0.0: {} + pend@1.2.0: {} perfect-debounce@1.0.0: {} @@ -11699,6 +12382,8 @@ snapshots: pirates@4.0.6: {} + pkce-challenge@4.1.0: {} + pkg-types@1.3.0: dependencies: confbox: 0.1.8 @@ -11816,6 +12501,10 @@ snapshots: pretty-bytes@5.6.0: {} + pretty-ms@9.2.0: + dependencies: + parse-ms: 4.0.0 + prism-theme-vars@0.2.5: {} process-nextick-args@2.0.1: {} @@ -11829,6 +12518,11 @@ snapshots: property-information@7.0.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.0.0: {} proxy-from-env@1.1.0: @@ -11851,10 +12545,18 @@ snapshots: punycode@2.3.1: {} + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + qs@6.13.1: dependencies: side-channel: 1.0.6 + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + quansync@0.2.8: {} queue-microtask@1.2.3: {} @@ -11865,6 +12567,15 @@ snapshots: quick-lru@5.1.1: {} + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -12038,6 +12749,12 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + router@2.1.0: + dependencies: + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + run-applescript@7.0.0: {} run-parallel@1.2.0: @@ -12082,6 +12799,32 @@ snapshots: semver@7.7.1: {} + send@1.1.0: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + 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 + transitivePeerDependencies: + - supports-color + + serve-static@2.1.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.1.0 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -12093,6 +12836,8 @@ snapshots: setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -12118,6 +12863,26 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -12125,6 +12890,14 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -12231,10 +13004,14 @@ snapshots: statuses@1.5.0: {} + statuses@2.0.1: {} + std-env@3.8.0: {} stoppable@1.1.0: {} + strict-event-emitter-types@2.0.0: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -12283,6 +13060,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-final-newline@4.0.0: {} + strip-indent@4.0.0: dependencies: min-indent: 1.0.1 @@ -12297,6 +13076,11 @@ snapshots: js-tokens: 9.0.0 optional: true + strtok3@10.2.2: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 7.0.0 + style-value-types@5.1.2: dependencies: hey-listen: 1.0.8 @@ -12439,6 +13223,13 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + + token-types@6.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + toml-eslint-parser@0.10.0: dependencies: eslint-visitor-keys: 3.4.3 @@ -12553,6 +13344,12 @@ snapshots: type-fest@4.35.0: {} + type-is@2.0.0: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.0 + typed-rest-client@1.8.11: dependencies: qs: 6.13.1 @@ -12567,6 +13364,8 @@ snapshots: ufo@1.5.4: {} + uint8array-extras@1.4.0: {} + unconfig@7.0.0: dependencies: '@antfu/utils': 8.1.0 @@ -12601,6 +13400,8 @@ snapshots: undici@6.19.8: {} + undici@7.5.0: {} + unhead@1.11.20: dependencies: '@unhead/dom': 1.11.20 @@ -12610,6 +13411,8 @@ snapshots: unicorn-magic@0.1.0: {} + unicorn-magic@0.3.0: {} + unimport@3.11.1(rollup@4.35.0): dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.35.0) @@ -12773,6 +13576,8 @@ snapshots: dependencies: punycode: 2.3.1 + uri-templates@0.2.0: {} + url-join@4.0.1: {} util-deprecate@1.0.2: {} @@ -12788,6 +13593,8 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + vary@1.1.2: {} + verror@1.10.0: dependencies: assert-plus: 1.0.0 @@ -13211,8 +14018,16 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.1: {} + zhead@2.2.4: {} + zod-to-json-schema@3.24.5(zod@3.24.2): + dependencies: + zod: 3.24.2 + + zod@3.24.2: {} + zwitch@2.0.4: {} zx@8.4.0: {} From e5ca8cba337053fd42f69877e5f7652785805bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=91=E8=BD=BB=E7=8B=82?= <1677568218@qq.com> Date: Mon, 24 Mar 2025 18:18:12 +0800 Subject: [PATCH 2/3] fix: refactor MCP server endpoint handling for improved clarity --- packages/vscode/src/composables/useMcpServer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vscode/src/composables/useMcpServer.ts b/packages/vscode/src/composables/useMcpServer.ts index dfce9dd49b..f3cea10642 100644 --- a/packages/vscode/src/composables/useMcpServer.ts +++ b/packages/vscode/src/composables/useMcpServer.ts @@ -202,6 +202,7 @@ export async function createMcpServerDefault(): Promise { } export const useMcpServer = createSingletonComposable(() => { + const endpoint = '/sse' const state = reactive({ status: false, tools, @@ -239,7 +240,7 @@ export const useMcpServer = createSingletonComposable(() => { server.start({ transportType: 'sse', sse: { - endpoint: '/sse', + endpoint, port: mcpPort.value, }, }) @@ -248,7 +249,7 @@ export const useMcpServer = createSingletonComposable(() => { const rootPath = workspace.workspaceFolders[0].uri.fsPath await updateCursorMcpConfig( rootPath, - mcpUrl.value, + `${mcpUrl.value}${endpoint}`, mcpIDE.value, ) } @@ -278,7 +279,7 @@ export const useMcpServer = createSingletonComposable(() => { return { state, server: computed(() => serverInstance), - url: computed(() => `${mcpUrl.value}/sse`), + url: computed(() => `${mcpUrl.value}${endpoint}`), start, stop, } From a877f3d84267bcebe49b83fbc3c091d6bc90062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=91=E8=BD=BB=E7=8B=82?= <1677568218@qq.com> Date: Sun, 30 Mar 2025 18:21:31 +0800 Subject: [PATCH 3/3] feat: implement MCP server creation and IDE config update utilities --- .../vscode/src/composables/useMcpServer.ts | 230 +---- packages/vscode/src/utils/createMcpServer.ts | 51 ++ packages/vscode/src/utils/createMcpTools.ts | 853 ++++++++++++++++++ .../vscode/src/utils/updateIDEMcpConfig.ts | 50 + 4 files changed, 965 insertions(+), 219 deletions(-) create mode 100644 packages/vscode/src/utils/createMcpServer.ts create mode 100644 packages/vscode/src/utils/createMcpTools.ts create mode 100644 packages/vscode/src/utils/updateIDEMcpConfig.ts diff --git a/packages/vscode/src/composables/useMcpServer.ts b/packages/vscode/src/composables/useMcpServer.ts index f3cea10642..4bf732ac94 100644 --- a/packages/vscode/src/composables/useMcpServer.ts +++ b/packages/vscode/src/composables/useMcpServer.ts @@ -1,205 +1,11 @@ -import type { LoadedSlidevData } from '@slidev/parser/fs' import type { FastMCP } from 'fastmcp' -import { Buffer } from 'node:buffer' -import { computed, createSingletonComposable, reactive, useActiveTextEditor, useTextEditorSelection, useVscodeContext } from 'reactive-vscode' -import { TextEditorSelectionChangeKind, Uri, window, workspace } from 'vscode' -import { z } from 'zod' +import { computed, createSingletonComposable, reactive, useVscodeContext } from 'reactive-vscode' +import { window, workspace } from 'vscode' import { mcpIDE, mcpPort, mcpUrl } from '../configs' -import { activeSlidevData } from '../projects' -import { getFirstDisplayedChild } from '../utils/getFirstDisplayedChild' +import { createMcpServerDefault } from '../utils/createMcpServer' +import { tools } from '../utils/createMcpTools' +import { updateIDEMcpConfig } from '../utils/updateIDEMcpConfig' import { logger } from '../views/logger' -import { getProjectFromDoc } from './useProjectFromDoc' - -function generateGlobalSlidesInfo(activeSlidevData: LoadedSlidevData) { - return { - totalSlides: activeSlidevData.slides.length, - headmatter: activeSlidevData.headmatter, - features: activeSlidevData.features, - allSlideTitles: activeSlidevData.slides.map(s => s.title), - allSlideNotes: activeSlidevData.slides.map(s => s.note), - } -} - -const tools = [ - { - name: 'get-current-slide-no', - description: 'Get current slide number', - parameters: z.object({}), - execute: async () => { - const editor = useActiveTextEditor() - const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) - const projectInfo = getProjectFromDoc(editor?.value?.document) - if (!activeSlidevData.value || !projectInfo || !editor.value) - return String('Error: No active slidev data or project info') - const line = selection.value.active.line + 1 - const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) - if (slide) { - const source = getFirstDisplayedChild(slide) - const no = activeSlidevData.value.slides.findIndex(s => s.source === source) + 1 - if (no) - return JSON.stringify({ slideNo: no, totalSlides: activeSlidevData.value.slides.length }) - } - return 'Error: No slide number found' - }, - }, - { - name: 'get-current-slide', - description: 'Get current slide info', - parameters: z.object({}), - execute: async () => { - const editor = useActiveTextEditor() - const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) - const projectInfo = getProjectFromDoc(editor?.value?.document) - if (!activeSlidevData.value || !projectInfo || !editor.value) - return String('Error: No active slidev data or project info') - const line = selection.value.active.line + 1 - const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) - if (slide) { - const source = getFirstDisplayedChild(slide) - const no = activeSlidevData.value.slides.findIndex(s => s.source === source) + 1 - return JSON.stringify({ - slideNo: no, - title: slide.title, - frontmatter: slide.frontmatter, - note: slide.note, - content: slide.content, - globalSlidesInfo: generateGlobalSlidesInfo(activeSlidevData.value), - }) - } - return 'Error: No slide found' - }, - }, - { - name: 'get-slide-by-no', - description: 'Get slide info by number', - parameters: z.object({ - slideNo: z.number(), - }), - execute: async (params: { slideNo: number }) => { - if (!activeSlidevData.value) - return String('Error: No active slidev data or project info') - const no = params.slideNo - if (no > 0 && no <= activeSlidevData.value.slides.length) { - const slide = activeSlidevData.value.slides[no - 1] - return JSON.stringify({ - slideNo: no, - title: slide.title, - frontmatter: slide.frontmatter, - note: slide.note, - content: slide.content, - globalSlidesInfo: generateGlobalSlidesInfo(activeSlidevData.value), - }) - } - return 'Error: No slide found' - }, - }, - { - name: 'get-all-slides', - description: 'Get all slides info', - parameters: z.object({}), - execute: async () => { - const editor = useActiveTextEditor() - const projectInfo = getProjectFromDoc(editor?.value?.document) - if (!activeSlidevData.value || !projectInfo || !editor.value) - return String('Error: No active slidev data or project info') - const slides = activeSlidevData.value.slides.map(slide => ({ - title: slide.title, - frontmatter: slide.frontmatter, - note: slide.note, - content: slide.content, - })) - return JSON.stringify({ - slides, - globalSlidesInfo: { - totalSlides: activeSlidevData.value.slides.length, - headmatter: activeSlidevData.value.headmatter, - features: activeSlidevData.value.features, - }, - }) - }, - }, -] - -/** - * Update the MCP config for IDEs. - * This is used to set the MCP server URL for IDEs. - * - * @param root Project Root - * @param type IDE type "cursor" | "vscode" - * @param url MCP Server URL - */ -export async function updateCursorMcpConfig(root: string, url: string, type: 'cursor' | 'vscode' = 'cursor'): Promise { - try { - const cursorDirUri = Uri.file(`${root}/.${type}`) - const mcpConfigUri = Uri.file(`${root}/.${type}/mcp.json`) - - try { - await workspace.fs.stat(cursorDirUri) - } - catch { - logger.info(`No .${type} directory found, skipping MCP config update`) - return - } - - let mcpConfig: any = {} - try { - const mcpData = await workspace.fs.readFile(mcpConfigUri) - mcpConfig = JSON.parse(Buffer.from(mcpData).toString('utf-8')) - } - catch { - logger.info('Creating new MCP config') - mcpConfig = {} - } - - mcpConfig.mcpServers = mcpConfig.mcpServers || {} - - mcpConfig.mcpServers.slidev = { url } - - const configContent = `${JSON.stringify(mcpConfig, null, 2)}\n` - await workspace.fs.writeFile( - mcpConfigUri, - Buffer.from(configContent, 'utf-8'), - ) - - logger.info(`Updated Cursor MCP config at ${mcpConfigUri.fsPath}`) - } - catch (error) { - logger.error('Failed to update Cursor MCP config:', error) - } -} - -// Fastmcp does not support ESM, so we need to use dynamic import -let FastMCPModule: any = null - -/** - * Create a default MCP server - * @returns {FastMCP} - */ -export async function createMcpServerDefault(): Promise { - if (!FastMCPModule) { - try { - FastMCPModule = await import('fastmcp') - } - catch (error) { - logger.error('Failed to load FastMCP module:', error) - window.showErrorMessage(`Failed to load MCP module: ${error}`) - throw error - } - } - - const { FastMCP } = FastMCPModule - const server = new FastMCP( - { - name: 'slidev-vscode', - version: '1.0.0', - }, - ) - for (const tool of tools) { - server.addTool(tool) - } - - return server -} export const useMcpServer = createSingletonComposable(() => { const endpoint = '/sse' @@ -211,22 +17,6 @@ export const useMcpServer = createSingletonComposable(() => { let serverInstance: FastMCP | null = null - async function initServer() { - if (!serverInstance) { - serverInstance = await createMcpServerDefault() - - serverInstance.on('connect', (event: any) => { - logger.info('Client connected:', event.session) - }) - - serverInstance.on('disconnect', (event: any) => { - logger.info('Client disconnected:', event.session) - }) - } - - return serverInstance - } - /** * Start MCP service */ @@ -236,8 +26,10 @@ export const useMcpServer = createSingletonComposable(() => { return } try { - const server = await initServer() - server.start({ + if (!serverInstance) { + serverInstance = await createMcpServerDefault({ tools }) + } + serverInstance.start({ transportType: 'sse', sse: { endpoint, @@ -247,13 +39,13 @@ export const useMcpServer = createSingletonComposable(() => { state.status = true if (!!mcpIDE.value && workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { const rootPath = workspace.workspaceFolders[0].uri.fsPath - await updateCursorMcpConfig( + await updateIDEMcpConfig( rootPath, `${mcpUrl.value}${endpoint}`, mcpIDE.value, ) } - window.showInformationMessage(`Slidev MCP Server is started, url: ${mcpUrl.value}`) + window.showInformationMessage(`Slidev MCP Server is started, url: ${mcpUrl.value}${endpoint}`) } catch (error) { window.showErrorMessage(`MCP Server Error: ${error}`) diff --git a/packages/vscode/src/utils/createMcpServer.ts b/packages/vscode/src/utils/createMcpServer.ts new file mode 100644 index 0000000000..695c7b37ec --- /dev/null +++ b/packages/vscode/src/utils/createMcpServer.ts @@ -0,0 +1,51 @@ +import type { FastMCP, Tool } from 'fastmcp' +import { window } from 'vscode' +import { logger } from '../views/logger' + +// Fastmcp does not support ESM, so we need to use dynamic import +let FastMCPModule: any = null + +/** + * Create a default MCP server + * @returns {FastMCP} + */ +export async function createMcpServerDefault({ tools }: { tools: Tool[] }): Promise { + if (!FastMCPModule) { + try { + FastMCPModule = await import('fastmcp') + } + catch (error) { + logger.error('Failed to load FastMCP module:', error) + window.showErrorMessage(`Failed to load MCP module: ${error}`) + throw error + } + } + try { + const { FastMCP } = FastMCPModule + const server = new FastMCP( + { + name: 'slidev', + version: '1.0.0', + }, + ) + + for (const tool of tools) { + server.addTool(tool) + } + + server.on('connect', (event: any) => { + logger.info('Client connected:', event.session) + }) + + server.on('disconnect', (event: any) => { + logger.info('Client disconnected:', event.session) + }) + + return server + } + catch (error) { + logger.error('Failed to create MCP server:', error) + window.showErrorMessage(`Failed to create MCP server: ${error}`) + throw error + } +} diff --git a/packages/vscode/src/utils/createMcpTools.ts b/packages/vscode/src/utils/createMcpTools.ts new file mode 100644 index 0000000000..a7deead0f8 --- /dev/null +++ b/packages/vscode/src/utils/createMcpTools.ts @@ -0,0 +1,853 @@ +import type { LoadedSlidevData } from '@slidev/parser/fs' +import type { SlidevMarkdown, SourceSlideInfo } from '@slidev/types' +import { dirname, extname, join } from 'node:path' +import { parseSlide, save as slidevSave } from '@slidev/parser/fs' +import { createKeyedComposable, useActiveTextEditor, useTextEditorSelection } from 'reactive-vscode' +import { FileType, TextEditorSelectionChangeKind, Uri, workspace } from 'vscode' +import { z } from 'zod' +import { getProjectFromDoc } from '../composables/useProjectFromDoc' +import { activeSlidevData } from '../projects' +import { getFirstDisplayedChild } from './getFirstDisplayedChild' + +interface Feature { + name: string + title: string + description: string + depends: string[] + relates: string[] + derives: string[] + tags: string[] + since?: string +} + +interface GithubFile { + name: string + download_url: string +} + +const getSlidevFeaturesMap = createKeyedComposable(async () => { + const listResponse = await fetch('https://api.github.com/repos/slidevjs/slidev/contents/docs/features') + + if (!listResponse.ok) + throw new Error(`Error: Failed to fetch features list (${listResponse.status})`) + + const files = await listResponse.json() as GithubFile[] + const mdFiles = files.filter(file => + file.name.endsWith('.md') + && file.name !== 'index.md' + && file.name !== 'features.md', + ) + + const featuresMap = new Map() + const derivesMap = new Map() + + await Promise.all(mdFiles.map(async (file) => { + try { + const contentResponse = await fetch(file.download_url) + if (!contentResponse.ok) + return + + const content = await contentResponse.text() + const name = file.name.replace('.md', '') + const md = parseSlide(content) + const frontmatter = md.frontmatter + + const depends = frontmatter.depends || [] + for (const depend of depends) { + const dependName = depend.match(/\/([\w-]+)($|#)/)?.[1] + if (dependName) { + if (!derivesMap.has(dependName)) + derivesMap.set(dependName, []) + derivesMap.get(dependName)!.push(`features/${name}`) + } + } + + featuresMap.set(name, { + name, + title: md.title || '', + description: frontmatter.description || '', + depends: frontmatter.depends || [], + relates: frontmatter.relates || [], + derives: frontmatter.derives || [], + tags: frontmatter.tags || [], + since: frontmatter.since || '', + }) + } + catch (err) { + throw new Error(`Error processing feature ${file.name}:${String(err)}`) + } + })) + + for (const [name, feature] of featuresMap.entries()) { + if (derivesMap.has(name)) { + const derives = [...feature.derives] + for (const derive of derivesMap.get(name)!) { + if (!derives.includes(derive)) { + derives.push(derive) + } + } + feature.derives = derives + } + } + + const result = Object.fromEntries(featuresMap) + return result +}, () => `slidev-features-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`) + +const getSlidevFeatureUsage = createKeyedComposable(async (name: string) => { + const download_url = `https://raw.githubusercontent.com/slidevjs/slidev/main/docs/features/${name}.md` + const response = await fetch(download_url) + if (!response.ok) + return `Error: Failed to fetch feature usage (${response.status})` + const content = await response.text() + const md = parseSlide(content) + const frontmatter = md.frontmatter + return JSON.stringify({ + name, + title: md.title || '', + description: frontmatter.description || '', + depends: frontmatter.depends || [], + relates: frontmatter.relates || [], + derives: frontmatter.derives || [], + tags: frontmatter.tags || [], + since: frontmatter.since || '', + content: md.content, + }) +}, (name: string) => `slidev-feature-${name}-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`) + +function generateGlobalSlidesInfo(activeSlidevData: LoadedSlidevData) { + return { + totalSlides: activeSlidevData.slides.length, + headmatter: activeSlidevData.headmatter, + features: activeSlidevData.features, + allSlideTitles: activeSlidevData.slides.map(s => s.title), + allSlideNotes: activeSlidevData.slides.map(s => s.note), + } +} + +const getNpmPackages = createKeyedComposable(async (size: number, type: 'theme' | 'addon') => { + try { + const response = await fetch(`https://registry.npmjs.org/-/v1/search?text=keywords:slidev-${type}&size=${size}`) + if (!response.ok) + throw new Error(`Error: Failed to get themes (${response.status})`) + + const data = await response.json() + return JSON.stringify(data.objects.map((item: { + package: { + name?: string + description?: string + } + }) => { + return { + packageName: item.package.name, + description: item.package.description, + } + })) + } + catch (e) { + throw new Error(`Error: ${String(e)}`) + } +}, (size: number, type: 'theme' | 'addon') => `slidev-npm-packages-${size}-${type}-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`) + +function getCurrentSlideNo() { + const editor = useActiveTextEditor() + const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) + const projectInfo = getProjectFromDoc(editor?.value?.document) + if (!activeSlidevData.value || !projectInfo || !editor.value) + throw new Error('No active slidev data or project info') + const line = selection.value.active.line + 1 + const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) + if (slide) { + const source = getFirstDisplayedChild(slide) + return activeSlidevData.value.slides.findIndex(s => s.source === source) + 1 + } + return -1 +} + +export const tools = [ + { + name: 'get-current-slide-no', + description: 'Get current slide number', + parameters: z.object({}), + execute: async () => { + try { + const no = getCurrentSlideNo() + if (activeSlidevData.value && no !== -1) { + return JSON.stringify({ slideNo: no, totalSlides: activeSlidevData.value.slides.length }) + } + return 'Error: No slide number found' + } + catch (e) { + return String(e) + } + }, + }, + { + name: 'get-slide', + description: 'Get slide info by args.slideNo. If args.slideNo is not provided, get the current selected slide.', + parameters: z.object({ + slideNo: z.number().optional(), + }), + execute: async (args: { slideNo?: number }) => { + if (!activeSlidevData.value) + return 'Error: No active slidev data or project info' + let no = args?.slideNo + if (!no) { + // if no is not provided, get the current selected slide + try { + no = getCurrentSlideNo() + } + catch (e) { + return String(e) + } + if (!(activeSlidevData.value && no !== -1)) { + return 'Error: No slide found' + } + } + // if no is provided, get the slide by number + + if (no > 0 && no <= activeSlidevData.value.slides.length) { + const slide = activeSlidevData.value.slides[no - 1] + return JSON.stringify({ + slideNo: no, + raw: slide.source.raw, + parsedSlideData: { + title: slide.title, + frontmatter: slide.frontmatter, + note: slide.note, + content: slide.content, + }, + globalSlidesInfo: generateGlobalSlidesInfo(activeSlidevData.value), + }) + } + else { + return 'Error: No slide found' + } + }, + }, + { + name: 'get-all-slides', + description: 'Get all slides info', + parameters: z.object({}), + execute: async () => { + if (!activeSlidevData.value) + return String('Error: No active slidev data or project info') + const slides = activeSlidevData.value.slides.map(slide => ({ + slideNo: slide.index + 1, + title: slide.title, + frontmatter: slide.frontmatter, + note: slide.note, + content: slide.content, + })) + return JSON.stringify({ + globalSlidesInfo: { + totalSlides: activeSlidevData.value.slides.length, + headmatter: activeSlidevData.value.headmatter, + features: activeSlidevData.value.features, + }, + parsedSlidesData: slides, + }) + }, + }, + { + name: 'update-slide', + description: 'Update the slide by args.slideNo. If args.slideNo is not provided, update the current slide. only Update the args that are provided. The headermatter is the frontmatter of the slide 1. Before Update, it\'s better to check the slide info by `get-slide`. Avoid empty lines in HTML blocks as they may be parsed as Markdown.', + parameters: z.object({ + slideNo: z.number().optional().describe('Slide number to update. If not provided, update the current slide.'), + raw: z.string().describe('Slidev syntax markdown, including frontmatter and content.'), + }), + execute: async (args: { + slideNo?: number + raw: string + }) => { + if (!activeSlidevData.value) + return 'Error: No active slidev data or project info' + let no = args?.slideNo + if (!no) { + // if no is not provided, get the current selected slide + try { + no = getCurrentSlideNo() + } + catch (e) { + return String(e) + } + if (!(activeSlidevData.value && no !== -1)) { + return 'Error: No slide found' + } + } + // if no is provided, get the slide by number + if (no > 0 && no <= activeSlidevData.value.slides.length) { + const entrySlideNo = activeSlidevData.value.slides[no - 1].source.index + 1 + const entrySlideFilePath = activeSlidevData.value.slides[no - 1].source.filepath + const slidevMarkdown = { + filepath: entrySlideFilePath, + slides: activeSlidevData.value.markdownFiles[entrySlideFilePath].slides, + } + // only Update the args that are provided + + slidevMarkdown.slides[entrySlideNo - 1].raw = args.raw + await slidevSave(slidevMarkdown as SlidevMarkdown) + return JSON.stringify({ + slideNo: no, + parsedSlideData: parseSlide(args.raw), + }) + } + else { + return 'Error: No slide found' + } + }, + }, + { + name: 'insert-slide', + description: 'Insert slide by args.slideNo. If args.slideNo is not provided, insert into the current slide. If args.slideNo is -1, add at the end. Avoid empty lines in HTML blocks as they may be parsed as Markdown.', + parameters: z.object({ + slideNo: z.number().optional().describe('Slide number to insert. If not provided, insert into the current slide. If -1, add at the end.'), + raw: z.string().describe('Slidev syntax markdown, including frontmatter and content.'), + }), + + execute: async (args: { + slideNo?: number + raw: string + }) => { + if (!activeSlidevData.value) + return 'Error: No active slidev data or project info' + let no = args?.slideNo + if (!no) { + // if no is not provided, get the current selected slide + try { + no = getCurrentSlideNo() + } + catch (e) { + return String(e) + } + if (!(activeSlidevData.value && no !== -1)) { + return 'Error: No slide found' + } + } + const entrySlideFilePath = activeSlidevData.value.slides[no - 1].source.filepath + const slidevMarkdown = { + filepath: entrySlideFilePath, + slides: activeSlidevData.value.markdownFiles[entrySlideFilePath].slides, + } + // if no is provided, get the slide by number + if (no > 0 && no <= activeSlidevData.value.slides.length) { + const entrySlideNo = activeSlidevData.value.slides[no - 1].source.index + 1 + slidevMarkdown.slides.splice(entrySlideNo - 1, 0, { raw: args.raw } as SourceSlideInfo) + } + else if (no === -1) { + slidevMarkdown.slides = [...activeSlidevData.value.entry.slides, { raw: args.raw } as SourceSlideInfo] + } + else { + return 'Error: No slide found' + } + await slidevSave(slidevMarkdown as SlidevMarkdown) + return JSON.stringify({ + slideNo: no, + parsedSlideData: parseSlide(args.raw), + }) + }, + }, + { + name: 'remove-slide', + description: 'Remove slide by args.slideNo. If args.slideNo is not provided, remove the current slide.', + parameters: z.object({ + slideNo: z.number().optional().describe('Slide number to remove. If not provided, remove the current slide.'), + }), + execute: async (args: { slideNo?: number }) => { + if (!activeSlidevData.value) + return 'Error: No active slidev data or project info' + let no = args?.slideNo + if (!no) { + try { + no = getCurrentSlideNo() + } + catch (e) { + return String(e) + } + if (!(activeSlidevData.value && no !== -1)) { + return 'Error: No slide found' + } + } + if (no > 0 && no <= activeSlidevData.value.slides.length) { + const entrySlideNo = activeSlidevData.value.slides[no - 1].source.index + 1 + const entrySlideFilePath = activeSlidevData.value.slides[no - 1].source.filepath + const slidevMarkdown = { + filepath: entrySlideFilePath, + slides: activeSlidevData.value.markdownFiles[entrySlideFilePath].slides, + } + slidevMarkdown.slides.splice(entrySlideNo - 1, 1) + await slidevSave(slidevMarkdown as SlidevMarkdown) + return JSON.stringify({ + result: 'success', + }) + } + return 'Error: No slide found' + }, + }, + { + name: 'get-slidev-themes', + description: 'Get slidev theme list. Use `npm install xxx` to install the theme and add theme option on headmatter.', + parameters: z.object({ + size: z.number().optional().default(20), + }), + execute: async (args: { size?: number }) => { + try { + const size = args.size || 20 + const res = await getNpmPackages(size, 'theme') + return res + } + catch (error) { + return String(error) + } + }, + }, + { + name: 'get-slidev-addons', + description: 'Get slidev addons list. Use `npm install xxx` to install the addon and add addons option on headmatter.', + parameters: z.object({ + size: z.number().optional().default(20), + }), + execute: async (args: { size?: number }) => { + try { + const size = args.size || 20 + const res = await getNpmPackages(size, 'addon') + return res + } + catch (error) { + return String(error) + } + }, + }, + { + name: 'get-layouts', + description: 'Get all layouts available in Slidev, including both built-in and custom layouts', + parameters: z.object({}), + execute: async () => { + const builtinLayouts = [ + { + name: 'center', + description: 'Display content in the center of the screen', + usage: '---\nlayout: center\n---', + }, + { + name: 'cover', + description: 'Display the cover page of the presentation, including title, speaker, etc.', + usage: '---\nlayout: cover\n---', + }, + { + name: 'default', + description: 'The most basic layout for displaying any type of content', + usage: '---\nlayout: default\n---', + }, + { + name: 'end', + description: 'The last page of the presentation', + usage: '---\nlayout: end\n---', + }, + { + name: 'fact', + description: 'Highlight multiple facts or data on the screen', + usage: '---\nlayout: fact\n---', + }, + { + name: 'full', + description: 'Use the entire screen space to display content', + usage: '---\nlayout: full\n---', + }, + { + name: 'image-left', + description: 'Display image on the left side of the screen, content on the right', + usage: '---\nlayout: image-left\nimage: /path/to/the/image\nclass: my-cool-content-on-the-right\n---', + }, + { + name: 'image-right', + description: 'Display image on the right side of the screen, content on the left', + usage: '---\nlayout: image-right\nimage: /path/to/the/image\nclass: my-cool-content-on-the-left\n---', + }, + { + name: 'image', + description: 'Display an image as the main content of the page', + usage: '---\nlayout: image\nimage: /path/to/the/image\nbackgroundSize: contain\n---', + }, + { + name: 'iframe-left', + description: 'Display web page in an iframe on the left side, content on the right', + usage: '---\nlayout: iframe-left\nurl: https://github.com/slidevjs/slidev\nclass: my-cool-content-on-the-right\n---', + }, + { + name: 'iframe-right', + description: 'Display web page in an iframe on the right side, content on the left', + usage: '---\nlayout: iframe-right\nurl: https://github.com/slidevjs/slidev\nclass: my-cool-content-on-the-left\n---', + }, + { + name: 'iframe', + description: 'The content of the slide is an embedded web page', + usage: '---\nlayout: iframe\nurl: https://github.com/slidevjs/slidev\n---', + }, + { + name: 'intro', + description: 'Introduce the presentation, usually with title, brief description, author, etc.', + usage: '---\nlayout: intro\n---', + }, + { + name: 'none', + description: 'Layout without any styling', + usage: '---\nlayout: none\n---', + }, + { + name: 'quote', + description: 'Highlight quotations', + usage: '---\nlayout: quote\n---', + }, + { + name: 'section', + description: 'Mark the beginning of a new section in the presentation', + usage: '---\nlayout: section\n---', + }, + { + name: 'statement', + description: 'Present a statement/declaration as the main page content', + usage: '---\nlayout: statement\n---', + }, + { + name: 'two-cols', + description: 'Split the page content into two columns', + usage: '---\nlayout: two-cols\n---\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right', + }, + { + name: 'two-cols-header', + description: 'Split the page content into two columns with separate content at top and bottom', + usage: '---\nlayout: two-cols-header\n---\n\nThis spans both\n\n::left::\n\n# Left\n\nThis shows on the left\n\n::right::\n\n# Right\n\nThis shows on the right', + }, + ] + const result = { + custom: {}, + builtin: { + layouts: builtinLayouts, + count: builtinLayouts.length, + }, + } + if (activeSlidevData.value) { + try { + const slidevFilePath = activeSlidevData.value.entry.filepath + const projectRoot = dirname(slidevFilePath) + const layoutsDir = join(projectRoot, 'layouts') + + const layoutsUri = Uri.file(layoutsDir) + let layoutsExist = false + + try { + await workspace.fs.stat(layoutsUri) + layoutsExist = true + + const files = await workspace.fs.readDirectory(layoutsUri) + const supportedExtensions = ['.vue', '.js', '.ts', '.jsx', '.tsx'] + + const customLayouts = files + .filter(([name, type]) => { + if (type !== FileType.File) + return false + + const ext = extname(name).toLowerCase() + return supportedExtensions.includes(ext) + }) + .map(([name]) => { + return { + fileName: name, + } + }) + + result.custom = { + layouts: customLayouts, + exists: layoutsExist, + count: customLayouts.length, + } + } + catch { + result.custom = { + layouts: [], + exists: false, + count: 0, + message: 'Layouts directory does not exist', + } + } + } + catch (e) { + result.custom = { + error: `Error: ${String(e)}`, + } + } + } + + return JSON.stringify(result) + }, + }, + { + name: 'get-components', + description: 'Get all components available in Slidev, including both built-in and custom components.', + parameters: z.object({}), + execute: async () => { + const builtinComponents = [ + { + name: 'Arrow', + description: 'Draw an arrow', + usage: '', + props: [ + { name: 'x1', type: 'string | number', required: true, description: 'Starting x position' }, + { name: 'y1', type: 'string | number', required: true, description: 'Starting y position' }, + { name: 'x2', type: 'string | number', required: true, description: 'Ending x position' }, + { name: 'y2', type: 'string | number', required: true, description: 'Ending y position' }, + { name: 'width', type: 'string | number', default: '2', description: 'Line width' }, + { name: 'color', type: 'string', default: 'currentColor', description: 'Color' }, + { name: 'two-way', type: 'boolean', default: 'false', description: 'Whether to show arrow on both sides' }, + ], + }, + { + name: 'VDragArrow', + description: 'Similar to Arrow component but can be dragged', + usage: '', + props: [ + { name: 'x1', type: 'string | number', required: true, description: 'Starting x position' }, + { name: 'y1', type: 'string | number', required: true, description: 'Starting y position' }, + { name: 'x2', type: 'string | number', required: true, description: 'Ending x position' }, + { name: 'y2', type: 'string | number', required: true, description: 'Ending y position' }, + { name: 'width', type: 'string | number', default: '2', description: 'Line width' }, + { name: 'color', type: 'string', default: 'currentColor', description: 'Color' }, + { name: 'two-way', type: 'boolean', default: 'false', description: 'Whether to show arrow on both sides' }, + ], + }, + { + name: 'AutoFitText', + description: 'Font size will automatically adapt to the content box', + usage: '', + props: [ + { name: 'max', type: 'string | number', default: '100', description: 'Maximum font size' }, + { name: 'min', type: 'string | number', default: '30', description: 'Minimum font size' }, + { name: 'modelValue', type: 'string', default: '\'\'', description: 'Text content' }, + ], + }, + { + name: 'LightOrDark', + description: 'Display different content based on current theme (light or dark)', + usage: '\n \n \n', + }, + { + name: 'Link', + description: 'Insert a link that can navigate to a specified slide', + usage: 'Go to slide 42', + props: [ + { name: 'to', type: 'string | number', required: true, description: 'Path to navigate to (slides count from 1)' }, + { name: 'title', type: 'string', description: 'Title to display' }, + ], + }, + { + name: 'PoweredBySlidev', + description: 'Add a "Powered by Slidev" badge with a link to the slidev website', + usage: '', + }, + { + name: 'RenderWhen', + description: 'Slot is only rendered when the context satisfies the condition', + usage: 'This will only show in presenter view.', + props: [ + { name: 'context', type: 'Context | Context[]', required: true, description: 'Required context(s) for rendering' }, + ], + }, + { + name: 'SlideCurrentNo', + description: 'Current slide number', + usage: '', + }, + { + name: 'SlidesTotal', + description: 'Total number of slides', + usage: '', + }, + { + name: 'TitleRenderer', + description: 'Insert the main title in a slide that is parsed as HTML', + usage: '', + props: [ + { name: 'no', type: 'string | number', required: true, description: 'Slide number to display title for' }, + ], + }, + { + name: 'Toc', + description: 'Insert a table of contents', + usage: '', + props: [ + { name: 'columns', type: 'string | number', default: '1', description: 'Number of columns to display' }, + { name: 'listClass', type: 'string | string[]', default: '\'\'', description: 'Classes to apply to the TOC list' }, + { name: 'maxDepth', type: 'string | number', default: 'Infinity', description: 'Maximum depth level of headings to display' }, + { name: 'minDepth', type: 'string | number', default: '1', description: 'Minimum depth level of headings to display' }, + { name: 'mode', type: '\'all\' | \'onlyCurrentTree\' | \'onlySiblings\'', default: '\'all\'', description: 'Display mode for TOC items' }, + ], + }, + { + name: 'Transform', + description: 'Apply scale transformation to elements', + usage: '\n \n', + props: [ + { name: 'scale', type: 'number | string', default: '1', description: 'Scale ratio' }, + { name: 'origin', type: 'string', default: '\'top left\'', description: 'Origin position' }, + ], + }, + { + name: 'Tweet', + description: 'Embed a tweet', + usage: '', + props: [ + { name: 'id', type: 'number | string', required: true, description: 'Tweet ID' }, + { name: 'scale', type: 'number | string', default: '1', description: 'Scale ratio' }, + { name: 'conversation', type: 'string', default: '\'none\'', description: 'Tweet embed parameter' }, + { name: 'cards', type: '\'hidden\' | \'visible\'', default: '\'visible\'', description: 'Tweet embed parameter' }, + ], + }, + { + name: 'VClick', + description: 'Animation on click', + usage: 'Content revealed on click', + }, + { + name: 'VClicks', + description: 'Animation on multiple clicks', + usage: '\n
First
\n
Second
\n
', + }, + { + name: 'VSwitch', + description: 'Switch between slots based on click animations', + usage: '\n \n \n \n', + props: [ + { name: 'unmount', type: 'boolean', default: 'false', description: 'When true, unmount previous slot content' }, + { name: 'tag', type: 'string', default: '\'div\'', description: 'Tag for component' }, + { name: 'childTag', type: 'string', default: '\'div\'', description: 'Tag for child elements' }, + { name: 'transition', type: 'string', default: 'false', description: 'Transition effect' }, + ], + }, + { + name: 'VDrag', + description: 'Element that can be dragged with mouse to move, rotate and resize', + usage: 'Draggable content', + }, + { + name: 'SlidevVideo', + description: 'Embed a video', + usage: '\n \n', + props: [ + { name: 'controls', type: 'boolean', default: 'false', description: 'Show video controls' }, + { name: 'autoplay', type: 'boolean | \'once\'', default: 'false', description: 'Autoplay video' }, + { name: 'autoreset', type: '\'slide\' | \'click\'', default: 'undefined', description: 'When to reset video' }, + { name: 'poster', type: 'string', default: 'undefined', description: 'Image to show when video not playing' }, + { name: 'printPoster', type: 'string', default: 'undefined', description: 'Override poster for printing' }, + { name: 'timestamp', type: 'string | number', default: '0', description: 'Start time of video (seconds)' }, + { name: 'printTimestamp', type: 'string | number | \'last\'', default: 'undefined', description: 'Override timestamp for printing' }, + ], + }, + { + name: 'Youtube', + description: 'Embed a YouTube video', + usage: '', + props: [ + { name: 'id', type: 'string', required: true, description: 'YouTube video ID' }, + { name: 'width', type: 'number', description: 'Video width' }, + { name: 'height', type: 'number', description: 'Video height' }, + ], + }, + ] + const result = { + custom: {}, + builtin: { + components: builtinComponents, + count: builtinComponents.length, + }, + } + if (activeSlidevData.value) { + try { + const slidevFilePath = activeSlidevData.value.entry.filepath + const projectRoot = dirname(slidevFilePath) + const componentsDir = join(projectRoot, 'components') + + const componentsUri = Uri.file(componentsDir) + let componentsExist = false + + try { + await workspace.fs.stat(componentsUri) + componentsExist = true + + const files = await workspace.fs.readDirectory(componentsUri) + const supportedExtensions = ['.vue', '.js', '.ts', '.jsx', '.tsx', '.md'] + + const customComponents = files + .filter(([name, type]) => { + if (type !== FileType.File) + return false + + const ext = extname(name).toLowerCase() + return supportedExtensions.includes(ext) + }) + .map(([name]) => { + return { + fileName: name, + } + }) + result.custom = { + components: customComponents, + exists: componentsExist, + count: customComponents.length, + } + } + catch { + } + } + catch (e) { + result.custom = { + error: `Error: ${String(e)}`, + } + } + } + + return JSON.stringify(result) + }, + }, + { + name: 'get-slidev-features', + description: 'Get all Slidev features or search related Slidev features that about layouts, exports, builds, syntax, presentations, animations, code blocks, navigation, styling, drawing, CLI, themes, components, diagrams, editors, remote control, or client APIs.', + parameters: z.object({ + search: z.string().optional().describe('search for related features'), + }), + execute: async (args: { search?: string }) => { + try { + const result = await getSlidevFeaturesMap() + if (args.search) { + const s = args.search?.toLowerCase().trim() + return JSON.stringify(Object.values(result).filter((feature) => { + return (!s || feature.title.toLowerCase().includes(s) || feature.description.toLowerCase().includes(s) || feature.tags.some(tag => tag.toLowerCase().includes(s))) + })) + } + return JSON.stringify(result) + } + catch (e) { + return `Error: ${String(e)}` + } + }, + }, + { + name: 'get-slidev-feature-usage', + description: 'Get Slidev feature usage.', + parameters: z.object({ + name: z.string().describe('feature name'), + }), + execute: async (args: { name: string }) => { + try { + if (!args.name) { + return 'Error: No feature name provided' + } + const res = await getSlidevFeatureUsage(args.name) + return res + } + catch (e) { + return `Error: ${String(e)}` + } + }, + }, +] diff --git a/packages/vscode/src/utils/updateIDEMcpConfig.ts b/packages/vscode/src/utils/updateIDEMcpConfig.ts new file mode 100644 index 0000000000..574e4a4864 --- /dev/null +++ b/packages/vscode/src/utils/updateIDEMcpConfig.ts @@ -0,0 +1,50 @@ +import { Buffer } from 'node:buffer' +import { Uri, workspace } from 'vscode' +import { logger } from '../views/logger' +/** + * Update the MCP config for IDEs. + * This is used to set the MCP server URL for IDEs. + * + * @param root Project Root + * @param type IDE type "cursor" | "vscode" + * @param url MCP Server URL + */ +export async function updateIDEMcpConfig(root: string, url: string, type: 'cursor' | 'vscode' = 'cursor'): Promise { + try { + const ideDirUri = Uri.file(`${root}/.${type}`) + const mcpConfigUri = Uri.file(`${root}/.${type}/mcp.json`) + + try { + await workspace.fs.stat(ideDirUri) + } + catch { + logger.info(`No .${type} directory found, skipping MCP config update`) + return + } + + let mcpConfig: any = {} + try { + const mcpData = await workspace.fs.readFile(mcpConfigUri) + mcpConfig = JSON.parse(Buffer.from(mcpData).toString('utf-8')) + } + catch { + logger.info('Creating new MCP config') + mcpConfig = {} + } + + mcpConfig.mcpServers = mcpConfig.mcpServers || {} + + mcpConfig.mcpServers.slidev = { url } + + const configContent = `${JSON.stringify(mcpConfig, null, 2)}\n` + await workspace.fs.writeFile( + mcpConfigUri, + Buffer.from(configContent, 'utf-8'), + ) + + logger.info(`Updated Cursor MCP config at ${mcpConfigUri.fsPath}`) + } + catch (error) { + logger.error('Failed to update Cursor MCP config:', error) + } +}