diff --git a/copy-project-files.js b/copy-project-files.js index 52a53ecbc06..008e51fd1d9 100644 --- a/copy-project-files.js +++ b/copy-project-files.js @@ -36,11 +36,11 @@ const files = [ }, { file: "demo/api/block-meta.json", - targetDir: ["demo/admin", "demo/site"], + targetDir: ["demo/admin", "demo/site", "demo/mcp-server"], }, { file: "demo/api/schema.gql", - targetDir: ["demo/admin", "demo/site"], + targetDir: ["demo/admin", "demo/site", "demo/mcp-server"], }, { file: "demo/api/src/comet-config.json", diff --git a/demo/mcp-server/.gitignore b/demo/mcp-server/.gitignore new file mode 100644 index 00000000000..e6e585555c0 --- /dev/null +++ b/demo/mcp-server/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +schema.gql +block-meta.json +*.generated.ts diff --git a/demo/mcp-server/README.md b/demo/mcp-server/README.md new file mode 100644 index 00000000000..c050ce3ad23 --- /dev/null +++ b/demo/mcp-server/README.md @@ -0,0 +1,98 @@ +# Comet Demo MCP Server + +An [MCP](https://modelcontextprotocol.io) server that exposes the Comet demo API's page tree, page content, +and DAM operations as tools and resources over the Model Context Protocol. It lets an MCP client (e.g. Claude +Code, Claude Desktop) read and edit demo CMS content via natural language. + +It talks to the demo API over GraphQL, so the demo API must be running and reachable. + +## Tools + +| Tool | Description | +| ---------------------------------- | ------------------------------------------------------- | +| `list_page_tree_nodes` | List page tree nodes in a scope (published only) | +| `get_page_tree_node` | Get a single page tree node by ID (published only) | +| `get_page_tree_node_by_path` | Look up a page tree node by URL path | +| `get_page` | Get a page document (content, SEO, stage) by ID | +| `check_slug_availability` | Check whether a slug is available under a parent | +| `create_page_tree_node` | Create a new page tree node | +| `update_page_tree_node` | Update a node's name, slug, and `hideInMenu` | +| `save_page` | Save page content (content blocks, SEO, stage) | +| `update_page_tree_node_visibility` | Publish, unpublish, or archive a node | +| `list_dam_files` | List files in the Digital Asset Management (DAM) system | +| `get_dam_file` | Get a single DAM file by ID | +| `delete_page_tree_node` | Permanently delete an archived node and its document | + +## Resources + +| Resource | URI | Description | +| ------------- | --------------------- | -------------------------------------------------------------------- | +| `block-types` | `comet://block-types` | Lightweight TypeScript interfaces for all content blocks (preferred) | +| `block-meta` | `comet://block-meta` | Full block schema metadata (field kinds, nullability, enums) | + +## Prerequisites + +- The demo API running and reachable (see the repository root [`AGENTS.md`](../../AGENTS.md)). +- `@comet/cli` built (it is built as part of the normal monorepo setup). +- `schema.gql` and `block-meta.json` present in this folder. They are symlinks to `demo/api`, created by the + root `copy-project-files` script (run automatically during setup, or manually via `pnpm run copy-project-files`). + +## Build + +From the repository root: + +```bash +pnpm --filter comet-demo-mcp-server run build +``` + +This generates GraphQL types (`gql:types`) and block-type interfaces (`generate-block-types`), then bundles the +server to `dist/index.js` with `tsup`. + +## Configuration + +The server reads the following environment variables (with development defaults): + +| Variable | Default | Description | +| ------------------------------------- | --------------------------- | --------------------------------------- | +| `API_URL` | `http://localhost:4000` | Base URL of the demo API | +| `API_BASIC_AUTH_SYSTEM_USER_PASSWORD` | `aPasswordWith16Characters` | Password for the basic-auth system user | + +> **Warning:** Authentication uses the basic-auth system user, which has full access to the API. This is intended +> for local development and testing only — it is **not** production-ready and must be replaced by a user-based +> authentication method before any non-local use. + +## Usage with an MCP client + +MCP clients spawn the server over stdio. After building, register it with a command like: + +```json +{ + "mcpServers": { + "comet-demo": { + "command": "node", + "args": ["demo/mcp-server/dist/index.js"], + "env": { + "API_URL": "http://localhost:4000", + "API_BASIC_AUTH_SYSTEM_USER_PASSWORD": "aPasswordWith16Characters" + } + } + } +} +``` + +Use an absolute path to `dist/index.js` if the client's working directory differs from the repository root. + +## Development + +- GraphQL operations live next to their tool in `*.gql.ts` files; queries are validated against `schema.gql` at + build time via GraphQL Code Generator. +- Generated files (`*.generated.ts`, `blocks.generated.ts`), the build output (`dist/`), and the `schema.gql` / + `block-meta.json` symlinks are git-ignored. + +Run linting from the repository root: + +```bash +pnpm --filter comet-demo-mcp-server run lint +# auto-fix: +pnpm --filter comet-demo-mcp-server run lint:fix +``` diff --git a/demo/mcp-server/codegen.ts b/demo/mcp-server/codegen.ts new file mode 100644 index 00000000000..6384ab20f98 --- /dev/null +++ b/demo/mcp-server/codegen.ts @@ -0,0 +1,52 @@ +import { CodegenConfig } from "@graphql-codegen/cli"; + +const config: CodegenConfig = { + schema: "schema.gql", + generates: { + "./src/graphql.generated.ts": { + plugins: ["typescript"], + config: { + avoidOptionals: { + field: true, + }, + enumsAsTypes: true, + namingConvention: "keep", + scalars: { + DateTime: "string", + Date: "string", + LocalDate: "string", + JSON: "unknown", + JSONObject: "Record", + }, + typesPrefix: "GQL", + }, + }, + "./src/": { + documents: ["./src/**/!(*.generated).{tsx,ts}"], + preset: "near-operation-file", + presetConfig: { + extension: ".generated.ts", + baseTypesPath: "graphql.generated.ts", + }, + config: { + avoidOptionals: { + field: true, + }, + enumsAsTypes: true, + namingConvention: "keep", + scalars: { + DateTime: "string", + Date: "string", + LocalDate: "string", + JSON: "unknown", + JSONObject: "Record", + }, + typesPrefix: "GQL", + }, + plugins: ["typescript-operations"], + }, + }, + ignoreNoDocuments: true, +}; + +export default config; diff --git a/demo/mcp-server/eslint.config.mjs b/demo/mcp-server/eslint.config.mjs new file mode 100644 index 00000000000..4b4e0c0f7ec --- /dev/null +++ b/demo/mcp-server/eslint.config.mjs @@ -0,0 +1,16 @@ +import eslintConfigCore from "@comet/eslint-config/core.js"; + +/** @type {import("eslint")} */ +const config = [ + { + ignores: ["dist/**", "src/**/*.generated.ts", "block-meta.json"], + }, + ...eslintConfigCore, + { + rules: { + "@comet/no-other-module-relative-import": "off", + }, + }, +]; + +export default config; diff --git a/demo/mcp-server/package.json b/demo/mcp-server/package.json new file mode 100644 index 00000000000..81e68932593 --- /dev/null +++ b/demo/mcp-server/package.json @@ -0,0 +1,45 @@ +{ + "name": "comet-demo-mcp-server", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "run-s gql:types generate-block-types && tsup", + "generate-block-types": "comet generate-block-types --output-file blocks.generated.ts", + "gql:types": "graphql-codegen", + "lint": "run-s gql:types generate-block-types && run-p lint:prettier lint:eslint lint:tsc", + "lint:ci": "pnpm run lint", + "lint:eslint": "eslint --max-warnings 0 src/ '**/*.json' --no-warn-ignored", + "lint:fix": "run-p lint:fix:eslint lint:fix:prettier", + "lint:fix:eslint": "pnpm run lint:eslint --fix", + "lint:fix:prettier": "pnpm run lint:prettier --write", + "lint:prettier": "pnpm exec prettier --check '*.{ts,js,json,md,yml,yaml}'", + "lint:tsc": "tsc --project .", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.27.1", + "graphql": "^16.14.0", + "graphql-request": "^7.1.2", + "zod": "^3.25.7" + }, + "devDependencies": { + "@babel/core": "^7.29.0", + "@comet/cli": "workspace:*", + "@comet/eslint-config": "workspace:*", + "@graphql-codegen/cli": "^5.0.7", + "@graphql-codegen/near-operation-file-preset": "^3.1.0", + "@graphql-codegen/typescript": "^4.1.6", + "@graphql-codegen/typescript-operations": "^4.6.1", + "@parcel/watcher": "^2.5.6", + "@types/node": "^24.12.4", + "eslint": "^9.39.4", + "npm-run-all2": "^8.0.4", + "prettier": "^3.6.2", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "engines": { + "node": "24" + } +} diff --git a/demo/mcp-server/src/client.ts b/demo/mcp-server/src/client.ts new file mode 100644 index 00000000000..104cebf33ba --- /dev/null +++ b/demo/mcp-server/src/client.ts @@ -0,0 +1,16 @@ +import { GraphQLClient } from "graphql-request"; + +// TODO: +// This authentication method is not PRODUCTION ready and needs to be replaced by a user based authentication method. It +// is only intended for development and testing purposes. +// +// The system user has full access to the API. +const API_URL = process.env.API_URL ?? "http://localhost:4000"; +const API_PASSWORD = process.env.API_BASIC_AUTH_SYSTEM_USER_PASSWORD ?? "aPasswordWith16Characters"; +const AUTH_HEADER = `Basic ${Buffer.from(`system-user:${API_PASSWORD}`).toString("base64")}`; + +export const client = new GraphQLClient(`${API_URL}/api/graphql`, { + headers: { + Authorization: AUTH_HEADER, + }, +}); diff --git a/demo/mcp-server/src/index.ts b/demo/mcp-server/src/index.ts new file mode 100644 index 00000000000..01aab3395da --- /dev/null +++ b/demo/mcp-server/src/index.ts @@ -0,0 +1,57 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; + +import { registerBlockMetaResource } from "./resources/blockMeta/blockMeta"; +import { registerBlockTypesResource } from "./resources/blockTypes/blockTypes"; +import { registerCheckSlugAvailability } from "./tools/checkSlugAvailability/checkSlugAvailability"; +import { registerCreatePageTreeNode } from "./tools/createPageTreeNode/createPageTreeNode"; +import { registerDeletePageTreeNode } from "./tools/deletePageTreeNode/deletePageTreeNode"; +import { registerGetDamFile } from "./tools/getDamFile/getDamFile"; +import { registerGetPage } from "./tools/getPage/getPage"; +import { registerGetPageTreeNode } from "./tools/getPageTreeNode/getPageTreeNode"; +import { registerGetPageTreeNodeByPath } from "./tools/getPageTreeNodeByPath/getPageTreeNodeByPath"; +import { registerListDamFiles } from "./tools/listDamFiles/listDamFiles"; +import { registerListPageTreeNodes } from "./tools/listPageTreeNodes/listPageTreeNodes"; +import { registerSavePage } from "./tools/savePage/savePage"; +import { registerUpdatePageTreeNode } from "./tools/updatePageTreeNode/updatePageTreeNode"; +import { registerUpdatePageTreeNodeVisibility } from "./tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility"; + +// --------------------------------------------------------------------------- +// MCP Server +// --------------------------------------------------------------------------- + +const server = new McpServer({ + name: "comet-demo", + version: "1.0.0", +}); + +// --------------------------------------------------------------------------- +// Tools +// --------------------------------------------------------------------------- + +registerListPageTreeNodes(server); +registerGetPageTreeNode(server); +registerGetPageTreeNodeByPath(server); +registerGetPage(server); +registerCheckSlugAvailability(server); +registerCreatePageTreeNode(server); +registerUpdatePageTreeNode(server); +registerSavePage(server); +registerUpdatePageTreeNodeVisibility(server); +registerListDamFiles(server); +registerGetDamFile(server); +registerDeletePageTreeNode(server); + +// --------------------------------------------------------------------------- +// Resources +// --------------------------------------------------------------------------- + +registerBlockMetaResource(server); +registerBlockTypesResource(server); + +// --------------------------------------------------------------------------- +// Start server +// --------------------------------------------------------------------------- + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/demo/mcp-server/src/resources/blockMeta/blockMeta.ts b/demo/mcp-server/src/resources/blockMeta/blockMeta.ts new file mode 100644 index 00000000000..91dae7134b2 --- /dev/null +++ b/demo/mcp-server/src/resources/blockMeta/blockMeta.ts @@ -0,0 +1,23 @@ +import { readFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const BLOCK_META_PATH = resolve(__dirname, "../block-meta.json"); + +export function registerBlockMetaResource(server: McpServer): void { + server.registerResource( + "block-meta", + "comet://block-meta", + { + description: + "Full block schema metadata — field definitions, types, enums, and nesting for all CMS content blocks (~3000 lines). IMPORTANT: Prefer the lightweight 'block-types' resource (comet://block-types) instead — it provides the same structural information in ~450 lines of TypeScript interfaces, which is sufficient to construct valid save_page inputs. Only use this resource if you need detailed field-level metadata (e.g. kinds, nullability) beyond what block-types provides. Fields named 'draftContent' with kind 'Json' contain Draft.js editor format (from the draft-js library).", + }, + async () => { + const content = await readFile(BLOCK_META_PATH, "utf-8"); + return { contents: [{ uri: "comet://block-meta", text: content, mimeType: "application/json" }] }; + }, + ); +} diff --git a/demo/mcp-server/src/resources/blockTypes/blockTypes.ts b/demo/mcp-server/src/resources/blockTypes/blockTypes.ts new file mode 100644 index 00000000000..e7f474222de --- /dev/null +++ b/demo/mcp-server/src/resources/blockTypes/blockTypes.ts @@ -0,0 +1,23 @@ +import { readFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const BLOCK_TYPES_PATH = resolve(__dirname, "../blocks.generated.ts"); + +export function registerBlockTypesResource(server: McpServer): void { + server.registerResource( + "block-types", + "comet://block-types", + { + description: + "Lightweight TypeScript interfaces describing the shape of all CMS content blocks. PREFER this over block-meta — it is much smaller (~450 lines vs ~3000 lines) and provides the exact same structural information needed to construct valid save_page inputs. Fields named 'draftContent' of type 'unknown' contain Draft.js editor format (from the draft-js library).", + }, + async () => { + const content = await readFile(BLOCK_TYPES_PATH, "utf-8"); + return { contents: [{ uri: "comet://block-types", text: content, mimeType: "text/plain" }] }; + }, + ); +} diff --git a/demo/mcp-server/src/schemas.ts b/demo/mcp-server/src/schemas.ts new file mode 100644 index 00000000000..4f67f6cfc36 --- /dev/null +++ b/demo/mcp-server/src/schemas.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +export const scopeSchema = z + .object({ + domain: z.string().default("main"), + language: z.string().default("en"), + }) + .default({ domain: "main", language: "en" }); + +export const damScopeSchema = z + .object({ + domain: z.string().default("main"), + }) + .default({ domain: "main" }); diff --git a/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.gql.ts b/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.gql.ts new file mode 100644 index 00000000000..8c297db671a --- /dev/null +++ b/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.gql.ts @@ -0,0 +1,7 @@ +import { gql } from "graphql-request"; + +export const checkSlugAvailabilityQuery = gql` + query CheckSlug($slug: String!, $scope: PageTreeNodeScopeInput!, $parentId: ID) { + pageTreeNodeSlugAvailable(slug: $slug, scope: $scope, parentId: $parentId) + } +`; diff --git a/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.ts b/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.ts new file mode 100644 index 00000000000..d2221597afd --- /dev/null +++ b/demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.ts @@ -0,0 +1,25 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { scopeSchema } from "../../schemas"; +import { checkSlugAvailabilityQuery } from "./checkSlugAvailability.gql"; + +export function registerCheckSlugAvailability(server: McpServer): void { + server.registerTool( + "check_slug_availability", + { + description: `Check if a slug is available under a given parent in a scope. +Returns "Available", "Taken", or "Reserved".`, + inputSchema: { + slug: z.string().describe("The slug to check"), + scope: scopeSchema, + parentId: z.string().optional().describe("Parent node ID (omit for root level)"), + }, + }, + async ({ slug, scope, parentId }) => { + const data = await client.request(checkSlugAvailabilityQuery, { slug, scope, parentId }); + return { content: [{ type: "text" as const, text: data.pageTreeNodeSlugAvailable }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.gql.ts b/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.gql.ts new file mode 100644 index 00000000000..51384b3a8d5 --- /dev/null +++ b/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.gql.ts @@ -0,0 +1,25 @@ +import { gql } from "graphql-request"; + +export const createPageTreeNodeMutation = gql` + mutation CreateNode($input: PageTreeNodeCreateInput!, $scope: PageTreeNodeScopeInput!, $category: String!) { + createPageTreeNode(input: $input, scope: $scope, category: $category) { + id + name + slug + path + visibility + documentType + parentId + category + document { + __typename + ... on Page { + id + } + ... on Link { + id + } + } + } + } +`; diff --git a/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.ts b/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.ts new file mode 100644 index 00000000000..e955b57082b --- /dev/null +++ b/demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.ts @@ -0,0 +1,53 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { scopeSchema } from "../../schemas"; +import { createPageTreeNodeMutation } from "./createPageTreeNode.gql"; + +export function registerCreatePageTreeNode(server: McpServer): void { + server.registerTool( + "create_page_tree_node", + { + description: `Create a new page tree node. + +Parameters: +- name: Display name of the page +- slug: URL slug (must be unique within parent) +- documentType: "Page" or "Link" +- parentId: Parent node ID (omit for root level) +- pos: Position among siblings (optional) +- category: e.g. "mainNavigation" (default) +- hideInMenu: Whether to hide in navigation menus + +The node is created with visibility "Unpublished" by default. Use update_page_tree_node_visibility to publish it. + +IMPORTANT: The attached document (Page or Link) is NOT created automatically by this mutation. +To create the document, call save_page with a new UUID as pageId and pass this node's id as attachedPageTreeNodeId. +The response will show document: null — this is expected.`, + inputSchema: { + name: z.string().describe("Display name of the page"), + slug: z.string().describe("URL-safe slug"), + documentType: z.enum(["Page", "Link"]).default("Page").describe('"Page" or "Link"'), + scope: scopeSchema, + parentId: z.string().optional().describe("Parent node ID for nesting"), + pos: z.number().optional().describe("Position among siblings"), + category: z.string().default("mainNavigation").describe("Node category"), + hideInMenu: z.boolean().optional().describe("Hide this node in navigation menus"), + }, + }, + async ({ name, slug, documentType, scope, parentId, pos, category, hideInMenu }) => { + const input: Record = { + name, + slug, + attachedDocument: { type: documentType }, + ...(parentId !== undefined && { parentId }), + ...(pos !== undefined && { pos }), + ...(hideInMenu !== undefined && { hideInMenu }), + }; + + const data = await client.request(createPageTreeNodeMutation, { input, scope, category }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.createPageTreeNode, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.gql.ts b/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.gql.ts new file mode 100644 index 00000000000..ebbf1b8c976 --- /dev/null +++ b/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.gql.ts @@ -0,0 +1,7 @@ +import { gql } from "graphql-request"; + +export const deletePageTreeNodeMutation = gql` + mutation DeleteNode($id: ID!) { + deletePageTreeNode(id: $id) + } +`; diff --git a/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.ts b/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.ts new file mode 100644 index 00000000000..bdfbb58e2fe --- /dev/null +++ b/demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.ts @@ -0,0 +1,22 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { deletePageTreeNodeMutation } from "./deletePageTreeNode.gql"; + +export function registerDeletePageTreeNode(server: McpServer): void { + server.registerTool( + "delete_page_tree_node", + { + description: `Permanently delete a page tree node and its attached document. +WARNING: This is irreversible. The node must be archived first (visibility = "Archived") before it can be deleted.`, + inputSchema: { + id: z.string().describe("The page tree node ID to delete"), + }, + }, + async ({ id }) => { + const data = await client.request(deletePageTreeNodeMutation, { id }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.deletePageTreeNode, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/getDamFile/getDamFile.gql.ts b/demo/mcp-server/src/tools/getDamFile/getDamFile.gql.ts new file mode 100644 index 00000000000..596056c648f --- /dev/null +++ b/demo/mcp-server/src/tools/getDamFile/getDamFile.gql.ts @@ -0,0 +1,36 @@ +import { gql } from "graphql-request"; + +export const getDamFileQuery = gql` + query GetDamFile($id: ID!) { + damFile(id: $id) { + id + name + size + mimetype + contentHash + title + altText + archived + fileUrl + damPath + image { + width + height + dominantColor + cropArea { + focalPoint + width + height + x + y + } + } + folder { + id + name + } + createdAt + updatedAt + } + } +`; diff --git a/demo/mcp-server/src/tools/getDamFile/getDamFile.ts b/demo/mcp-server/src/tools/getDamFile/getDamFile.ts new file mode 100644 index 00000000000..f9a13c1a381 --- /dev/null +++ b/demo/mcp-server/src/tools/getDamFile/getDamFile.ts @@ -0,0 +1,21 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { getDamFileQuery } from "./getDamFile.gql"; + +export function registerGetDamFile(server: McpServer): void { + server.registerTool( + "get_dam_file", + { + description: `Get a single DAM file by ID. Returns full file metadata including image dimensions, crop area, and file URL.`, + inputSchema: { + id: z.string().describe("The DAM file ID (UUID)"), + }, + }, + async ({ id }) => { + const data = await client.request(getDamFileQuery, { id }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.damFile, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/getPage/getPage.gql.ts b/demo/mcp-server/src/tools/getPage/getPage.gql.ts new file mode 100644 index 00000000000..9b3ea716036 --- /dev/null +++ b/demo/mcp-server/src/tools/getPage/getPage.gql.ts @@ -0,0 +1,13 @@ +import { gql } from "graphql-request"; + +export const getPageQuery = gql` + query GetPage($id: ID!) { + page(id: $id) { + id + content + seo + stage + updatedAt + } + } +`; diff --git a/demo/mcp-server/src/tools/getPage/getPage.ts b/demo/mcp-server/src/tools/getPage/getPage.ts new file mode 100644 index 00000000000..401d712b21b --- /dev/null +++ b/demo/mcp-server/src/tools/getPage/getPage.ts @@ -0,0 +1,22 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { getPageQuery } from "./getPage.gql"; + +export function registerGetPage(server: McpServer): void { + server.registerTool( + "get_page", + { + description: `Get a page document by ID. Returns the full page content, SEO, and stage block data as JSON. +Use the document ID from a page tree node's "document.id" field.`, + inputSchema: { + id: z.string().describe("The page document ID (UUID)"), + }, + }, + async ({ id }) => { + const data = await client.request(getPageQuery, { id }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.page, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.gql.ts b/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.gql.ts new file mode 100644 index 00000000000..99e72708f20 --- /dev/null +++ b/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.gql.ts @@ -0,0 +1,45 @@ +import { gql } from "graphql-request"; + +export const getPageTreeNodeQuery = gql` + query GetNode($id: ID!) { + pageTreeNode(id: $id) { + id + name + slug + path + visibility + documentType + parentId + category + hideInMenu + pos + numberOfDescendants + childNodes { + id + name + slug + path + visibility + documentType + } + parentNodes { + id + name + slug + } + document { + __typename + ... on Page { + id + } + ... on Link { + id + } + } + scope { + domain + language + } + } + } +`; diff --git a/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.ts b/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.ts new file mode 100644 index 00000000000..617d1114517 --- /dev/null +++ b/demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.ts @@ -0,0 +1,25 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { getPageTreeNodeQuery } from "./getPageTreeNode.gql"; + +export function registerGetPageTreeNode(server: McpServer): void { + server.registerTool( + "get_page_tree_node", + { + description: `Get a single page tree node by ID. +Returns name, slug, path, visibility, documentType, children, parent info, and the attached document ID. + +IMPORTANT: This query only returns Published nodes. Unpublished or Archived nodes will result in an error. +If you just created a node (which defaults to Unpublished), you cannot retrieve it with this tool until it is published.`, + inputSchema: { + id: z.string().describe("The page tree node ID (UUID)"), + }, + }, + async ({ id }) => { + const data = await client.request(getPageTreeNodeQuery, { id }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.pageTreeNode, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.gql.ts b/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.gql.ts new file mode 100644 index 00000000000..aae637d7c88 --- /dev/null +++ b/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.gql.ts @@ -0,0 +1,38 @@ +import { gql } from "graphql-request"; + +export const getPageTreeNodeByPathQuery = gql` + query GetNodeByPath($path: String!, $scope: PageTreeNodeScopeInput!) { + pageTreeNodeByPath(path: $path, scope: $scope) { + id + name + slug + path + visibility + documentType + parentId + category + hideInMenu + childNodes { + id + name + slug + path + visibility + documentType + } + document { + __typename + ... on Page { + id + } + ... on Link { + id + } + } + scope { + domain + language + } + } + } +`; diff --git a/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.ts b/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.ts new file mode 100644 index 00000000000..46311f72dcf --- /dev/null +++ b/demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.ts @@ -0,0 +1,24 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { scopeSchema } from "../../schemas"; +import { getPageTreeNodeByPathQuery } from "./getPageTreeNodeByPath.gql"; + +export function registerGetPageTreeNodeByPath(server: McpServer): void { + server.registerTool( + "get_page_tree_node_by_path", + { + description: `Look up a page tree node by its URL path (e.g. "/about" or "/products/widget"). +Returns the full node including children and attached document ID.`, + inputSchema: { + path: z.string().describe('The URL path, e.g. "/about"'), + scope: scopeSchema, + }, + }, + async ({ path, scope }) => { + const data = await client.request(getPageTreeNodeByPathQuery, { path, scope }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.pageTreeNodeByPath, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/listDamFiles/listDamFiles.gql.ts b/demo/mcp-server/src/tools/listDamFiles/listDamFiles.gql.ts new file mode 100644 index 00000000000..8633340e96e --- /dev/null +++ b/demo/mcp-server/src/tools/listDamFiles/listDamFiles.gql.ts @@ -0,0 +1,54 @@ +import { gql } from "graphql-request"; + +export const listDamFilesQuery = gql` + query ListDamFiles( + $scope: DamScopeInput! + $folderId: ID + $filter: FileFilterInput + $limit: Int + $offset: Int + $sortColumnName: String + $sortDirection: SortDirection + ) { + damFilesList( + scope: $scope + folderId: $folderId + filter: $filter + limit: $limit + offset: $offset + sortColumnName: $sortColumnName + sortDirection: $sortDirection + ) { + nodes { + id + name + size + mimetype + contentHash + title + altText + archived + fileUrl + image { + width + height + dominantColor + cropArea { + focalPoint + width + height + x + y + } + } + folder { + id + name + } + createdAt + updatedAt + } + totalCount + } + } +`; diff --git a/demo/mcp-server/src/tools/listDamFiles/listDamFiles.ts b/demo/mcp-server/src/tools/listDamFiles/listDamFiles.ts new file mode 100644 index 00000000000..fe5320c0815 --- /dev/null +++ b/demo/mcp-server/src/tools/listDamFiles/listDamFiles.ts @@ -0,0 +1,46 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { damScopeSchema } from "../../schemas"; +import { listDamFilesQuery } from "./listDamFiles.gql"; + +export function registerListDamFiles(server: McpServer): void { + server.registerTool( + "list_dam_files", + { + description: `List files in the Digital Asset Management (DAM) system. +Returns an array of files with id, name, mimetype, size, fileUrl, title, altText, and image metadata. +Use the returned file IDs as damFileId when constructing PixelImage or SvgImage blocks. + +Optional: filter by mimetype (e.g. "image/jpeg"), search by filename, or list files in a specific folder.`, + inputSchema: { + scope: damScopeSchema, + folderId: z.string().optional().describe("Filter by folder ID"), + filter: z + .object({ + searchText: z.string().optional().describe("Search by filename"), + mimetypes: z.array(z.string()).optional().describe('Filter by mimetypes, e.g. ["image/jpeg", "image/png"]'), + }) + .optional() + .describe("Filter options"), + limit: z.number().default(50).describe("Max number of files to return"), + offset: z.number().default(0).describe("Offset for pagination"), + sortColumnName: z.string().optional().describe("Sort by column name"), + sortDirection: z.enum(["ASC", "DESC"]).optional().describe("Sort direction"), + }, + }, + async ({ scope, folderId, filter, limit, offset, sortColumnName, sortDirection }) => { + const data = await client.request(listDamFilesQuery, { + scope, + folderId, + filter, + limit, + offset, + ...(sortColumnName && { sortColumnName }), + ...(sortDirection && { sortDirection }), + }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.damFilesList, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.gql.ts b/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.gql.ts new file mode 100644 index 00000000000..64e9c9a6415 --- /dev/null +++ b/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.gql.ts @@ -0,0 +1,21 @@ +import { gql } from "graphql-request"; + +export const listPageTreeNodesQuery = gql` + query ListNodes($scope: PageTreeNodeScopeInput!, $category: String, $documentType: String) { + paginatedPageTreeNodes(scope: $scope, category: $category, documentType: $documentType, limit: 100, offset: 0) { + nodes { + id + name + slug + path + visibility + documentType + parentId + category + hideInMenu + pos + } + totalCount + } + } +`; diff --git a/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.ts b/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.ts new file mode 100644 index 00000000000..72ddd07d4d0 --- /dev/null +++ b/demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.ts @@ -0,0 +1,28 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { scopeSchema } from "../../schemas"; +import { listPageTreeNodesQuery } from "./listPageTreeNodes.gql"; + +export function registerListPageTreeNodes(server: McpServer): void { + server.registerTool( + "list_page_tree_nodes", + { + description: `List all page tree nodes in a scope. +Returns an array of nodes with id, name, slug, path, visibility, documentType, parentId, and category. +Optional: filter by category (e.g. "mainNavigation") or documentType (e.g. "Page", "Link"). + +IMPORTANT: This query only returns Published nodes. Newly created (Unpublished) or Archived nodes will not appear in the results.`, + inputSchema: { + scope: scopeSchema, + category: z.string().optional().describe('Filter by category, e.g. "mainNavigation"'), + documentType: z.string().optional().describe('Filter by document type, e.g. "Page" or "Link"'), + }, + }, + async ({ scope, category, documentType }) => { + const data = await client.request(listPageTreeNodesQuery, { scope, category, documentType }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.paginatedPageTreeNodes, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/savePage/savePage.gql.ts b/demo/mcp-server/src/tools/savePage/savePage.gql.ts new file mode 100644 index 00000000000..eb4e279f82a --- /dev/null +++ b/demo/mcp-server/src/tools/savePage/savePage.gql.ts @@ -0,0 +1,13 @@ +import { gql } from "graphql-request"; + +export const savePageMutation = gql` + mutation SavePage($pageId: ID!, $input: PageInput!, $attachedPageTreeNodeId: ID) { + savePage(pageId: $pageId, input: $input, attachedPageTreeNodeId: $attachedPageTreeNodeId) { + id + content + seo + stage + updatedAt + } + } +`; diff --git a/demo/mcp-server/src/tools/savePage/savePage.ts b/demo/mcp-server/src/tools/savePage/savePage.ts new file mode 100644 index 00000000000..046aa369068 --- /dev/null +++ b/demo/mcp-server/src/tools/savePage/savePage.ts @@ -0,0 +1,68 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { savePageMutation } from "./savePage.gql"; + +export function registerSavePage(server: McpServer): void { + server.registerTool( + "save_page", + { + description: `Save page content (content blocks, SEO, stage) for an existing page document. + +The pageId is the document ID from the page tree node's "document.id" field. +If the page document does not exist yet, generate a new UUID for pageId and pass attachedPageTreeNodeId to create and attach it. + +IMPORTANT: Always pass attachedPageTreeNodeId (the page tree node ID) on every save call — not just the first one. The API uses it for content scope resolution and will fail without it. + +Read the block-meta resource (comet://block-meta) to understand the available block types, their fields, nesting, and valid values. Use it to construct valid content, seo, and stage inputs. + +Each block in a blocks-block array needs a "key" (uuid), "visible" (boolean), "type" (string), and "props" (object matching the block's fields from block-meta). + +Minimal example — a page with a single heading block: +{ + "content": { + "blocks": [{ + "key": "", + "visible": true, + "type": "heading", + "props": { + "heading": { + "eyebrow": { "draftContent": { "blocks": [{ "key": "abc12", "text": "", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], "data": {} }], "entityMap": {} } }, + "headline": { "draftContent": { "blocks": [{ "key": "def34", "text": "My Page Title", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], "data": {} }], "entityMap": {} } }, + "htmlTag": "h1" + }, + "textAlignment": "left" + } + }] + }, + "seo": { + "htmlTitle": "My Page", + "metaDescription": "A description", + "openGraphTitle": "", + "openGraphDescription": "", + "openGraphImage": { "block": null, "visible": false }, + "structuredData": "{}", + "noIndex": false, + "priority": "0_5", + "changeFrequency": "weekly", + "alternativeLinks": [] + }, + "stage": { "blocks": [] } +}`, + inputSchema: { + pageId: z.string().describe("The page document ID (UUID)"), + input: z.object({ + content: z.any().describe("PageContentBlock JSON"), + seo: z.any().describe("SeoBlock JSON"), + stage: z.any().describe("StageBlock JSON"), + }), + attachedPageTreeNodeId: z.string().optional().describe("Page tree node ID to attach this page to"), + }, + }, + async ({ pageId, input, attachedPageTreeNodeId }) => { + const data = await client.request(savePageMutation, { pageId, input, attachedPageTreeNodeId }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.savePage, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.gql.ts b/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.gql.ts new file mode 100644 index 00000000000..27b5ff0105b --- /dev/null +++ b/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.gql.ts @@ -0,0 +1,14 @@ +import { gql } from "graphql-request"; + +export const updatePageTreeNodeMutation = gql` + mutation UpdateNode($id: ID!, $input: PageTreeNodeUpdateInput!) { + updatePageTreeNode(id: $id, input: $input) { + id + name + slug + path + visibility + hideInMenu + } + } +`; diff --git a/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.ts b/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.ts new file mode 100644 index 00000000000..51f2ef1a814 --- /dev/null +++ b/demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.ts @@ -0,0 +1,33 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { updatePageTreeNodeMutation } from "./updatePageTreeNode.gql"; + +export function registerUpdatePageTreeNode(server: McpServer): void { + server.registerTool( + "update_page_tree_node", + { + description: `Update a page tree node's name, slug, and/or hideInMenu flag. +Set createAutomaticRedirectsOnSlugChange to false to skip redirect creation when changing the slug.`, + inputSchema: { + id: z.string().describe("The page tree node ID"), + name: z.string().describe("New display name"), + slug: z.string().describe("New URL slug"), + hideInMenu: z.boolean().optional().describe("Hide in navigation menus"), + createAutomaticRedirectsOnSlugChange: z.boolean().default(true).describe("Auto-create redirects on slug change"), + }, + }, + async ({ id, name, slug, hideInMenu, createAutomaticRedirectsOnSlugChange }) => { + const input: Record = { + name, + slug, + createAutomaticRedirectsOnSlugChange, + ...(hideInMenu !== undefined && { hideInMenu }), + }; + + const data = await client.request(updatePageTreeNodeMutation, { id, input }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.updatePageTreeNode, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.gql.ts b/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.gql.ts new file mode 100644 index 00000000000..8d38033a246 --- /dev/null +++ b/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.gql.ts @@ -0,0 +1,13 @@ +import { gql } from "graphql-request"; + +export const updatePageTreeNodeVisibilityMutation = gql` + mutation UpdateVisibility($id: ID!, $input: PageTreeNodeUpdateVisibilityInput!) { + updatePageTreeNodeVisibility(id: $id, input: $input) { + id + name + slug + path + visibility + } + } +`; diff --git a/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.ts b/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.ts new file mode 100644 index 00000000000..34f835b5fa8 --- /dev/null +++ b/demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.ts @@ -0,0 +1,29 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { client } from "../../client"; +import { updatePageTreeNodeVisibilityMutation } from "./updatePageTreeNodeVisibility.gql"; + +export function registerUpdatePageTreeNodeVisibility(server: McpServer): void { + server.registerTool( + "update_page_tree_node_visibility", + { + description: `Publish, unpublish, or archive a page tree node. + +Visibility values: +- "Published" — page is publicly visible +- "Unpublished" — page exists but is not publicly accessible +- "Archived" — soft-deleted, content cannot be updated + +Note: Archived pages cannot have their content updated via savePage.`, + inputSchema: { + id: z.string().describe("The page tree node ID"), + visibility: z.enum(["Published", "Unpublished", "Archived"]).describe("The new visibility state"), + }, + }, + async ({ id, visibility }) => { + const data = await client.request(updatePageTreeNodeVisibilityMutation, { id, input: { visibility } }); + return { content: [{ type: "text" as const, text: JSON.stringify(data.updatePageTreeNodeVisibility, null, 2) }] }; + }, + ); +} diff --git a/demo/mcp-server/tsconfig.json b/demo/mcp-server/tsconfig.json new file mode 100644 index 00000000000..8abb0f19090 --- /dev/null +++ b/demo/mcp-server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "ES2022", + "moduleResolution": "bundler", + "noEmit": true, + "outDir": "dist", + "rootDir": "src", + "skipLibCheck": true, + "strict": true, + "target": "ES2022" + }, + "include": ["src"] +} diff --git a/demo/mcp-server/tsup.config.ts b/demo/mcp-server/tsup.config.ts new file mode 100644 index 00000000000..5cb2591f328 --- /dev/null +++ b/demo/mcp-server/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: "esm", + target: "es2022", + outDir: "dist", + clean: true, +}); diff --git a/dev-pm.config.ts b/dev-pm.config.ts index 9948ceeac27..b4cfa2b0897 100644 --- a/dev-pm.config.ts +++ b/dev-pm.config.ts @@ -234,6 +234,12 @@ export default defineConfig({ name: "demo-api-storybook", script: "pnpm --filter comet-demo-api run storybook", }, + { + name: "demo-mcp-server", + script: "pnpm --filter comet-demo-mcp-server run build && pnpm --filter comet-demo-mcp-server run start", + group: ["demo-api", "demo"], + waitOn: ["tcp:$API_PORT"], + }, //group demo site { diff --git a/knip.json b/knip.json index 17c590bcc55..d4402c4a2b3 100644 --- a/knip.json +++ b/knip.json @@ -110,6 +110,12 @@ "ignoreDependencies": ["@babel/core"], "ignore": ["**/*.generated.ts"] }, + "demo/mcp-server": { + "entry": ["./src/index.ts"], + "project": ["./src/**/*.{ts,tsx}"], + "ignore": ["**/*.generated.ts"], + "ignoreDependencies": ["@babel/core", "@parcel/watcher"] + }, "storybook": { "entry": ["./src/**/*.stories.tsx"], "project": ["./src/**/*.{ts,tsx}"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c4e3828ce2..a11c519938b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -516,6 +516,64 @@ importers: specifier: ^4.0.16 version: 4.0.16(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(jiti@2.7.0)(jsdom@28.1.0(@noble/hashes@1.8.0))(msw@2.14.6(@types/node@24.12.4)(typescript@5.9.3))(sass@1.93.0)(terser@5.46.1)(yaml@2.8.3) + demo/mcp-server: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.27.1 + version: 1.29.0(zod@3.25.76) + graphql: + specifier: ^16.14.0 + version: 16.14.0 + graphql-request: + specifier: ^7.1.2 + version: 7.4.0(graphql@16.14.0) + zod: + specifier: ^3.25.7 + version: 3.25.76 + devDependencies: + '@babel/core': + specifier: ^7.29.0 + version: 7.29.0 + '@comet/cli': + specifier: workspace:* + version: link:../../packages/cli + '@comet/eslint-config': + specifier: workspace:* + version: link:../../packages/eslint-config + '@graphql-codegen/cli': + specifier: ^5.0.7 + version: 5.0.7(@babel/core@7.29.0)(@parcel/watcher@2.5.6)(@types/node@24.12.4)(cosmiconfig-toml-loader@1.0.0)(enquirer@2.4.1)(graphql@16.14.0)(typescript@5.9.3) + '@graphql-codegen/near-operation-file-preset': + specifier: ^3.1.0 + version: 3.1.0(graphql@16.14.0) + '@graphql-codegen/typescript': + specifier: ^4.1.6 + version: 4.1.6(@babel/core@7.29.0)(graphql@16.14.0) + '@graphql-codegen/typescript-operations': + specifier: ^4.6.1 + version: 4.6.1(@babel/core@7.29.0)(graphql@16.14.0) + '@parcel/watcher': + specifier: ^2.5.6 + version: 2.5.6 + '@types/node': + specifier: ^24.12.4 + version: 24.12.4 + eslint: + specifier: ^9.39.4 + version: 9.39.4(jiti@2.7.0) + npm-run-all2: + specifier: ^8.0.4 + version: 8.0.4 + prettier: + specifier: ^3.6.2 + version: 3.6.2 + tsup: + specifier: ^8.5.1 + version: 8.5.1(@microsoft/api-extractor@7.52.8(@types/node@24.12.4))(@swc/core@1.15.40)(jiti@2.7.0)(postcss@8.5.14)(typescript@5.9.3)(yaml@2.8.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + demo/site: dependencies: '@comet/mail-react': @@ -5512,6 +5570,12 @@ packages: '@hapi/topo@6.0.2': resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -6082,6 +6146,16 @@ packages: peerDependencies: '@mikro-orm/core': ^6.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 + '@mswjs/interceptors@0.41.9': resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} @@ -9269,6 +9343,9 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -9657,6 +9734,12 @@ packages: peerDependencies: esbuild: '>=0.17' + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -11383,6 +11466,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + exceljs@4.4.0: resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} engines: {node: '>=8.3.0'} @@ -11398,6 +11489,12 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express-static-gzip@3.0.1: resolution: {integrity: sha512-LMeU/3YjFlFUa4vrPX+RoMMRW5mIpF4Iysgs6gX7A59WCY4BzyF3O28mBr4eMlWuW4DU9wVAVuVcfx29ln1N6g==} @@ -11632,6 +11729,9 @@ packages: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -11962,6 +12062,11 @@ packages: peerDependencies: graphql: 14 - 16 + graphql-request@7.4.0: + resolution: {integrity: sha512-xfr+zFb/QYbs4l4ty0dltqiXIp07U6sl+tOKAb0t50/EnQek6CVVBLjETXi+FghElytvgaAWtIOt3EV7zLzIAQ==} + peerDependencies: + graphql: 14 - 16 + graphql-scalars@1.25.0: resolution: {integrity: sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==} engines: {node: '>=10'} @@ -12100,6 +12205,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hono@4.12.23: + resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==} + engines: {node: '>=16.9.0'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -12395,6 +12504,10 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -12770,6 +12883,9 @@ packages: jose@6.1.1: resolution: {integrity: sha512-GWSqjfOPf4cWOkBzw5THBjtGPhXKqYnfRBzh4Ni+ArTrQQ9unvmsA3oFLqaYKoKe5sjWmGu5wVKg9Ft1i+LQfg==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -13864,6 +13980,9 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -14460,6 +14579,14 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-dir@7.0.0: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} @@ -14631,6 +14758,24 @@ packages: peerDependencies: postcss: ^8.4 + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss-loader@7.3.4: resolution: {integrity: sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==} engines: {node: '>= 14.15.0'} @@ -16048,6 +16193,10 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -16365,6 +16514,11 @@ packages: peerDependencies: graphql: ^15.7.2 || ^16.0.0 + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -16489,6 +16643,13 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thingies@2.5.0: resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} engines: {node: '>=10.18'} @@ -16626,6 +16787,9 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-invariant@0.10.3: resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} engines: {node: '>=8'} @@ -16709,6 +16873,25 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -17578,6 +17761,11 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + 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==} @@ -21709,6 +21897,10 @@ snapshots: dependencies: '@hapi/hoek': 11.0.7 + '@hono/node-server@1.19.14(hono@4.12.23)': + dependencies: + hono: 4.12.23 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -22363,6 +22555,28 @@ snapshots: - supports-color - tedious + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.23) + ajv: 8.18.0 + ajv-formats: 3.0.1 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.23 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.1 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@mswjs/interceptors@0.41.9': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -26010,6 +26224,8 @@ snapshots: ansis@4.2.0: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -26524,6 +26740,11 @@ snapshots: esbuild: 0.17.19 load-tsconfig: 0.2.5 + bundle-require@5.1.0(esbuild@0.27.4): + dependencies: + esbuild: 0.27.4 + load-tsconfig: 0.2.5 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -28476,6 +28697,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + exceljs@4.4.0: dependencies: archiver: 5.3.2 @@ -28504,6 +28731,11 @@ snapshots: expect-type@1.3.0: {} + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + express-static-gzip@3.0.1: dependencies: mime-types: 3.0.2 @@ -28844,6 +29076,12 @@ snapshots: locate-path: 7.2.0 path-exists: 5.0.0 + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.7.4 + rollup: 4.59.0 + flat-cache@3.2.0: dependencies: flatted: 3.3.3 @@ -29228,6 +29466,11 @@ snapshots: transitivePeerDependencies: - encoding + graphql-request@7.4.0(graphql@16.14.0): + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.0) + graphql: 16.14.0 + graphql-scalars@1.25.0(graphql@16.14.0): dependencies: graphql: 16.14.0 @@ -29421,6 +29664,8 @@ snapshots: dependencies: react-is: 16.13.1 + hono@4.12.23: {} + hookable@6.0.1: {} hookified@1.15.0: {} @@ -29774,6 +30019,8 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@10.2.0: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -30112,6 +30359,8 @@ snapshots: jose@6.1.1: {} + jose@6.2.3: {} + joycon@3.1.1: {} js-beautify@1.15.4: @@ -31710,6 +31959,12 @@ snapshots: mute-stream@3.0.0: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.12: {} nanoid@5.1.6: {} @@ -32333,6 +32588,10 @@ snapshots: pify@4.0.1: {} + pirates@4.0.7: {} + + pkce-challenge@5.0.1: {} + pkg-dir@7.0.0: dependencies: find-up: 6.3.0 @@ -32504,6 +32763,14 @@ snapshots: '@csstools/utilities': 2.0.0(postcss@8.5.14) postcss: 8.5.14 + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.14)(yaml@2.8.3): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.7.0 + postcss: 8.5.14 + yaml: 2.8.3 + postcss-loader@7.3.4(postcss@8.5.14)(typescript@5.9.3)(webpack@5.106.2(@swc/core@1.15.40)): dependencies: cosmiconfig: 8.3.6(typescript@5.9.3) @@ -34277,6 +34544,8 @@ snapshots: source-map@0.7.4: {} + source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} spawndamnit@3.0.1: @@ -34669,6 +34938,16 @@ snapshots: - bufferutil - utf-8-validate + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -34838,6 +35117,14 @@ snapshots: text-table@0.2.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thingies@2.5.0(tslib@2.8.1): dependencies: tslib: 2.8.1 @@ -34942,6 +35229,8 @@ snapshots: ts-dedent@2.2.0: {} + ts-interface-checker@0.1.13: {} + ts-invariant@0.10.3: dependencies: tslib: 2.8.1 @@ -35032,6 +35321,36 @@ snapshots: tsscmp@1.0.6: {} + tsup@8.5.1(@microsoft/api-extractor@7.52.8(@types/node@24.12.4))(@swc/core@1.15.40)(jiti@2.7.0)(postcss@8.5.14)(typescript@5.9.3)(yaml@2.8.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.4) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.4 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.14)(yaml@2.8.3) + resolve-from: 5.0.0 + rollup: 4.59.0 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + '@microsoft/api-extractor': 7.52.8(@types/node@24.12.4) + '@swc/core': 1.15.40 + postcss: 8.5.14 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -36150,6 +36469,10 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod@3.25.76: {} zod@4.4.3: {}