diff --git a/AGENTS.md.license b/AGENTS.md.license new file mode 100644 index 00000000..52b25ff5 --- /dev/null +++ b/AGENTS.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Dyne.org foundation + +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/pkg/mcp/.npmignore b/pkg/mcp/.npmignore new file mode 100644 index 00000000..0b8d064b --- /dev/null +++ b/pkg/mcp/.npmignore @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Dyne.org foundation +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +node_modules +*.tsbuildinfo +build +coverage +.nyc_output +test +.prettierrc +.eslintrc +.gitignore +.prettierignore +.editorconfig +CONTRIBUTING.md +CHANGELOG.md +README.md.license diff --git a/pkg/mcp/README.md b/pkg/mcp/README.md new file mode 100644 index 00000000..0c515b51 --- /dev/null +++ b/pkg/mcp/README.md @@ -0,0 +1,44 @@ +# @slangroom/mcp + +Model Context Protocol (MCP) server for Slangroom contract development assistance. + +## Overview + +This package provides an MCP server that helps developers and AI assistants write Slangroom contracts by providing: + +- Slangroom contract templates +- Zencode keyword autocomplete suggestions +- Documentation for Slangroom plugins +- Contract validation +- Test data generation + +## Features + +- Context-aware assistance for Slangroom contract development +- Integration with AI coding assistants via MCP +- Resource provider for Slangroom and Zencode documentation +- Tool definitions for common Slangroom operations + +## Installation + +```bash +pnpm add @slangroom/mcp +``` + +## Usage + +The MCP server is designed to be used with AI coding assistants that support the Model Context Protocol. + +## Development + +To build the package: + +```bash +pnpm build +``` + +To run tests: + +```bash +pnpm test +``` \ No newline at end of file diff --git a/pkg/mcp/README.md.license b/pkg/mcp/README.md.license new file mode 100644 index 00000000..52b25ff5 --- /dev/null +++ b/pkg/mcp/README.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Dyne.org foundation + +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/pkg/mcp/package.json b/pkg/mcp/package.json new file mode 100644 index 00000000..abaa4539 --- /dev/null +++ b/pkg/mcp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@slangroom/mcp", + "version": "1.0.0", + "dependencies": { + "@slangroom/core": "workspace:*", + "@slangroom/shared": "workspace:*", + "@modelcontextprotocol/sdk": "^1.0.0" + }, + "devDependencies": { + "@types/node": "^22.7.6", + "esbuild": "^0.24.0", + "typescript": "^5.6.3" + }, + "repository": "https://github.com/dyne/slangroom", + "license": "AGPL-3.0-only", + "scripts": { + "build": "tsc --outdir build/esm" + }, + "type": "module", + "main": "./build/esm/src/index.js", + "types": "./build/esm/src/index.d.ts", + "bin": { + "slangroom-mcp": "./build/esm/src/cli.js" + }, + "exports": { + ".": { + "import": { + "types": "./build/esm/src/index.d.ts", + "default": "./build/esm/src/index.js" + } + }, + "./*": { + "import": { + "types": "./build/esm/src/*.d.ts", + "default": "./build/esm/src/*.js" + } + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": "^18.20.0 || ^20.10.0 || ^22 || ^23 || ^24" + } +} diff --git a/pkg/mcp/package.json.license b/pkg/mcp/package.json.license new file mode 100644 index 00000000..52b25ff5 --- /dev/null +++ b/pkg/mcp/package.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Dyne.org foundation + +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/pkg/mcp/src/catalog.ts b/pkg/mcp/src/catalog.ts new file mode 100644 index 00000000..b12580a8 --- /dev/null +++ b/pkg/mcp/src/catalog.ts @@ -0,0 +1,741 @@ +// SPDX-FileCopyrightText: 2026 Dyne.org foundation +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + PluginMap, + type PluginMapKey, + type PluginResult, + lex, + parse, + visit, +} from '@slangroom/core'; +import type { JsonableObject, ZenParams } from '@slangroom/shared'; + +export type OpenConnect = 'open' | 'connect'; +export type StatementFormat = 'given_then' | 'prepare_compute'; +export type StatementPhase = 'Given' | 'Then' | 'Prepare' | 'Compute'; +export type SourceKind = 'example' | 'test'; + +export type StatementDefinition = { + id: string; + plugin: string; + openconnect?: OpenConnect; + params: string[]; + phrase: string; + givenThenTemplate: string; + prepareComputeTemplate: string; + exampleSourceIds: string[]; + testSourceIds: string[]; +}; + +export type ContractSource = { + id: string; + uri: string; + kind: SourceKind; + name: string; + title: string; + plugin?: string; + sourceFile: string; + contract: string; + data?: JsonableObject; + keys?: JsonableObject; + meta?: Record; + matchedStatementIds: string[]; + validationErrors: string[]; +}; + +export type MatchedStatement = { + lineNo: number; + plugin: string; + phrase: string; + openconnect?: OpenConnect; + params: string[]; + into?: string; + intoSecret?: string; +}; + +export type ContractValidation = { + ok: boolean; + matchedStatements: MatchedStatement[]; + errors: { lineNo: number; message: string }[]; + missingBindings: string[]; +}; + +export type KnowledgeBase = { + repoRoot: string; + syntaxReference: string; + statements: StatementDefinition[]; + statementById: Map; + sources: ContractSource[]; + sourceById: Map; + pluginMap: PluginMap; +}; + +const NOOP_EXECUTOR = (): PluginResult => ({ ok: true, value: null }); + +const SOURCE_MARKER = + /(^|\n)\s*(Rule\b|Scenario\b|Given\b|Then\b|Prepare\b|Compute\b|When\b|If\b|endif\b|foreach\b|endforeach\b)/i; + +const slugify = (value: string): string => + value + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + +const countNewlines = (value: string): number => value.split('\n').length - 1; +const splitSearchTokens = (value: string): string[] => + value + .toLowerCase() + .split(/[^a-z0-9_-]+/) + .filter((token) => token !== ''); + +const quoted = (value: string): string => `'${value}'`; + +const renderGivenThenTemplate = ( + definition: Pick, + phase: 'Given' | 'Then' = 'Given', + output = 'result', +) => { + const clauses: string[] = []; + if (definition.openconnect === 'connect') { + clauses.push(`connect to ${quoted('connect_ref')}`); + } + if (definition.openconnect === 'open') { + clauses.push(`open ${quoted('open_ref')}`); + } + for (const param of definition.params) { + clauses.push(`send ${param} ${quoted(`${param}_ref`)}`); + } + clauses.push(definition.phrase); + if (output) { + clauses.push(`output into ${quoted(output)}`); + } + return `${phase} I ${clauses.join(' and ')}`; +}; + +const renderPrepareComputeTemplate = ( + definition: Pick, + phase: 'Prepare' | 'Compute' = 'Prepare', + output = 'result', +) => { + const pieces: string[] = []; + if (definition.openconnect === 'connect') { + pieces.push(`connect to ${quoted('connect_ref')}`); + } + if (definition.openconnect === 'open') { + pieces.push(`open ${quoted('open_ref')}`); + } + pieces.push(definition.phrase); + const statement = pieces.join(' and '); + const params = + definition.params.length === 0 + ? '' + : ` with ${definition.params + .map((param) => `${param} ${quoted(`${param}_ref`)}`) + .join(', ')}`; + return `${phase} ${quoted(output)}: ${statement}${params}`; +}; + +const definitionLookupKey = (key: { + phrase: string; + openconnect?: OpenConnect; + params?: string[]; +}) => `${key.phrase}:${key.openconnect ?? ''}:${(key.params ?? []).join(',')}`; + +export const resolveRepoRoot = async (): Promise => { + let currentDir = path.dirname(fileURLToPath(import.meta.url)); + for (let i = 0; i < 8; i += 1) { + try { + await fs.access(path.join(currentDir, 'docs', 'statements', 'index.md')); + await fs.access(path.join(currentDir, 'examples')); + return currentDir; + } catch { + const parent = path.dirname(currentDir); + if (parent === currentDir) break; + currentDir = parent; + } + } + throw new Error('Unable to resolve the slangroom repository root'); +}; + +const createStatementId = (plugin: string, key: PluginMapKey) => + `${plugin}:${key.openconnect ?? 'none'}:${(key.params ?? []).join(',')}:${key.phrase}`; + +const buildPluginMap = (definitions: StatementDefinition[]) => { + const pluginMap = new PluginMap(); + for (const definition of definitions) { + const key: PluginMapKey = { phrase: definition.phrase }; + if (definition.params.length > 0) key.params = [...definition.params]; + if (definition.openconnect) key.openconnect = definition.openconnect; + pluginMap.set(key, NOOP_EXECUTOR); + } + return pluginMap; +}; + +const parseStatementReference = async (repoRoot: string): Promise => { + const file = await fs.readFile(path.join(repoRoot, 'docs', 'statements', 'index.md'), 'utf8'); + const lines = file.split('\n'); + const definitions: StatementDefinition[] = []; + let currentPlugin: string | undefined; + + for (const line of lines) { + const headerMatch = /^##\s+([a-z0-9-]+)\s+plugin$/i.exec(line.trim()); + if (headerMatch) { + currentPlugin = headerMatch[1]!.toLowerCase(); + continue; + } + if (!currentPlugin || !line.trim().startsWith('|')) { + continue; + } + const columns = line + .split('|') + .slice(1, -1) + .map((column) => column.trim()); + if (columns.length !== 3) continue; + if (columns[0] === 'open/connect' || /^-+$/.test(columns[0]!.replaceAll(' ', ''))) continue; + + const openconnect = columns[0] === '' ? undefined : (columns[0] as OpenConnect); + const params = columns[1] === '' ? [] : columns[1]!.split(',').map((param) => param.trim()); + const phrase = columns[2]!; + const key: PluginMapKey = { phrase }; + if (params.length > 0) key.params = params; + if (openconnect) key.openconnect = openconnect; + const definition: StatementDefinition = { + id: createStatementId(currentPlugin, key), + plugin: currentPlugin, + params, + phrase, + givenThenTemplate: renderGivenThenTemplate({ ...key, params }), + prepareComputeTemplate: renderPrepareComputeTemplate({ ...key, params }), + exampleSourceIds: [], + testSourceIds: [], + }; + if (openconnect) definition.openconnect = openconnect; + definitions.push(definition); + } + + return definitions; +}; + +const detectPluginFromTitle = (title: string): string | undefined => { + const match = /^([a-z0-9-]+)\s+example$/i.exec(title.trim()); + return match?.[1]?.toLowerCase(); +}; + +const classifyLine = ( + line: string, +): + | 'blank' + | 'comment' + | 'rule' + | 'scenario' + | 'given-have' + | 'given-name' + | 'when' + | 'if' + | 'endif' + | 'foreach' + | 'endforeach' + | 'print' + | 'custom' + | 'unknown' => { + const trimmed = line.trim(); + if (trimmed === '') return 'blank'; + if (trimmed.startsWith('#')) return 'comment'; + if (/^rule\b/i.test(trimmed)) return 'rule'; + if (/^scenario\b/i.test(trimmed)) return 'scenario'; + if (/^given\s+i\s+have\b/i.test(trimmed)) return 'given-have'; + if (/^given\s+i\s+am\b/i.test(trimmed) || /^given\s+i?\s*my name is\b/i.test(trimmed)) + return 'given-name'; + if (/^when\s+i\b/i.test(trimmed)) return 'when'; + if (/^if\s+i\s+verify\b/i.test(trimmed)) return 'if'; + if (/^endif\b/i.test(trimmed)) return 'endif'; + if (/^foreach\b/i.test(trimmed)) return 'foreach'; + if (/^endforeach\b/i.test(trimmed)) return 'endforeach'; + if (/^then\s+i?\s*print\b/i.test(trimmed)) return 'print'; + if (/^(given|then)\s+I\b/i.test(trimmed) || /^(prepare|compute)\b/i.test(trimmed)) return 'custom'; + return 'unknown'; +}; + +const errorMessage = (value: unknown): string => + value instanceof Error ? value.message : typeof value === 'string' ? value : JSON.stringify(value); + +export const validateContract = ( + contract: string, + pluginMap: PluginMap, + statements?: StatementDefinition[], + params?: Partial, +): ContractValidation => { + const lines = contract.split('\n'); + const errors: { lineNo: number; message: string }[] = []; + const matchedStatements: MatchedStatement[] = []; + const missingBindings = new Set(); + const statementLookup = new Map( + (statements ?? []).map((statement) => [ + definitionLookupKey(statement), + statement, + ]), + ); + + for (const [index, line] of lines.entries()) { + const lineNo = index + 1; + const kind = classifyLine(line); + if (kind === 'blank' || kind === 'comment') continue; + if (kind === 'unknown') { + errors.push({ + lineNo, + message: 'Line does not match known Slangroom or common Zencode statement shapes', + }); + continue; + } + if (kind !== 'custom') continue; + + const lexed = lex(line.trim(), lineNo); + if (!lexed.ok) { + errors.push({ lineNo: lexed.error.lineNo, message: errorMessage(lexed.error.message) }); + continue; + } + + const cst = parse(pluginMap, ...lexed.value); + for (const error of cst.errors) { + errors.push({ lineNo: error.lineNo, message: errorMessage(error.message) }); + } + + const match = cst.matches[0]; + if (!match) { + errors.push({ lineNo, message: 'No statement definition matched this line' }); + continue; + } + + for (const error of match.err) { + errors.push({ lineNo: error.lineNo, message: errorMessage(error.message) }); + } + if (cst.errors.length > 0 || match.err.length > 0) continue; + + const definition = statementLookup.get(definitionLookupKey(match.key)); + const matched: MatchedStatement = { + lineNo, + plugin: definition?.plugin ?? 'unknown', + phrase: match.key.phrase, + params: [...(match.key.params ?? [])], + }; + if (match.key.openconnect) matched.openconnect = match.key.openconnect; + if (match.into) matched.into = match.into; + if (match.intoSecret) matched.intoSecret = match.intoSecret; + matchedStatements.push(matched); + + if (!params) continue; + try { + visit(cst, { + data: params.data ?? {}, + keys: params.keys ?? {}, + conf: params.conf ?? '', + extra: params.extra ?? {}, + }); + } catch (error) { + missingBindings.add(errorMessage(error)); + } + } + + return { + ok: errors.length === 0 && missingBindings.size === 0, + matchedStatements, + errors, + missingBindings: [...missingBindings], + }; +}; + +const readJsonObject = async (filePath: string): Promise => { + try { + const value = JSON.parse(await fs.readFile(filePath, 'utf8')) as JsonableObject; + return value; + } catch { + return undefined; + } +}; + +const readExamples = async (repoRoot: string): Promise => { + const examplesDir = path.join(repoRoot, 'examples'); + const plugins = await fs.readdir(examplesDir); + const sources: ContractSource[] = []; + + for (const plugin of plugins) { + const pluginDir = path.join(examplesDir, plugin); + const entries = await fs.readdir(pluginDir); + const bases = [...new Set(entries.filter((entry) => entry.endsWith('.slang')).map((entry) => entry.slice(0, -6)))]; + for (const base of bases) { + const contract = await fs.readFile(path.join(pluginDir, `${base}.slang`), 'utf8'); + const meta = await readJsonObject(path.join(pluginDir, `${base}.meta.json`)); + const data = await readJsonObject(path.join(pluginDir, `${base}.data.json`)); + const keys = await readJsonObject(path.join(pluginDir, `${base}.keys.json`)); + const title = + typeof meta?.['title'] === 'string' && meta['title'] !== '' + ? meta['title'] + : base.replaceAll('_', ' '); + const source: ContractSource = { + id: `example:${plugin}/${base}`, + uri: `slangroom://examples/${plugin}/${encodeURIComponent(base)}`, + kind: 'example', + name: base, + title, + plugin, + sourceFile: path.relative(repoRoot, path.join(pluginDir, `${base}.slang`)), + contract, + matchedStatementIds: [], + validationErrors: [], + }; + if (data) source.data = data; + if (keys) source.keys = keys; + if (meta) source.meta = meta as Record; + sources.push(source); + } + } + + return sources; +}; + +const readGrammarCases = async (repoRoot: string): Promise => { + const filePath = path.join(repoRoot, 'grammar', 'test', 'cases.txt'); + const content = await fs.readFile(filePath, 'utf8'); + const lines = content.split('\n'); + const sources: ContractSource[] = []; + + let currentTitle: string | undefined; + let currentBuffer: string[] = []; + let currentLineStart = 1; + + const flush = () => { + if (!currentTitle) return; + const contract = currentBuffer.join('\n').trim(); + if (contract === '') return; + const id = `test:grammar/${slugify(currentTitle)}`; + const source: ContractSource = { + id, + uri: `slangroom://tests/${encodeURIComponent(`grammar-${slugify(currentTitle)}`)}`, + kind: 'test', + name: slugify(currentTitle), + title: currentTitle, + sourceFile: `${path.relative(repoRoot, filePath)}:${currentLineStart}`, + contract, + matchedStatementIds: [], + validationErrors: [], + }; + const plugin = detectPluginFromTitle(currentTitle); + if (plugin) source.plugin = plugin; + sources.push(source); + }; + + for (const [index, line] of lines.entries()) { + if (line.startsWith('#')) { + flush(); + currentTitle = line.replace(/^#\s*/, '').trim(); + currentBuffer = []; + currentLineStart = index + 2; + continue; + } + if (line.trim() === '==>') { + flush(); + currentTitle = undefined; + currentBuffer = []; + continue; + } + if (currentTitle) { + currentBuffer.push(line); + } + } + flush(); + + return sources; +}; + +const readInlineTestContracts = async (repoRoot: string): Promise => { + const root = path.join(repoRoot, 'pkg'); + const packages = await fs.readdir(root); + const sources: ContractSource[] = []; + + for (const pkgName of packages) { + const testDir = path.join(root, pkgName, 'test'); + try { + await fs.access(testDir); + } catch { + continue; + } + const queue = [testDir]; + while (queue.length > 0) { + const current = queue.shift()!; + const entries = await fs.readdir(current, { withFileTypes: true }); + for (const entry of entries) { + const absolutePath = path.join(current, entry.name); + if (entry.isDirectory()) { + queue.push(absolutePath); + continue; + } + if (!/\.(ts|js|txt)$/.test(entry.name)) continue; + const file = await fs.readFile(absolutePath, 'utf8'); + const literalRegex = /`([\s\S]*?)`/g; + let match: RegExpExecArray | null; + let ordinal = 0; + while ((match = literalRegex.exec(file)) !== null) { + const contract = match[1] ?? ''; + if (!SOURCE_MARKER.test(contract)) continue; + const title = `${pkgName}/${entry.name} snippet ${ordinal + 1}`; + const lineStart = countNewlines(file.slice(0, match.index)) + 1; + sources.push({ + id: `test:${pkgName}/${slugify(entry.name)}-${ordinal}`, + uri: `slangroom://tests/${encodeURIComponent(`${pkgName}-${slugify(entry.name)}-${ordinal}`)}`, + kind: 'test', + name: `${slugify(entry.name)}-${ordinal}`, + title, + plugin: pkgName, + sourceFile: `${path.relative(repoRoot, absolutePath)}:${lineStart}`, + contract: contract.trim(), + matchedStatementIds: [], + validationErrors: [], + }); + ordinal += 1; + } + } + } + } + + return sources; +}; + +const annotateSources = ( + sources: ContractSource[], + statements: StatementDefinition[], + pluginMap: PluginMap, +) => { + const lookup = new Map( + statements.map((statement) => [ + definitionLookupKey(statement), + statement.id, + ]), + ); + + for (const source of sources) { + const analysis = validateContract(source.contract, pluginMap, statements); + source.validationErrors = analysis.errors.map((error) => `line ${error.lineNo}: ${error.message}`); + source.matchedStatementIds = analysis.matchedStatements + .map((statement) => lookup.get(definitionLookupKey(statement))) + .filter((value): value is string => typeof value === 'string'); + } + + for (const statement of statements) { + statement.exampleSourceIds = sources + .filter((source) => source.kind === 'example' && source.matchedStatementIds.includes(statement.id)) + .map((source) => source.id); + statement.testSourceIds = sources + .filter((source) => source.kind === 'test' && source.matchedStatementIds.includes(statement.id)) + .map((source) => source.id); + } +}; + +let cache: KnowledgeBase | undefined; + +export const loadKnowledgeBase = async (): Promise => { + if (cache) return cache; + const repoRoot = await resolveRepoRoot(); + const syntaxReference = await fs.readFile( + path.join(repoRoot, 'docs', 'statements', 'index.md'), + 'utf8', + ); + const statements = await parseStatementReference(repoRoot); + const pluginMap = buildPluginMap(statements); + const sources = [ + ...(await readExamples(repoRoot)), + ...(await readGrammarCases(repoRoot)), + ...(await readInlineTestContracts(repoRoot)), + ]; + annotateSources(sources, statements, pluginMap); + + cache = { + repoRoot, + syntaxReference, + statements, + statementById: new Map(statements.map((statement) => [statement.id, statement])), + sources, + sourceById: new Map(sources.map((source) => [source.id, source])), + pluginMap, + }; + return cache; +}; + +export const searchStatements = ( + statements: StatementDefinition[], + query?: string, + plugin?: string, +): StatementDefinition[] => { + const normalizedPlugin = plugin?.toLowerCase(); + const tokens = splitSearchTokens(query ?? ''); + + return [...statements] + .filter((statement) => !normalizedPlugin || statement.plugin === normalizedPlugin) + .map((statement) => { + const wordSet = new Set( + splitSearchTokens(`${statement.plugin} ${statement.phrase} ${statement.params.join(' ')}`), + ); + const haystack = `${statement.plugin}\n${statement.phrase}\n${statement.params.join('\n')}`; + let score = 0; + if (tokens.length === 0) score = 1; + for (const token of tokens) { + if (wordSet.has(token)) score += 5; + else if (haystack.includes(token)) score += 1; + if (statement.openconnect === token) score += 2; + } + return { statement, score }; + }) + .filter(({ score }) => score > 0) + .sort((left, right) => right.score - left.score || left.statement.phrase.localeCompare(right.statement.phrase)) + .map(({ statement }) => statement); +}; + +export const searchSources = ( + sources: ContractSource[], + query?: string, + plugin?: string, + kind?: SourceKind | 'all', +): ContractSource[] => { + const normalizedPlugin = plugin?.toLowerCase(); + const normalizedKind = kind === 'all' ? undefined : kind; + const tokens = splitSearchTokens(query ?? ''); + + return [...sources] + .filter((source) => !normalizedPlugin || source.plugin === normalizedPlugin) + .filter((source) => !normalizedKind || source.kind === normalizedKind) + .map((source) => { + const haystack = `${source.title}\n${source.contract}\n${source.plugin ?? ''}`.toLowerCase(); + let score = tokens.length === 0 ? 1 : 0; + for (const token of tokens) { + if (source.title.toLowerCase().includes(token)) score += 4; + if ((source.plugin ?? '').includes(token)) score += 2; + if (haystack.includes(token)) score += 1; + } + return { source, score }; + }) + .filter(({ score }) => score > 0) + .sort((left, right) => right.score - left.score || left.source.title.localeCompare(right.source.title)) + .map(({ source }) => source); +}; + +export const formatSourceResource = (source: ContractSource): string => { + const chunks = [`# ${source.title}`, '', `Source: ${source.sourceFile}`, `Kind: ${source.kind}`]; + if (source.plugin) chunks.push(`Plugin: ${source.plugin}`); + chunks.push('', '## Contract', '```gherkin', source.contract.trim(), '```'); + if (source.data) { + chunks.push('', '## Data', '```json', JSON.stringify(source.data, null, 2), '```'); + } + if (source.keys) { + chunks.push('', '## Keys', '```json', JSON.stringify(source.keys, null, 2), '```'); + } + if (source.matchedStatementIds.length > 0) { + chunks.push('', '## Matched Statements'); + for (const statementId of source.matchedStatementIds) { + chunks.push(`- ${statementId}`); + } + } + if (source.validationErrors.length > 0) { + chunks.push('', '## Validation Notes'); + for (const error of source.validationErrors) { + chunks.push(`- ${error}`); + } + } + return chunks.join('\n'); +}; + +export const buildDraft = ( + statement: StatementDefinition, + options?: { + format?: StatementFormat; + phase?: StatementPhase; + output?: string; + secretOutput?: boolean; + connectRef?: string; + openRef?: string; + paramRefs?: Record; + includeScenario?: string; + includeRuleUnknownIgnore?: boolean; + includePrintData?: boolean; + }, +) => { + const format = options?.format ?? 'given_then'; + const phase = + options?.phase ?? + (format === 'given_then' ? 'Given' : 'Prepare'); + const output = options?.output ?? 'result'; + const connectRef = options?.connectRef ?? 'connect_ref'; + const openRef = options?.openRef ?? 'open_ref'; + const paramRefs = options?.paramRefs ?? {}; + + const parts: string[] = []; + if (options?.includeRuleUnknownIgnore ?? true) parts.push('Rule unknown ignore'); + if (options?.includeScenario) parts.push(`Scenario '${statement.plugin}': ${options.includeScenario}`); + + let line = ''; + if (format === 'given_then') { + const phaseKeyword = phase === 'Then' ? 'Then' : 'Given'; + const clauses: string[] = []; + if (statement.openconnect === 'connect') { + clauses.push(`connect to ${quoted(connectRef)}`); + } + if (statement.openconnect === 'open') { + clauses.push(`open ${quoted(openRef)}`); + } + for (const param of statement.params) { + clauses.push(`send ${param} ${quoted(paramRefs[param] ?? `${param}_ref`)}`); + } + clauses.push(statement.phrase); + if (output !== '') { + const outputClause = options?.secretOutput + ? `output secret into ${quoted(output)}` + : `output into ${quoted(output)}`; + clauses.push(outputClause); + } + line = `${phaseKeyword} I ${clauses.join(' and ')}`; + } else { + const phaseKeyword = phase === 'Compute' ? 'Compute' : 'Prepare'; + const segments: string[] = []; + if (statement.openconnect === 'connect') { + segments.push(`connect to ${quoted(connectRef)}`); + } + if (statement.openconnect === 'open') { + segments.push(`open ${quoted(openRef)}`); + } + segments.push(statement.phrase); + const withClause = + statement.params.length === 0 + ? '' + : ` with ${statement.params + .map((param) => `${param} ${quoted(paramRefs[param] ?? `${param}_ref`)}`) + .join(', ')}`; + const outputClause = + output === '' + ? `${phaseKeyword}:` + : options?.secretOutput + ? `${phaseKeyword} secret ${quoted(output)}:` + : `${phaseKeyword} ${quoted(output)}:`; + line = `${outputClause} ${segments.join(' and ')}${withClause}`; + } + + parts.push(line); + if ((options?.includePrintData ?? true) && phase !== 'Then' && phase !== 'Compute') { + parts.push('', 'Then print the data'); + } + + const data: JsonableObject = {}; + if (statement.openconnect === 'connect') data[connectRef] = ''; + if (statement.openconnect === 'open') data[openRef] = ''; + for (const param of statement.params) { + data[paramRefs[param] ?? `${param}_ref`] = ''; + } + + return { + script: parts.join('\n'), + data, + keys: {}, + }; +}; diff --git a/pkg/mcp/src/cli.ts b/pkg/mcp/src/cli.ts new file mode 100644 index 00000000..bac55142 --- /dev/null +++ b/pkg/mcp/src/cli.ts @@ -0,0 +1,13 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: 2026 Dyne.org foundation +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { startStdioServer } from './server.js'; + +try { + await startStdioServer(); +} catch (error) { + console.error(error); + process.exitCode = 1; +} diff --git a/pkg/mcp/src/index.ts b/pkg/mcp/src/index.ts new file mode 100644 index 00000000..f2ce3ea0 --- /dev/null +++ b/pkg/mcp/src/index.ts @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2026 Dyne.org foundation +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +export * from './catalog.js'; +export * from './server.js'; diff --git a/pkg/mcp/src/server.ts b/pkg/mcp/src/server.ts new file mode 100644 index 00000000..2dfd4756 --- /dev/null +++ b/pkg/mcp/src/server.ts @@ -0,0 +1,451 @@ +// SPDX-FileCopyrightText: 2026 Dyne.org foundation +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + type CallToolResult, + type ListResourcesResult, + type ListToolsResult, + type ReadResourceResult, +} from '@modelcontextprotocol/sdk/types.js'; +import type { JsonableObject } from '@slangroom/shared'; +import { + buildDraft, + formatSourceResource, + loadKnowledgeBase, + searchSources, + searchStatements, + validateContract, + type SourceKind, +} from './catalog.js'; + +const SERVER_INFO = { + name: 'slangroom-mcp', + version: '1.0.0', +}; + +const TOOL_DEFINITIONS: ListToolsResult['tools'] = [ + { + name: 'list_statements', + description: + 'List Slangroom statement definitions from the live syntax reference, with example and test coverage.', + inputSchema: { + type: 'object', + properties: { + plugin: { type: 'string', description: 'Optional plugin filter such as helpers, http, or wallet.' }, + query: { type: 'string', description: 'Optional free-text phrase filter.' }, + limit: { type: 'number', description: 'Maximum number of statements to return.' }, + }, + additionalProperties: false, + }, + }, + { + name: 'search_contract_sources', + description: + 'Search example contracts and test snippets that use valid Slangroom syntax.', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Free-text search over titles and contract bodies.' }, + plugin: { type: 'string', description: 'Optional plugin filter.' }, + source_kind: { + type: 'string', + enum: ['all', 'example', 'test'], + description: 'Restrict the search corpus.', + }, + limit: { type: 'number', description: 'Maximum number of sources to return.' }, + }, + additionalProperties: false, + }, + }, + { + name: 'draft_contract', + description: + 'Generate a minimal Slangroom contract draft and starter data bundle for a chosen statement.', + inputSchema: { + type: 'object', + properties: { + statement_id: { type: 'string', description: 'Statement id returned by list_statements.' }, + format: { + type: 'string', + enum: ['given_then', 'prepare_compute'], + description: 'Draft format to use.', + }, + phase: { + type: 'string', + enum: ['Given', 'Then', 'Prepare', 'Compute'], + description: 'Leading phase keyword.', + }, + output: { type: 'string', description: 'Output variable name. Empty string omits output storage.' }, + secret_output: { type: 'boolean', description: 'Store output in keys instead of data.' }, + connect_ref: { type: 'string', description: 'Identifier used in the connect clause.' }, + open_ref: { type: 'string', description: 'Identifier used in the open clause.' }, + param_refs: { + type: 'object', + description: 'Override placeholder identifiers for statement params.', + additionalProperties: { type: 'string' }, + }, + include_scenario: { type: 'string', description: 'Optional scenario title.' }, + include_rule_unknown_ignore: { type: 'boolean', description: 'Include the Rule unknown ignore header.' }, + include_print_data: { type: 'boolean', description: 'Append Then print the data when applicable.' }, + }, + required: ['statement_id'], + additionalProperties: false, + }, + }, + { + name: 'validate_contract', + description: + 'Validate Slangroom-focused contract syntax and optionally verify that referenced data and key bindings exist.', + inputSchema: { + type: 'object', + properties: { + contract: { type: 'string', description: 'Full contract text to validate.' }, + data: { type: 'object', description: 'Optional data heap for binding checks.', additionalProperties: true }, + keys: { type: 'object', description: 'Optional keys heap for binding checks.', additionalProperties: true }, + }, + required: ['contract'], + additionalProperties: false, + }, + }, +]; + +const asObject = (value: unknown): Record => { + if (value === undefined) return {}; + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error('Tool arguments must be a JSON object'); + } + return value as Record; +}; + +const readOptionalString = (args: Record, key: string): string | undefined => { + const value = args[key]; + if (value === undefined) return undefined; + if (typeof value !== 'string') throw new Error(`${key} must be a string`); + return value; +}; + +const readRequiredString = (args: Record, key: string): string => { + const value = readOptionalString(args, key); + if (value === undefined) throw new Error(`${key} is required`); + return value; +}; + +const readOptionalBoolean = (args: Record, key: string): boolean | undefined => { + const value = args[key]; + if (value === undefined) return undefined; + if (typeof value !== 'boolean') throw new Error(`${key} must be a boolean`); + return value; +}; + +const readOptionalNumber = (args: Record, key: string): number | undefined => { + const value = args[key]; + if (value === undefined) return undefined; + if (typeof value !== 'number' || Number.isNaN(value)) throw new Error(`${key} must be a number`); + return value; +}; + +const readOptionalStringMap = ( + args: Record, + key: string, +): Record | undefined => { + const value = args[key]; + if (value === undefined) return undefined; + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error(`${key} must be an object`); + } + const parsed: Record = {}; + for (const [entryKey, entryValue] of Object.entries(value)) { + if (typeof entryValue !== 'string') throw new Error(`${key}.${entryKey} must be a string`); + parsed[entryKey] = entryValue; + } + return parsed; +}; + +const readOptionalJsonObject = ( + args: Record, + key: string, +): JsonableObject | undefined => { + const value = args[key]; + if (value === undefined) return undefined; + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error(`${key} must be an object`); + } + return value as JsonableObject; +}; + +const limitResults = (values: T[], limit?: number): T[] => { + if (limit === undefined) return values; + return values.slice(0, Math.max(0, Math.trunc(limit))); +}; + +const textResult = (text: string, structuredContent?: Record): CallToolResult => ({ + content: [{ type: 'text', text }], + ...(structuredContent ? { structuredContent } : {}), +}); + +const formatStatementList = async (args: Record): Promise => { + const kb = await loadKnowledgeBase(); + const plugin = readOptionalString(args, 'plugin'); + const query = readOptionalString(args, 'query'); + const limit = readOptionalNumber(args, 'limit'); + const statements = limitResults(searchStatements(kb.statements, query, plugin), limit); + const payload = statements.map((statement) => ({ + id: statement.id, + plugin: statement.plugin, + openconnect: statement.openconnect ?? null, + params: statement.params, + phrase: statement.phrase, + example_source_ids: statement.exampleSourceIds, + test_source_ids: statement.testSourceIds, + given_then_template: statement.givenThenTemplate, + prepare_compute_template: statement.prepareComputeTemplate, + })); + + const lines = statements.flatMap((statement) => [ + `- ${statement.id}`, + ` ${statement.givenThenTemplate}`, + ` examples=${statement.exampleSourceIds.length} tests=${statement.testSourceIds.length}`, + ]); + + return textResult(lines.length === 0 ? 'No statement definitions matched.' : lines.join('\n'), { + statements: payload, + }); +}; + +const formatSourceSearch = async (args: Record): Promise => { + const kb = await loadKnowledgeBase(); + const query = readOptionalString(args, 'query'); + const plugin = readOptionalString(args, 'plugin'); + const limit = readOptionalNumber(args, 'limit'); + const sourceKind = (readOptionalString(args, 'source_kind') ?? 'all') as SourceKind | 'all'; + const sources = limitResults(searchSources(kb.sources, query, plugin, sourceKind), limit); + const payload = sources.map((source) => ({ + id: source.id, + uri: source.uri, + kind: source.kind, + plugin: source.plugin ?? null, + title: source.title, + source_file: source.sourceFile, + matched_statement_ids: source.matchedStatementIds, + validation_errors: source.validationErrors, + contract_preview: source.contract.split('\n').slice(0, 8).join('\n'), + })); + const lines = sources.flatMap((source) => [ + `- ${source.id} (${source.kind})`, + ` ${source.title}`, + ` ${source.uri}`, + ]); + return textResult(lines.length === 0 ? 'No example or test sources matched.' : lines.join('\n'), { + sources: payload, + }); +}; + +const formatDraft = async (args: Record): Promise => { + const kb = await loadKnowledgeBase(); + const statementId = readRequiredString(args, 'statement_id'); + const statement = kb.statementById.get(statementId); + if (!statement) throw new Error(`Unknown statement_id: ${statementId}`); + + const draftOptions: Parameters[1] = {}; + const format = readOptionalString(args, 'format') as 'given_then' | 'prepare_compute' | undefined; + const phase = readOptionalString(args, 'phase') as 'Given' | 'Then' | 'Prepare' | 'Compute' | undefined; + const output = readOptionalString(args, 'output'); + const secretOutput = readOptionalBoolean(args, 'secret_output'); + const connectRef = readOptionalString(args, 'connect_ref'); + const openRef = readOptionalString(args, 'open_ref'); + const paramRefs = readOptionalStringMap(args, 'param_refs'); + const includeScenario = readOptionalString(args, 'include_scenario'); + const includeRuleUnknownIgnore = readOptionalBoolean(args, 'include_rule_unknown_ignore'); + const includePrintData = readOptionalBoolean(args, 'include_print_data'); + if (format) draftOptions.format = format; + if (phase) draftOptions.phase = phase; + if (output !== undefined) draftOptions.output = output; + if (secretOutput !== undefined) draftOptions.secretOutput = secretOutput; + if (connectRef) draftOptions.connectRef = connectRef; + if (openRef) draftOptions.openRef = openRef; + if (paramRefs) draftOptions.paramRefs = paramRefs; + if (includeScenario) draftOptions.includeScenario = includeScenario; + if (includeRuleUnknownIgnore !== undefined) { + draftOptions.includeRuleUnknownIgnore = includeRuleUnknownIgnore; + } + if (includePrintData !== undefined) draftOptions.includePrintData = includePrintData; + const draft = buildDraft(statement, draftOptions); + + const referenceSources = [ + ...statement.exampleSourceIds.map((id) => kb.sourceById.get(id)).filter((source) => source !== undefined), + ...statement.testSourceIds.map((id) => kb.sourceById.get(id)).filter((source) => source !== undefined), + ].slice(0, 6); + + const text = [ + `Statement: ${statement.id}`, + '', + '```gherkin', + draft.script, + '```', + '', + 'Starter data:', + '```json', + JSON.stringify(draft.data, null, 2), + '```', + '', + 'Starter keys:', + '```json', + JSON.stringify(draft.keys, null, 2), + '```', + '', + 'Reference sources:', + ...referenceSources.map((source) => `- ${source.id}: ${source.title}`), + ].join('\n'); + + return textResult(text, { + statement_id: statement.id, + script: draft.script, + data: draft.data, + keys: draft.keys, + reference_source_ids: referenceSources.map((source) => source.id), + }); +}; + +const formatValidation = async (args: Record): Promise => { + const kb = await loadKnowledgeBase(); + const contract = readRequiredString(args, 'contract'); + const data = readOptionalJsonObject(args, 'data'); + const keys = readOptionalJsonObject(args, 'keys'); + const params = data || keys ? ({ ...(data ? { data } : {}), ...(keys ? { keys } : {}) }) : undefined; + const validation = validateContract(contract, kb.pluginMap, kb.statements, params); + + const statementLookup = new Map( + kb.statements.map((statement) => [ + `${statement.phrase}:${statement.openconnect ?? ''}:${statement.params.join(',')}`, + statement.id, + ]), + ); + + const matchedStatements = validation.matchedStatements.map((statement) => ({ + line_no: statement.lineNo, + statement_id: + statementLookup.get( + `${statement.phrase}:${statement.openconnect ?? ''}:${statement.params.join(',')}`, + ) ?? null, + plugin: statement.plugin, + phrase: statement.phrase, + openconnect: statement.openconnect ?? null, + params: statement.params, + into: statement.into ?? null, + into_secret: statement.intoSecret ?? null, + })); + + const lines = [ + validation.ok ? 'Validation passed.' : 'Validation failed.', + ...matchedStatements.map((statement) => `- line ${statement.line_no}: ${statement.phrase}`), + ...validation.errors.map((error) => `- error line ${error.lineNo}: ${error.message}`), + ...validation.missingBindings.map((error) => `- binding: ${error}`), + ]; + + return textResult(lines.join('\n'), { + ok: validation.ok, + matched_statements: matchedStatements, + errors: validation.errors, + missing_bindings: validation.missingBindings, + }); +}; + +const buildResourceList = async (): Promise => { + const kb = await loadKnowledgeBase(); + return { + resources: [ + { + uri: 'slangroom://syntax/reference', + name: 'syntax-reference', + title: 'Slangroom Syntax Reference', + description: 'Committed syntax reference generated from the live statement catalog.', + mimeType: 'text/markdown', + }, + { + uri: 'slangroom://sources/index', + name: 'source-index', + title: 'Contract Source Index', + description: 'All example and test bundles known to the Slangroom MCP server.', + mimeType: 'text/markdown', + }, + ...kb.sources.map((source) => ({ + uri: source.uri, + name: source.id, + title: source.title, + description: `${source.kind} source from ${source.sourceFile}`, + mimeType: 'text/markdown', + })), + ], + }; +}; + +const readResource = async (uri: string): Promise => { + const kb = await loadKnowledgeBase(); + if (uri === 'slangroom://syntax/reference') { + return { + contents: [{ uri, mimeType: 'text/markdown', text: kb.syntaxReference }], + }; + } + if (uri === 'slangroom://sources/index') { + const body = kb.sources + .map((source) => `- ${source.id} | ${source.kind} | ${source.title} | ${source.uri}`) + .join('\n'); + return { + contents: [{ uri, mimeType: 'text/markdown', text: `# Contract Sources\n\n${body}` }], + }; + } + const source = kb.sources.find((candidate) => candidate.uri === uri); + if (!source) throw new Error(`Unknown resource URI: ${uri}`); + return { + contents: [{ uri, mimeType: 'text/markdown', text: formatSourceResource(source) }], + }; +}; + +export const createServer = () => { + const server = new Server(SERVER_INFO, { + capabilities: { + resources: { listChanged: false }, + tools: { listChanged: false }, + }, + instructions: + 'Use list_statements to inspect valid Slangroom statements, search_contract_sources to retrieve examples/tests, draft_contract to scaffold new scripts, and validate_contract to check syntax and binding references.', + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_DEFINITIONS }) as ListToolsResult); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const args = asObject(request.params.arguments); + switch (request.params.name) { + case 'list_statements': + return formatStatementList(args); + case 'search_contract_sources': + return formatSourceSearch(args); + case 'draft_contract': + return formatDraft(args); + case 'validate_contract': + return formatValidation(args); + default: + return textResult(`Unknown tool: ${request.params.name}`, { is_error: true }); + } + }); + + server.setRequestHandler(ListResourcesRequestSchema, async () => buildResourceList()); + server.setRequestHandler(ReadResourceRequestSchema, async (request) => readResource(request.params.uri)); + + return server; +}; + +export const startStdioServer = async () => { + const server = createServer(); + const transport = new StdioServerTransport(); + setInterval(() => undefined, 1 << 30); + await server.connect(transport); + return server; +}; diff --git a/pkg/mcp/test/index.ts b/pkg/mcp/test/index.ts new file mode 100644 index 00000000..9a132e38 --- /dev/null +++ b/pkg/mcp/test/index.ts @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: 2026 Dyne.org foundation +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import test from 'ava'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { + CallToolResultSchema, + ListResourcesResultSchema, + ListToolsResultSchema, + ReadResourceResultSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { createServer, loadKnowledgeBase } from '@slangroom/mcp'; + +const createConnectedClient = async () => { + const server = createServer(); + const client = new Client({ + name: 'slangroom-mcp-test-client', + version: '1.0.0', + }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]); + return { client, server, clientTransport, serverTransport }; +}; + +test('knowledge base loads statement catalog and sources', async (t) => { + const kb = await loadKnowledgeBase(); + const timestamp = kb.statements.find((statement) => statement.phrase === 'fetch the local timestamp in seconds'); + t.truthy(timestamp); + t.true((timestamp?.exampleSourceIds.length ?? 0) > 0); + t.true(kb.sources.some((source) => source.kind === 'example')); + t.true(kb.sources.some((source) => source.kind === 'test')); +}); + +test('mcp exposes tools and resources', async (t) => { + const { client, clientTransport, serverTransport } = await createConnectedClient(); + const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema); + t.deepEqual( + tools.tools.map((tool) => tool.name).sort(), + ['draft_contract', 'list_statements', 'search_contract_sources', 'validate_contract'], + ); + + const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema); + t.true(resources.resources.some((resource) => resource.uri === 'slangroom://syntax/reference')); + t.true(resources.resources.some((resource) => resource.uri === 'slangroom://sources/index')); + + await clientTransport.close(); + await serverTransport.close(); +}); + +test('draft_contract produces a valid timestamp contract', async (t) => { + const { client, clientTransport, serverTransport } = await createConnectedClient(); + + const listStatements = await client.request( + { + method: 'tools/call', + params: { + name: 'list_statements', + arguments: { plugin: 'timestamp', query: 'seconds' }, + }, + }, + CallToolResultSchema, + ); + const listPayload = listStatements.structuredContent as { statements: Array<{ id: string }> }; + const statementId = listPayload.statements[0]?.id; + t.truthy(statementId); + + const draft = await client.request( + { + method: 'tools/call', + params: { + name: 'draft_contract', + arguments: { + statement_id: statementId, + output: 'timestamp', + }, + }, + }, + CallToolResultSchema, + ); + const draftPayload = draft.structuredContent as { + script: string; + data: Record; + keys: Record; + }; + t.true(draftPayload.script.includes("Given I fetch the local timestamp in seconds and output into 'timestamp'")); + + const validation = await client.request( + { + method: 'tools/call', + params: { + name: 'validate_contract', + arguments: { + contract: draftPayload.script, + data: draftPayload.data, + keys: draftPayload.keys, + }, + }, + }, + CallToolResultSchema, + ); + const validationPayload = validation.structuredContent as { ok: boolean; errors: unknown[] }; + t.true(validationPayload.ok); + + await clientTransport.close(); + await serverTransport.close(); +}); + +test('search_contract_sources returns examples and resources are readable', async (t) => { + const { client, clientTransport, serverTransport } = await createConnectedClient(); + + const search = await client.request( + { + method: 'tools/call', + params: { + name: 'search_contract_sources', + arguments: { + plugin: 'helpers', + query: 'manipulate and get', + source_kind: 'example', + limit: 1, + }, + }, + }, + CallToolResultSchema, + ); + const payload = search.structuredContent as { + sources: Array<{ uri: string; kind: string }>; + }; + t.is(payload.sources[0]?.kind, 'example'); + + const resource = await client.request( + { + method: 'resources/read', + params: { uri: payload.sources[0]!.uri }, + }, + ReadResourceResultSchema, + ); + const firstContent = resource.contents[0]; + t.true( + firstContent !== undefined && + 'text' in firstContent && + typeof firstContent.text === 'string' && + firstContent.text.includes('## Contract'), + ); + + await clientTransport.close(); + await serverTransport.close(); +}); + +test('validate_contract reports missing bindings for unresolved references', async (t) => { + const { client, clientTransport, serverTransport } = await createConnectedClient(); + const validation = await client.request( + { + method: 'tools/call', + params: { + name: 'validate_contract', + arguments: { + contract: + "Rule unknown ignore\nGiven I send object 'missing_object' and send path 'missing_path' and manipulate and get and output into 'result'\nThen print the data\n", + data: {}, + keys: {}, + }, + }, + }, + CallToolResultSchema, + ); + const payload = validation.structuredContent as { + ok: boolean; + missing_bindings: string[]; + }; + t.false(payload.ok); + t.true(payload.missing_bindings.length > 0); + + await clientTransport.close(); + await serverTransport.close(); +}); diff --git a/pkg/mcp/tsconfig.json b/pkg/mcp/tsconfig.json new file mode 100644 index 00000000..4a287d65 --- /dev/null +++ b/pkg/mcp/tsconfig.json @@ -0,0 +1,38 @@ +{ + "include": ["src/**/*", "test/**/*"], + "compilerOptions": { + "rootDir": ".", + "target": "ESNext", + "module": "esnext", + "moduleResolution": "bundler", + "incremental": true, + "newLine": "lf", + "declaration": true, + "forceConsistentCasingInFileNames": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "useUnknownInCatchVariables": false, + "skipLibCheck": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "sourceMap": true, + "types": ["node"], + "lib": ["es6", "dom", "es2021"] + } +} diff --git a/pkg/mcp/tsconfig.json.license b/pkg/mcp/tsconfig.json.license new file mode 100644 index 00000000..52b25ff5 --- /dev/null +++ b/pkg/mcp/tsconfig.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Dyne.org foundation + +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba661178..460756db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -471,6 +471,28 @@ importers: specifier: workspace:* version: link:../core + pkg/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.0.0 + version: 1.29.0(zod@4.1.12) + '@slangroom/core': + specifier: workspace:* + version: link:../core + '@slangroom/shared': + specifier: workspace:* + version: link:../shared + devDependencies: + '@types/node': + specifier: ^22.7.6 + version: 22.15.3 + esbuild: + specifier: ^0.24.0 + version: 0.24.0 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + pkg/oauth: dependencies: '@node-oauth/oauth2-server': @@ -1123,6 +1145,12 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.0': resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} engines: {node: '>=18.18.0'} @@ -1263,6 +1291,16 @@ packages: resolution: {integrity: sha512-r8T/W5zXIvGSg6QIWgTIUU9fwXAJwJrutb4m+fbpkSGPwAPWQHzYvzaZRRe4wcezhxSpAqodOd4KsYc+swbp0w==} engines: {node: '>=18', npm: '>=8.0.0'} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@noble/curves@1.1.0': resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} @@ -1986,6 +2024,10 @@ packages: engines: {node: '>=10'} deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2032,6 +2074,14 @@ packages: resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} engines: {node: '>=18'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -2196,6 +2246,10 @@ packages: blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -2237,6 +2291,10 @@ packages: resolution: {integrity: sha512-xrJ8Hki7eQ6xew55mM6TG9zHI852OoAHcPfduWWtR6yxk2upTuIZy13VioRBDyHReHDdbeDPifUboeNkK/sXXA==} engines: {node: '>=12.17'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + c8@10.1.2: resolution: {integrity: sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==} engines: {node: '>=18'} @@ -2255,9 +2313,17 @@ packages: resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} engines: {node: ^16.14.0 || >=18.0.0} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.5: resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + 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'} @@ -2451,6 +2517,14 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + conventional-changelog-angular@7.0.0: resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} engines: {node: '>=16'} @@ -2511,6 +2585,14 @@ packages: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -2518,6 +2600,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -2545,6 +2631,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} @@ -2603,6 +2693,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -2648,6 +2747,10 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + 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'} @@ -2692,6 +2795,10 @@ packages: dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} @@ -2701,6 +2808,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emittery@0.10.0: resolution: {integrity: sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==} engines: {node: '>=12'} @@ -2721,6 +2831,10 @@ packages: emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -2749,6 +2863,18 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + 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'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -2767,6 +2893,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -2837,6 +2966,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'} + ethereum-cryptography@2.1.2: resolution: {integrity: sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==} @@ -2851,6 +2984,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2870,6 +3011,16 @@ packages: exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2934,6 +3085,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up-simple@1.0.0: resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} engines: {node: '>=18'} @@ -2996,6 +3151,14 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -3068,6 +3231,14 @@ packages: get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -3137,6 +3308,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'} + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -3174,6 +3349,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'} + has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} @@ -3198,6 +3377,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hono@4.12.14: + resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} + engines: {node: '>=16.9.0'} + hook-std@3.0.0: resolution: {integrity: sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3222,6 +3405,10 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} @@ -3261,6 +3448,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3337,10 +3528,18 @@ packages: resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} engines: {node: '>=12'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + irregular-plurals@3.5.0: resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} engines: {node: '>=8'} @@ -3506,6 +3705,9 @@ packages: jose@5.1.3: resolution: {integrity: sha512-GPExOkcMsCLBTi1YetY2LmkoY559fss0+0KVa6kOfb2YFe84nAM7Nm/XzuZozah4iHgmBGrCOHL5/cy670SBRw==} + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} @@ -3543,6 +3745,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -3748,6 +3953,10 @@ packages: resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + md5-hex@3.0.1: resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} engines: {node: '>=8'} @@ -3762,6 +3971,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memium@0.2.0: resolution: {integrity: sha512-BFNZHfk+zIFWmZ3zMr50S3KXVvw53E/kzlPy48aw9c493XyH8u13c3P6vufKj150P/8Qtre5sxbwfNWXkLUXYA==} @@ -3777,6 +3990,10 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3811,10 +4028,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.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@4.0.4: resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==} engines: {node: '>=16'} @@ -3963,6 +4188,10 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -4173,6 +4402,14 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + 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==} @@ -4333,6 +4570,10 @@ packages: parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -4367,6 +4608,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4415,6 +4659,10 @@ packages: resolution: {integrity: sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==} engines: {node: '>=14.16'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-conf@2.1.0: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} @@ -4519,6 +4767,10 @@ packages: protocols@2.0.1: resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -4538,12 +4790,24 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4664,6 +4928,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4709,6 +4977,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + sequelize-pool@7.1.0: resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} engines: {node: '>= 10.0.0'} @@ -4753,6 +5025,10 @@ packages: serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -4763,6 +5039,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==} + sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true @@ -4782,6 +5061,22 @@ packages: shiki@1.22.0: resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + 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.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4901,6 +5196,10 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stream-combiner2@1.1.1: resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} @@ -5067,6 +5366,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + toposort-class@1.0.1: resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} @@ -5153,6 +5456,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typedoc@0.26.10: resolution: {integrity: sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==} engines: {node: '>= 18'} @@ -5249,6 +5556,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5307,6 +5618,10 @@ packages: resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} engines: {node: '>= 0.10'} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -5625,6 +5940,11 @@ packages: resolution: {integrity: sha512-lQJUApp2IIIFQTbTp5TcE3g80oRpUHyed7hq/26hxST48Y/DugK8tovdRJlOYTNE0Q9dmMFHuCiNCWuvl8d+tg==} engines: {node: '>=22'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -6063,6 +6383,10 @@ snapshots: '@gar/promisify@1.1.3': optional: true + '@hono/node-server@1.19.14(hono@4.12.14)': + dependencies: + hono: 4.12.14 + '@humanfs/core@0.19.0': {} '@humanfs/node@0.16.5': @@ -6352,6 +6676,28 @@ snapshots: '@meeco/sd-jwt@0.0.3': {} + '@modelcontextprotocol/sdk@1.29.0(zod@4.1.12)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.14) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.14 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.1.12 + zod-to-json-schema: 3.25.2(zod@4.1.12) + transitivePeerDependencies: + - supports-color + '@noble/curves@1.1.0': dependencies: '@noble/hashes': 1.3.1 @@ -6912,7 +7258,7 @@ snapshots: '@types/bn.js@5.1.6': dependencies: - '@types/node': 22.15.3 + '@types/node': 24.0.3 '@types/debug@4.1.12': dependencies: @@ -6973,7 +7319,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.15.3 + '@types/node': 24.0.3 '@types/seedrandom@3.0.1': {} @@ -7248,6 +7594,11 @@ snapshots: level-supports: 2.1.0 queue-microtask: 1.2.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.13.0): dependencies: acorn: 8.13.0 @@ -7297,6 +7648,10 @@ snapshots: clean-stack: 5.2.0 indent-string: 5.0.0 + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -7500,6 +7855,20 @@ snapshots: blueimp-md5@2.19.0: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + bottleneck@2.19.5: {} brace-expansion@1.1.11: @@ -7547,6 +7916,8 @@ snapshots: byte-size@9.0.0: {} + bytes@3.1.2: {} + c8@10.1.2: dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -7600,12 +7971,22 @@ snapshots: tar: 6.2.1 unique-filename: 3.0.0 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.5: dependencies: function-bind: 1.1.2 get-intrinsic: 1.2.2 set-function-length: 1.1.1 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} callsites@4.2.0: {} @@ -7796,6 +8177,10 @@ snapshots: console-control-strings@1.1.0: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + conventional-changelog-angular@7.0.0: dependencies: compare-func: 2.0.0 @@ -7866,12 +8251,21 @@ snapshots: convert-to-spaces@2.0.1: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@9.0.0(typescript@4.9.5): dependencies: env-paths: 2.2.1 @@ -7908,6 +8302,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + crypto-random-string@4.0.0: dependencies: type-fest: 1.4.0 @@ -7948,6 +8348,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decamelize@4.0.0: {} @@ -7978,6 +8382,8 @@ snapshots: delegates@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-indent@7.0.1: {} @@ -8008,6 +8414,12 @@ snapshots: dottie@2.0.6: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 @@ -8016,6 +8428,8 @@ snapshots: eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + emittery@0.10.0: {} emittery@1.0.3: {} @@ -8028,6 +8442,8 @@ snapshots: emojilib@2.4.0: {} + encodeurl@2.0.0: {} + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -8054,6 +8470,14 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -8111,6 +8535,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} @@ -8192,6 +8618,8 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + ethereum-cryptography@2.1.2: dependencies: '@noble/curves': 1.1.0 @@ -8205,6 +8633,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -8248,6 +8682,44 @@ snapshots: exponential-backoff@3.1.1: {} + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -8305,6 +8777,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up-simple@1.0.0: {} find-up@2.1.0: @@ -8365,6 +8848,10 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + from2@2.3.0: dependencies: inherits: 2.0.4 @@ -8449,6 +8936,24 @@ 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-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@6.0.1: {} get-stream@7.0.1: {} @@ -8541,6 +9046,8 @@ snapshots: dependencies: get-intrinsic: 1.2.2 + gopd@1.2.0: {} + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -8570,6 +9077,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.0: dependencies: has-symbols: 1.0.3 @@ -8602,6 +9111,8 @@ snapshots: highlight.js@10.7.3: {} + hono@4.12.14: {} + hook-std@3.0.0: {} hookable@5.5.3: {} @@ -8620,6 +9131,14 @@ snapshots: http-cache-semantics@4.1.1: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@4.0.1: dependencies: '@tootallnate/once': 1.1.2 @@ -8673,6 +9192,10 @@ snapshots: safer-buffer: 2.1.2 optional: true + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore-by-default@2.1.0: {} @@ -8733,11 +9256,15 @@ snapshots: from2: 2.3.0 p-is-promise: 3.0.0 + ip-address@10.1.0: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 sprintf-js: 1.1.3 + ipaddr.js@1.9.1: {} + irregular-plurals@3.5.0: {} is-arguments@1.1.1: @@ -8877,6 +9404,8 @@ snapshots: jose@5.1.3: {} + jose@6.2.2: {} + js-string-escape@1.0.1: {} js-tokens@4.0.0: {} @@ -8904,6 +9433,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-nice@1.1.4: {} @@ -9135,6 +9666,8 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 + math-intrinsics@1.1.0: {} + md5-hex@3.0.1: dependencies: blueimp-md5: 2.19.0 @@ -9155,6 +9688,8 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + memium@0.2.0: dependencies: kerium: 1.3.5 @@ -9168,6 +9703,8 @@ snapshots: meow@13.2.0: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -9201,10 +9738,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.2: + dependencies: + mime-db: 1.54.0 + mime@4.0.4: {} mimic-fn@2.1.0: {} @@ -9355,6 +9898,8 @@ snapshots: negotiator@0.6.3: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} nerf-dart@1.0.0: {} @@ -9527,6 +10072,12 @@ snapshots: object-assign@4.1.1: {} + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -9705,6 +10256,8 @@ snapshots: parse5@6.0.1: {} + parseurl@1.3.3: {} + path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -9726,6 +10279,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} path-type@5.0.0: {} @@ -9754,6 +10309,8 @@ snapshots: pify@6.1.0: {} + pkce-challenge@5.0.1: {} + pkg-conf@2.1.0: dependencies: find-up: 2.1.0 @@ -9842,6 +10399,11 @@ snapshots: protocols@2.0.1: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} pump@3.0.3: @@ -9859,12 +10421,25 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -10012,6 +10587,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -10020,8 +10605,7 @@ snapshots: safe-buffer@5.2.1: {} - safer-buffer@2.1.2: - optional: true + safer-buffer@2.1.2: {} search-insights@2.13.0: {} @@ -10076,6 +10660,22 @@ snapshots: semver@7.6.3: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + sequelize-pool@7.1.0: {} sequelize@6.37.3(pg-hstore@2.3.4)(sqlite3@5.1.7): @@ -10110,6 +10710,15 @@ snapshots: dependencies: randombytes: 2.1.0 + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-function-length@1.1.1: @@ -10121,6 +10730,8 @@ snapshots: setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + sha.js@2.4.11: dependencies: inherits: 2.0.4 @@ -10145,6 +10756,34 @@ snapshots: '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 + side-channel-list@1.0.1: + 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.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10278,6 +10917,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + statuses@2.0.2: {} + stream-combiner2@1.1.1: dependencies: duplexer2: 0.1.4 @@ -10456,6 +11097,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + toposort-class@1.0.1: {} tr46@0.0.3: {} @@ -10527,6 +11170,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typedoc@0.26.10(typescript@5.6.3): dependencies: lunr: 2.3.9 @@ -10618,6 +11267,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -10675,6 +11326,8 @@ snapshots: validator@13.11.0: {} + vary@1.1.2: {} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -11139,6 +11792,10 @@ snapshots: zenroom@5.25.1: {} + zod-to-json-schema@3.25.2(zod@4.1.12): + dependencies: + zod: 4.1.12 + zod@3.25.76: {} zod@4.1.12: {}