From 3230a5d77987472b0d116a9de202c946bd69a6dc Mon Sep 17 00:00:00 2001 From: Johannes Obermair Date: Mon, 1 Jun 2026 14:07:11 +0200 Subject: [PATCH] Add Comet DXP MCP server to the demo Add a demo MCP server that exposes the demo API's page tree, page content, and DAM operations as Model Context Protocol tools and resources, so an MCP client can read and edit demo CMS content. The server is integrated as a pnpm workspace member (demo/mcp-server) and wired into the monorepo: dev-pm gains a demo-mcp-server process, schema.gql and block-meta.json are symlinked via copy-project-files, and knip is configured for the new package. Co-Authored-By: Claude Opus 4.8 --- copy-project-files.js | 4 +- demo/mcp-server/.gitignore | 5 + demo/mcp-server/README.md | 98 ++++++ demo/mcp-server/codegen.ts | 52 +++ demo/mcp-server/eslint.config.mjs | 16 + demo/mcp-server/package.json | 45 +++ demo/mcp-server/src/client.ts | 16 + demo/mcp-server/src/index.ts | 57 ++++ .../src/resources/blockMeta/blockMeta.ts | 23 ++ .../src/resources/blockTypes/blockTypes.ts | 23 ++ demo/mcp-server/src/schemas.ts | 14 + .../checkSlugAvailability.gql.ts | 7 + .../checkSlugAvailability.ts | 25 ++ .../createPageTreeNode.gql.ts | 25 ++ .../createPageTreeNode/createPageTreeNode.ts | 53 +++ .../deletePageTreeNode.gql.ts | 7 + .../deletePageTreeNode/deletePageTreeNode.ts | 22 ++ .../src/tools/getDamFile/getDamFile.gql.ts | 36 ++ .../src/tools/getDamFile/getDamFile.ts | 21 ++ .../src/tools/getPage/getPage.gql.ts | 13 + demo/mcp-server/src/tools/getPage/getPage.ts | 22 ++ .../getPageTreeNode/getPageTreeNode.gql.ts | 45 +++ .../tools/getPageTreeNode/getPageTreeNode.ts | 25 ++ .../getPageTreeNodeByPath.gql.ts | 38 +++ .../getPageTreeNodeByPath.ts | 24 ++ .../tools/listDamFiles/listDamFiles.gql.ts | 54 +++ .../src/tools/listDamFiles/listDamFiles.ts | 46 +++ .../listPageTreeNodes.gql.ts | 21 ++ .../listPageTreeNodes/listPageTreeNodes.ts | 28 ++ .../src/tools/savePage/savePage.gql.ts | 13 + .../mcp-server/src/tools/savePage/savePage.ts | 68 ++++ .../updatePageTreeNode.gql.ts | 14 + .../updatePageTreeNode/updatePageTreeNode.ts | 33 ++ .../updatePageTreeNodeVisibility.gql.ts | 13 + .../updatePageTreeNodeVisibility.ts | 29 ++ demo/mcp-server/tsconfig.json | 14 + demo/mcp-server/tsup.config.ts | 9 + dev-pm.config.ts | 6 + knip.json | 6 + pnpm-lock.yaml | 323 ++++++++++++++++++ 40 files changed, 1391 insertions(+), 2 deletions(-) create mode 100644 demo/mcp-server/.gitignore create mode 100644 demo/mcp-server/README.md create mode 100644 demo/mcp-server/codegen.ts create mode 100644 demo/mcp-server/eslint.config.mjs create mode 100644 demo/mcp-server/package.json create mode 100644 demo/mcp-server/src/client.ts create mode 100644 demo/mcp-server/src/index.ts create mode 100644 demo/mcp-server/src/resources/blockMeta/blockMeta.ts create mode 100644 demo/mcp-server/src/resources/blockTypes/blockTypes.ts create mode 100644 demo/mcp-server/src/schemas.ts create mode 100644 demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.gql.ts create mode 100644 demo/mcp-server/src/tools/checkSlugAvailability/checkSlugAvailability.ts create mode 100644 demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.gql.ts create mode 100644 demo/mcp-server/src/tools/createPageTreeNode/createPageTreeNode.ts create mode 100644 demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.gql.ts create mode 100644 demo/mcp-server/src/tools/deletePageTreeNode/deletePageTreeNode.ts create mode 100644 demo/mcp-server/src/tools/getDamFile/getDamFile.gql.ts create mode 100644 demo/mcp-server/src/tools/getDamFile/getDamFile.ts create mode 100644 demo/mcp-server/src/tools/getPage/getPage.gql.ts create mode 100644 demo/mcp-server/src/tools/getPage/getPage.ts create mode 100644 demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.gql.ts create mode 100644 demo/mcp-server/src/tools/getPageTreeNode/getPageTreeNode.ts create mode 100644 demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.gql.ts create mode 100644 demo/mcp-server/src/tools/getPageTreeNodeByPath/getPageTreeNodeByPath.ts create mode 100644 demo/mcp-server/src/tools/listDamFiles/listDamFiles.gql.ts create mode 100644 demo/mcp-server/src/tools/listDamFiles/listDamFiles.ts create mode 100644 demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.gql.ts create mode 100644 demo/mcp-server/src/tools/listPageTreeNodes/listPageTreeNodes.ts create mode 100644 demo/mcp-server/src/tools/savePage/savePage.gql.ts create mode 100644 demo/mcp-server/src/tools/savePage/savePage.ts create mode 100644 demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.gql.ts create mode 100644 demo/mcp-server/src/tools/updatePageTreeNode/updatePageTreeNode.ts create mode 100644 demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.gql.ts create mode 100644 demo/mcp-server/src/tools/updatePageTreeNodeVisibility/updatePageTreeNodeVisibility.ts create mode 100644 demo/mcp-server/tsconfig.json create mode 100644 demo/mcp-server/tsup.config.ts 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 71f5e058963..487adcf71d9 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.33)(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.33)): 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.33)(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.33 + 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: {}