UNPKG

notion-mcp-server

Version:

Notion MCP server with token auth: connect Claude Code, Claude Desktop, Cursor, VS Code, Codex and any MCP client to Notion. Search, create and edit pages, query databases, comment and upload files. Runs headless in CI and agents.

68 lines (67 loc) • 2.43 kB
import { z } from "zod"; import { stableStringify } from "../utils/stable-json.js"; const SHARED_REFS = {}; export function registerSharedRef(name, schema) { SHARED_REFS[name] = schema; } export function emitJsonSchema(schema) { const raw = z.toJSONSchema(schema, { target: "draft-7", unrepresentable: "any", }); return hoistSharedRefs(raw); } function hoistSharedRefs(root) { const sharedSchemas = {}; for (const [name, zodSchema] of Object.entries(SHARED_REFS)) { const raw = z.toJSONSchema(zodSchema, { target: "draft-7", unrepresentable: "any", }); // Root-level emission adds `$schema`, but nested inline usages don't carry it. // Strip it so the equality key matches inline sites. const { $schema: _drop, ...rest } = raw; void _drop; sharedSchemas[name] = rest; } const refByJson = new Map(); for (const [name, json] of Object.entries(sharedSchemas)) { refByJson.set(stableStringify(json), name); } let usedNames = new Set(); function walk(node) { if (Array.isArray(node)) return node.map(walk); if (node === null || typeof node !== "object") return node; const obj = node; const key = stableStringify(obj); const match = refByJson.get(key); if (match) { usedNames.add(match); return { $ref: `#/$defs/${match}` }; } return walkChildren(obj); } // Walk into a node without testing the node itself for a shared-ref match. // A definition body is the one place that must not become a $ref: matching // there emits `$defs.parent = {$ref: "#/$defs/parent"}`, which resolves to // itself and hides the shape it exists to publish. function walkChildren(obj) { const out = {}; for (const [k, v] of Object.entries(obj)) out[k] = walk(v); return out; } const walked = walk(root); if (usedNames.size === 0) return walked; const defs = walked.$defs ?? {}; // usedNames grows if one definition body references another. A for-of over a // Set visits entries added during iteration, so those get bodies too. for (const name of usedNames) { if (!(name in defs)) defs[name] = walkChildren(sharedSchemas[name]); } return { ...walked, $defs: defs }; }