From cb873381f1b878cd98f53ef7da60b08a7a3d85ec Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 9 Aug 2025 14:14:51 +0200 Subject: [PATCH] auto generated tools docs, added stdio transport, added additional mcp config and permissions --- app/internal/docs.build-assets.ts | 35 + app/package.json | 3 +- app/src/App.ts | 5 +- app/src/cli/commands/mcp/mcp.ts | 113 +- app/src/cli/utils/sys.ts | 2 +- app/src/core/utils/console.ts | 16 + app/src/data/connection/index.ts | 1 + app/src/index.ts | 1 + app/src/modules/ModuleHelper.ts | 8 +- app/src/modules/mcp/$object.ts | 32 +- app/src/modules/permissions/index.ts | 1 + app/src/modules/server/AppServer.ts | 3 + app/src/modules/server/SystemController.ts | 28 + app/src/modules/server/system-mcp.ts | 36 + app/tsconfig.json | 6 +- docs/app/[[...slug]]/page.tsx | 4 +- docs/components/McpTool.tsx | 55 + docs/content/docs/(documentation)/meta.json | 2 +- .../(documentation)/modules/server/mcp.mdx | 361 ++ .../(documentation)/modules/server/meta.json | 3 + .../{server.mdx => server/overview.mdx} | 0 docs/mcp.json | 3047 +++++++++++++++++ docs/package-lock.json | 26 +- docs/package.json | 4 +- docs/scripts/generate-mcp.ts | 65 + 25 files changed, 3770 insertions(+), 87 deletions(-) create mode 100644 app/internal/docs.build-assets.ts create mode 100644 app/src/modules/server/system-mcp.ts create mode 100644 docs/components/McpTool.tsx create mode 100644 docs/content/docs/(documentation)/modules/server/mcp.mdx create mode 100644 docs/content/docs/(documentation)/modules/server/meta.json rename docs/content/docs/(documentation)/modules/{server.mdx => server/overview.mdx} (100%) create mode 100644 docs/mcp.json create mode 100644 docs/scripts/generate-mcp.ts diff --git a/app/internal/docs.build-assets.ts b/app/internal/docs.build-assets.ts new file mode 100644 index 0000000..127def7 --- /dev/null +++ b/app/internal/docs.build-assets.ts @@ -0,0 +1,35 @@ +import { createApp } from "bknd/adapter/bun"; + +async function generate() { + console.info("Generating MCP documentation..."); + const app = await createApp({ + initialConfig: { + server: { + mcp: { + enabled: true, + }, + }, + auth: { + enabled: true, + }, + media: { + enabled: true, + adapter: { + type: "local", + config: { + path: "./", + }, + }, + }, + }, + }); + await app.build(); + + const res = await app.server.request("/mcp?explain=1"); + const { tools, resources } = await res.json(); + await Bun.write("../docs/mcp.json", JSON.stringify({ tools, resources }, null, 2)); + + console.info("MCP documentation generated."); +} + +void generate(); diff --git a/app/package.json b/app/package.json index 3ef908d..3fecc56 100644 --- a/app/package.json +++ b/app/package.json @@ -43,7 +43,8 @@ "test:e2e:adapters": "bun run e2e/adapters.ts", "test:e2e:ui": "playwright test --ui", "test:e2e:debug": "playwright test --debug", - "test:e2e:report": "playwright show-report" + "test:e2e:report": "playwright show-report", + "docs:build-assets": "bun internal/docs.build-assets.ts" }, "license": "FSL-1.1-MIT", "dependencies": { diff --git a/app/src/App.ts b/app/src/App.ts index bd519cf..82cf2ed 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -168,13 +168,12 @@ export class App program .command("mcp") - .description("mcp server") - .option("--verbose", "verbose output") + .description("mcp server stdio transport") .option("--config ", "config file") .option("--db-url ", "database url, can be any valid sqlite url") - .option("--port ", "port to listen on", "3000") - .option("--path ", "path to listen on", "/mcp") .option( "--token ", "token to authenticate requests, if not provided, uses BEARER_TOKEN environment variable", ) + .option("--verbose", "verbose output") .option("--log-level ", "log level") + .option("--force", "force enable mcp") .action(action); async function action(options: { verbose?: boolean; config?: string; dbUrl?: string; - port?: string; - path?: string; token?: string; logLevel?: string; + force?: boolean; }) { + const verbose = !!options.verbose; + const __oldConsole = { ...console }; + + // disable console + if (!verbose) { + $console.disable(); + Object.entries(console).forEach(([key]) => { + console[key] = () => null; + }); + } + const app = await makeAppFromEnv({ config: options.config, dbUrl: options.dbUrl, server: "node", }); - const token = options.token || process.env.BEARER_TOKEN; - const middlewareServer = getMcpServer(app.server); - - const appConfig = app.modules.configs(); - const { version, ...appSchema } = app.getSchema(); - - const schema = s.strictObject(appSchema); - - const nodes = [...schema.walk({ data: appConfig })].filter( - (n) => isObject(n.schema) && mcpSchemaSymbol in n.schema, - ) as s.Node[]; - const tools = [ - ...middlewareServer.tools, - ...app.modules.ctx().mcp.tools, - ...nodes.flatMap((n) => n.schema.getTools(n)), - ]; - const resources = [...middlewareServer.resources, ...app.modules.ctx().mcp.resources]; - - const server = new McpServer( - { - name: "bknd", - version: await getVersion(), - }, - { app, ctx: () => app.modules.ctx() }, - tools, - resources, - ); - - if (token) { - server.setAuthentication({ - type: "bearer", - token, - }); + if (!app.modules.get("server").config.mcp.enabled && !options.force) { + $console.enable(); + Object.assign(console, __oldConsole); + console.error("MCP is not enabled in the config, use --force to enable it"); + process.exit(1); } - const hono = new Hono().use( - mcpMiddleware({ - server, - sessionsEnabled: true, - debug: { - logLevel: options.logLevel as any, - explainEndpoint: true, - }, - endpoint: { - path: String(options.path) as any, - }, - }), - ); + const token = options.token || process.env.BEARER_TOKEN; + const server = getSystemMcp(app); - serve({ - fetch: hono.fetch, - port: Number(options.port) || 3000, - }); - - if (options.verbose) { - console.info(`Server is running on http://localhost:${options.port}${options.path}`); + if (verbose) { console.info( - `āš™ļø Tools (${server.tools.length}):\n${server.tools.map((t) => `- ${t.name}`).join("\n")}\n`, + `\nāš™ļø Tools (${server.tools.length}):\n${server.tools.map((t) => `- ${t.name}`).join("\n")}\n`, ); console.info( `šŸ“š Resources (${server.resources.length}):\n${server.resources.map((r) => `- ${r.name}`).join("\n")}`, ); + console.info("\nMCP server is running on STDIO transport"); + } + + if (options.logLevel) { + server.setLogLevel(options.logLevel as any); + } + + const stdout = process.stdout; + const stdin = process.stdin; + const stderr = process.stderr; + + { + using transport = stdioTransport(server, { + stdin, + stdout, + stderr, + raw: new Request("https://localhost", { + headers: token ? { Authorization: `Bearer ${token}` } : undefined, + }), + }); } } diff --git a/app/src/cli/utils/sys.ts b/app/src/cli/utils/sys.ts index 56ae32e..4882180 100644 --- a/app/src/cli/utils/sys.ts +++ b/app/src/cli/utils/sys.ts @@ -26,7 +26,7 @@ export async function getVersion(_path: string = "") { return JSON.parse(pkg).version ?? "preview"; } } catch (e) { - console.error("Failed to resolve version"); + //console.error("Failed to resolve version"); } return "unknown"; diff --git a/app/src/core/utils/console.ts b/app/src/core/utils/console.ts index b07fa2c..4932ada 100644 --- a/app/src/core/utils/console.ts +++ b/app/src/core/utils/console.ts @@ -76,6 +76,7 @@ declare global { | { level: TConsoleSeverity; id?: string; + enabled?: boolean; } | undefined; } @@ -86,6 +87,7 @@ const defaultLevel = env("cli_log_level", "log") as TConsoleSeverity; // biome-ignore lint/suspicious/noAssignInExpressions: const config = (globalThis.__consoleConfig ??= { level: defaultLevel, + enabled: true, //id: crypto.randomUUID(), // for debugging }); @@ -95,6 +97,14 @@ export const $console = new Proxy(config as any, { switch (prop) { case "original": return console; + case "disable": + return () => { + config.enabled = false; + }; + case "enable": + return () => { + config.enabled = true; + }; case "setLevel": return (l: TConsoleSeverity) => { config.level = l; @@ -105,6 +115,10 @@ export const $console = new Proxy(config as any, { }; } + if (!config.enabled) { + return () => null; + } + const current = keys.indexOf(config.level); const requested = keys.indexOf(prop as string); @@ -118,6 +132,8 @@ export const $console = new Proxy(config as any, { } & { setLevel: (l: TConsoleSeverity) => void; resetLevel: () => void; + disable: () => void; + enable: () => void; }; export function colorizeConsole(con: typeof console) { diff --git a/app/src/data/connection/index.ts b/app/src/data/connection/index.ts index a55d135..969611a 100644 --- a/app/src/data/connection/index.ts +++ b/app/src/data/connection/index.ts @@ -9,6 +9,7 @@ export { type ConnQueryResults, customIntrospector, } from "./Connection"; +export { DummyConnection } from "./DummyConnection"; // sqlite export { SqliteConnection } from "./sqlite/SqliteConnection"; diff --git a/app/src/index.ts b/app/src/index.ts index 3a7b4d1..19e6fd0 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -130,6 +130,7 @@ export { BaseIntrospector, Connection, customIntrospector, + DummyConnection, type FieldSpec, type IndexSpec, type DbFunctions, diff --git a/app/src/modules/ModuleHelper.ts b/app/src/modules/ModuleHelper.ts index 652f076..71031cc 100644 --- a/app/src/modules/ModuleHelper.ts +++ b/app/src/modules/ModuleHelper.ts @@ -116,12 +116,14 @@ export class ModuleHelper { async throwUnlessGranted( permission: Permission | string, - c: { context: ModuleBuildContextMcpContext; request: Request }, + c: { context: ModuleBuildContextMcpContext; raw?: unknown }, ) { invariant(c.context.app, "app is not available in mcp context"); - invariant(c.request instanceof Request, "request is not available in mcp context"); + invariant(c.raw instanceof Request, "request is not available in mcp context"); - const user = await c.context.app.module.auth.authenticator.resolveAuthFromRequest(c.request); + const user = await c.context.app.module.auth.authenticator.resolveAuthFromRequest( + c.raw as Request, + ); if (!this.ctx.guard.granted(permission, user)) { throw new Exception( diff --git a/app/src/modules/mcp/$object.ts b/app/src/modules/mcp/$object.ts index 788947a..f52c723 100644 --- a/app/src/modules/mcp/$object.ts +++ b/app/src/modules/mcp/$object.ts @@ -6,6 +6,7 @@ import { type McpSchema, type SchemaWithMcpOptions, } from "./McpSchemaHelper"; +import type { Module } from "modules/Module"; export interface ObjectToolSchemaOptions extends s.IObjectOptions, SchemaWithMcpOptions {} @@ -80,13 +81,36 @@ export class ObjectToolSchema< ...this.mcp.getToolOptions("update"), inputSchema: s.strictObject({ full: s.boolean({ default: false }).optional(), - value: s - .strictObject(schema.properties as any) - .partial() as unknown as s.ObjectSchema, + return_config: s + .boolean({ + default: false, + description: "If the new configuration should be returned", + }) + .optional(), + value: s.strictObject(schema.properties as {}).partial(), }), }, async (params, ctx: AppToolHandlerCtx) => { - return ctx.json(params); + const { full, value, return_config } = params; + const [module_name] = node.instancePath; + + if (full) { + await ctx.context.app.mutateConfig(module_name as any).set(value); + } else { + await ctx.context.app.mutateConfig(module_name as any).patch("", value); + } + + let config: any = undefined; + if (return_config) { + const configs = ctx.context.app.toJSON(); + config = getPath(configs, node.instancePath); + } + + return ctx.json({ + success: true, + module: module_name, + config, + }); }, ); } diff --git a/app/src/modules/permissions/index.ts b/app/src/modules/permissions/index.ts index cc54754..b6fbead 100644 --- a/app/src/modules/permissions/index.ts +++ b/app/src/modules/permissions/index.ts @@ -7,3 +7,4 @@ export const configReadSecrets = new Permission("system.config.read.secrets"); export const configWrite = new Permission("system.config.write"); export const schemaRead = new Permission("system.schema.read"); export const build = new Permission("system.build"); +export const mcp = new Permission("system.mcp"); diff --git a/app/src/modules/server/AppServer.ts b/app/src/modules/server/AppServer.ts index 2927c42..31a76c5 100644 --- a/app/src/modules/server/AppServer.ts +++ b/app/src/modules/server/AppServer.ts @@ -22,6 +22,9 @@ export const serverConfigSchema = $object( }), allow_credentials: s.boolean({ default: true }), }), + mcp: s.strictObject({ + enabled: s.boolean({ default: false }), + }), }, { description: "Server configuration", diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index c863317..dcc5d14 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -14,6 +14,7 @@ import { InvalidSchemaError, openAPISpecs, mcpTool, + mcp as mcpMiddleware, } from "bknd/utils"; import type { Context, Hono } from "hono"; import { Controller } from "modules/Controller"; @@ -27,6 +28,7 @@ import { import * as SystemPermissions from "modules/permissions"; import { getVersion } from "core/env"; import type { Module } from "modules/Module"; +import { getSystemMcp } from "./system-mcp"; export type ConfigUpdate = { success: true; @@ -52,6 +54,32 @@ export class SystemController extends Controller { return this.app.modules.ctx(); } + register(app: App) { + app.server.route("/api/system", this.getController()); + + if (!this.app.modules.get("server").config.mcp.enabled) { + return; + } + + this.registerMcp(); + + const mcpServer = getSystemMcp(app); + + app.server.use( + mcpMiddleware({ + server: mcpServer, + sessionsEnabled: true, + debug: { + logLevel: "debug", + explainEndpoint: true, + }, + endpoint: { + path: "/mcp", + }, + }), + ); + } + private registerConfigController(client: Hono): void { const { permission } = this.middlewares; // don't add auth again, it's already added in getController diff --git a/app/src/modules/server/system-mcp.ts b/app/src/modules/server/system-mcp.ts new file mode 100644 index 0000000..f8308e0 --- /dev/null +++ b/app/src/modules/server/system-mcp.ts @@ -0,0 +1,36 @@ +import type { App } from "App"; +import { mcpSchemaSymbol, type McpSchema } from "modules/mcp"; +import { getMcpServer, isObject, s, McpServer } from "bknd/utils"; +import { getVersion } from "core/env"; + +export function getSystemMcp(app: App) { + const middlewareServer = getMcpServer(app.server); + + const appConfig = app.modules.configs(); + const { version, ...appSchema } = app.getSchema(); + + const schema = s.strictObject(appSchema); + + const nodes = [...schema.walk({ data: appConfig })].filter( + (n) => isObject(n.schema) && mcpSchemaSymbol in n.schema, + ) as s.Node[]; + const tools = [ + // tools from hono routes + ...middlewareServer.tools, + // tools added from ctx + ...app.modules.ctx().mcp.tools, + // tools from app schema + ...nodes.flatMap((n) => n.schema.getTools(n)), + ]; + const resources = [...middlewareServer.resources, ...app.modules.ctx().mcp.resources]; + + return new McpServer( + { + name: "bknd", + version: getVersion(), + }, + { app, ctx: () => app.modules.ctx() }, + tools, + resources, + ); +} diff --git a/app/tsconfig.json b/app/tsconfig.json index a40d88a..55264d4 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -32,12 +32,8 @@ "*": ["./src/*"], "bknd": ["./src/index.ts"], "bknd/utils": ["./src/core/utils/index.ts"], - "bknd/core": ["./src/core/index.ts"], "bknd/adapter": ["./src/adapter/index.ts"], - "bknd/client": ["./src/ui/client/index.ts"], - "bknd/data": ["./src/data/index.ts"], - "bknd/media": ["./src/media/index.ts"], - "bknd/auth": ["./src/auth/index.ts"] + "bknd/client": ["./src/ui/client/index.ts"] } }, "include": [ diff --git a/docs/app/[[...slug]]/page.tsx b/docs/app/[[...slug]]/page.tsx index 920e47f..42ab098 100644 --- a/docs/app/[[...slug]]/page.tsx +++ b/docs/app/[[...slug]]/page.tsx @@ -13,10 +13,12 @@ export default async function Page(props: { if (!page) notFound(); const MDXContent = page.data.body; + // in case a page exports a custom toc + const toc = (page.data as any).custom_toc ?? page.data.toc; return ( s.toLowerCase().replace(/ /g, "-"); +export const indent = (s: string, indent = 2) => s.replace(/^/gm, " ".repeat(indent)); + +export function McpTool({ tool }: { tool: ReturnType }) { + return ( +
+ + {tool.name} + +

{tool.description}

+ + +
+ ); +} + +export function JsonSchemaTypeTable({ schema }: { schema: JSONSchemaDefinition }) { + const properties = schema.properties ?? {}; + const required = schema.required ?? []; + const getTypeDescription = (value: any) => + JSON.stringify( + { + ...value, + $target: undefined, + }, + null, + 2, + ); + + return Object.keys(properties).length > 0 ? ( + [ + key, + { + description: value.description, + typeDescription: ( + + ), + type: value.type, + default: value.default ? JSON.stringify(value.default) : undefined, + required: required.includes(key), + }, + ]), + )} + /> + ) : null; +} diff --git a/docs/content/docs/(documentation)/meta.json b/docs/content/docs/(documentation)/meta.json index 896c981..a476c95 100644 --- a/docs/content/docs/(documentation)/meta.json +++ b/docs/content/docs/(documentation)/meta.json @@ -24,7 +24,7 @@ "./integration/(runtimes)/", "---Modules---", "./modules/overview", - "./modules/server", + "./modules/server/", "./modules/data", "./modules/auth", "./modules/media", diff --git a/docs/content/docs/(documentation)/modules/server/mcp.mdx b/docs/content/docs/(documentation)/modules/server/mcp.mdx new file mode 100644 index 0000000..437c024 --- /dev/null +++ b/docs/content/docs/(documentation)/modules/server/mcp.mdx @@ -0,0 +1,361 @@ +--- +title: "MCP" +description: "Built-in full featured MCP server." +tags: ["documentation"] +--- +import { JsonSchemaTypeTable } from '@/components/McpTool'; + +## Tools + + +### data_sync + +Sync database schema + + + +### data_entity_fn_count + +Count entities + + + +### data_entity_fn_exists + +Check if entity exists + + + +### data_entity_read_one + +Read one + + + +### data_entity_read_many + +Query entities + + + +### data_entity_insert + +Insert one or many + + + +### data_entity_update_many + +Update many + + + +### data_entity_update_one + +Update one + + + +### data_entity_delete_one + +Delete one + + + +### data_entity_delete_many + +Delete many + + + +### data_entity_info + +Retrieve entity info + + + +### auth_me + +Get the current user + + + +### auth_strategies + +Get the available authentication strategies + + + +### system_config + +Get the config for a module + + + +### system_build + +Build the app + + + +### system_ping + +Ping the server + + + +### system_info + +Get the server info + + + +### auth_user_create + +Create a new user + + + +### auth_user_token + +Get a user token + + + +### auth_user_password_change + +Change a user's password + + + +### auth_user_password_test + +Test a user's password + + + +### config_server_get + +Get Server configuration + + + +### config_server_update + +Update Server configuration + + + +### config_data_get + + + + + +### config_data_update + + + + + +### config_data_entities_get + + + + + +### config_data_entities_add + + + + + +### config_data_entities_update + + + + + +### config_data_entities_remove + + + + + +### config_data_relations_get + + + + + +### config_data_relations_add + + + + + +### config_data_relations_update + + + + + +### config_data_relations_remove + + + + + +### config_data_indices_get + + + + + +### config_data_indices_add + + + + + +### config_data_indices_update + + + + + +### config_data_indices_remove + + + + + +### config_auth_get + + + + + +### config_auth_update + + + + + +### config_auth_strategies_get + + + + + +### config_auth_strategies_add + + + + + +### config_auth_strategies_update + + + + + +### config_auth_strategies_remove + + + + + +### config_auth_roles_get + + + + + +### config_auth_roles_add + + + + + +### config_auth_roles_update + + + + + +### config_auth_roles_remove + + + + + +### config_media_get + + + + + +### config_media_update + + + + + +### config_media_adapter_get + + + + + +### config_media_adapter_update + + + + + + +## Resources + + + +### data_entities + +Retrieve all entities + + + +### data_relations + +Retrieve all relations + + + +### data_indices + +Retrieve all indices + + + +### system_config + + + + + +### system_config_module + + + + + +### system_schema + + + + + +### system_schema_module + + + diff --git a/docs/content/docs/(documentation)/modules/server/meta.json b/docs/content/docs/(documentation)/modules/server/meta.json new file mode 100644 index 0000000..29f47f5 --- /dev/null +++ b/docs/content/docs/(documentation)/modules/server/meta.json @@ -0,0 +1,3 @@ +{ + "pages": ["overview", "mcp"] +} diff --git a/docs/content/docs/(documentation)/modules/server.mdx b/docs/content/docs/(documentation)/modules/server/overview.mdx similarity index 100% rename from docs/content/docs/(documentation)/modules/server.mdx rename to docs/content/docs/(documentation)/modules/server/overview.mdx diff --git a/docs/mcp.json b/docs/mcp.json new file mode 100644 index 0000000..0ee9e83 --- /dev/null +++ b/docs/mcp.json @@ -0,0 +1,3047 @@ +{ + "tools": [ + { + "name": "data_sync", + "description": "Sync database schema", + "inputSchema": { + "type": "object", + "properties": { + "force": { + "type": "boolean", + "$target": "query" + }, + "drop": { + "type": "boolean", + "$target": "query" + } + } + } + }, + { + "name": "data_entity_fn_count", + "description": "Count entities", + "inputSchema": { + "type": "object", + "required": [ + "entity" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "json": { + "examples": [ + { + "attribute": { + "$eq": 1 + } + } + ], + "default": {}, + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": {} + } + ], + "$target": "json" + } + } + } + }, + { + "name": "data_entity_fn_exists", + "description": "Check if entity exists", + "inputSchema": { + "type": "object", + "required": [ + "entity" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "json": { + "examples": [ + { + "attribute": { + "$eq": 1 + } + } + ], + "default": {}, + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": {} + } + ], + "$target": "json" + } + } + } + }, + { + "name": "data_entity_read_one", + "description": "Read one", + "inputSchema": { + "type": "object", + "required": [ + "entity", + "id" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "id": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "$target": "param" + }, + "offset": { + "type": "number", + "default": 0, + "$target": "query" + }, + "sort": { + "type": "string", + "default": "id", + "$target": "query" + }, + "select": { + "type": "array", + "$target": "query", + "items": { + "type": "string" + } + } + } + } + }, + { + "name": "data_entity_read_many", + "description": "Query entities", + "inputSchema": { + "type": "object", + "required": [ + "entity" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "limit": { + "type": "number", + "default": 10, + "$target": "json" + }, + "offset": { + "type": "number", + "default": 0, + "$target": "json" + }, + "sort": { + "type": "string", + "default": "id", + "$target": "json" + }, + "where": { + "examples": [ + { + "attribute": { + "$eq": 1 + } + } + ], + "default": {}, + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": {} + } + ], + "$target": "json" + }, + "select": { + "type": "array", + "$target": "json", + "items": { + "type": "string" + } + }, + "join": { + "type": "array", + "$target": "json", + "items": { + "type": "string" + } + }, + "with": { + "type": "object", + "$target": "json", + "properties": {} + } + } + } + }, + { + "name": "data_entity_insert", + "description": "Insert one or many", + "inputSchema": { + "type": "object", + "required": [ + "entity", + "json" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "json": { + "anyOf": [ + { + "type": "object", + "properties": {} + }, + { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + ], + "$target": "json" + } + } + } + }, + { + "name": "data_entity_update_many", + "description": "Update many", + "inputSchema": { + "type": "object", + "required": [ + "entity", + "update" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "update": { + "type": "object", + "$target": "json", + "properties": {} + }, + "where": { + "examples": [ + { + "attribute": { + "$eq": 1 + } + } + ], + "default": {}, + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": {} + } + ], + "$target": "json" + } + } + } + }, + { + "name": "data_entity_update_one", + "description": "Update one", + "inputSchema": { + "type": "object", + "required": [ + "entity", + "id" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "id": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "$target": "param" + } + } + } + }, + { + "name": "data_entity_delete_one", + "description": "Delete one", + "inputSchema": { + "type": "object", + "required": [ + "entity", + "id" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "id": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "$target": "param" + } + } + } + }, + { + "name": "data_entity_delete_many", + "description": "Delete many", + "inputSchema": { + "type": "object", + "required": [ + "entity" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + }, + "json": { + "examples": [ + { + "attribute": { + "$eq": 1 + } + } + ], + "default": {}, + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": {} + } + ], + "$target": "json" + } + } + } + }, + { + "name": "data_entity_info", + "description": "Retrieve entity info", + "inputSchema": { + "type": "object", + "required": [ + "entity" + ], + "properties": { + "entity": { + "type": "string", + "enum": [ + "users", + "media" + ], + "$target": "param" + } + } + } + }, + { + "name": "auth_me", + "description": "Get the current user", + "inputSchema": { + "type": "object" + } + }, + { + "name": "auth_strategies", + "description": "Get the available authentication strategies", + "inputSchema": { + "type": "object", + "properties": { + "include_disabled": { + "type": "boolean", + "$target": "query" + } + } + } + }, + { + "name": "system_config", + "description": "Get the config for a module", + "inputSchema": { + "type": "object", + "properties": { + "module": { + "type": "string", + "enum": [ + "server", + "data", + "auth", + "media", + "flows" + ], + "$target": "param" + }, + "secrets": { + "type": "boolean", + "$target": "query" + } + } + } + }, + { + "name": "system_build", + "description": "Build the app", + "inputSchema": { + "type": "object", + "properties": { + "sync": { + "type": "boolean", + "$target": "query" + }, + "fetch": { + "type": "boolean", + "$target": "query" + } + } + } + }, + { + "name": "system_ping", + "description": "Ping the server", + "inputSchema": { + "type": "object" + } + }, + { + "name": "system_info", + "description": "Get the server info", + "inputSchema": { + "type": "object" + } + }, + { + "name": "auth_user_create", + "description": "Create a new user", + "inputSchema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 8 + }, + "role": { + "type": "string", + "enum": [] + } + }, + "required": [ + "email", + "password" + ] + } + }, + { + "name": "auth_user_token", + "description": "Get a user token", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "email": { + "type": "string", + "format": "email" + } + } + } + }, + { + "name": "auth_user_password_change", + "description": "Change a user's password", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 8 + } + }, + "required": [ + "password" + ] + } + }, + { + "name": "auth_user_password_test", + "description": "Test a user's password", + "inputSchema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 8 + } + }, + "required": [ + "email", + "password" + ] + } + }, + { + "name": "config_server_get", + "description": "Get Server configuration", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "Path to the property to get, e.g. `key.subkey`", + "pattern": "^[a-zA-Z0-9_.]{0,}$" + }, + "depth": { + "type": "number", + "description": "Limit the depth of the response" + }, + "secrets": { + "type": "boolean", + "description": "Include secrets in the response config", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_server_update", + "description": "Update Server configuration", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "full": { + "type": "boolean", + "default": false + }, + "return_config": { + "type": "boolean", + "description": "If the new configuration should be returned", + "default": false + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "cors": { + "type": "object", + "additionalProperties": false, + "properties": { + "origin": { + "type": "string", + "default": "*" + }, + "allow_methods": { + "type": "array", + "default": [ + "GET", + "POST", + "PATCH", + "PUT", + "DELETE" + ], + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "GET", + "POST", + "PATCH", + "PUT", + "DELETE" + ] + } + }, + "allow_headers": { + "type": "array", + "default": [ + "Content-Type", + "Content-Length", + "Authorization", + "Accept" + ], + "items": { + "type": "string" + } + }, + "allow_credentials": { + "type": "boolean", + "default": true + } + } + }, + "mcp": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "default": false + } + } + } + } + } + }, + "required": [ + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "Path to the property to get, e.g. `key.subkey`", + "pattern": "^[a-zA-Z0-9_.]{0,}$" + }, + "depth": { + "type": "number", + "description": "Limit the depth of the response" + }, + "secrets": { + "type": "boolean", + "description": "Include secrets in the response config", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "full": { + "type": "boolean", + "default": false + }, + "return_config": { + "type": "boolean", + "description": "If the new configuration should be returned", + "default": false + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "basepath": { + "type": "string", + "default": "/api/data" + }, + "default_primary_format": { + "type": "string", + "enum": [ + "integer", + "uuid" + ], + "default": "integer" + } + } + } + }, + "required": [ + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_entities_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to get" + }, + "secrets": { + "type": "boolean", + "description": "(optional) include secrets in the response config", + "default": false + }, + "schema": { + "type": "boolean", + "description": "(optional) include the schema in the response", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_entities_add", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to add" + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "regular", + "system", + "generated" + ], + "default": "regular" + }, + "config": { + "type": "object", + "default": {}, + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "name_singular": { + "type": "string" + }, + "description": { + "type": "string" + }, + "sort_field": { + "type": "string", + "default": "id" + }, + "sort_dir": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + }, + "primary_format": { + "type": "string", + "enum": [ + "integer", + "uuid" + ] + } + } + }, + "fields": { + "type": "object", + "default": {}, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "title": "primary", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "primary" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "format": { + "type": "string", + "enum": [ + "integer", + "uuid" + ], + "default": "integer" + }, + "required": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "text", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "text" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "pattern": { + "type": "string" + }, + "html_config": { + "type": "object", + "properties": { + "element": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "title": "String" + }, + { + "type": "number", + "title": "Number" + } + ] + } + } + } + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "number", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "number" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "multipleOf": { + "type": "number" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "boolean", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "boolean" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "date", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "date" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "date", + "datetime", + "week" + ], + "default": "date" + }, + "timezone": { + "type": "string" + }, + "min_date": { + "type": "string" + }, + "max_date": { + "type": "string" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "enum", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "enum" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "string" + }, + "options": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "strings" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "type", + "values" + ] + }, + { + "type": "object", + "properties": { + "type": { + "const": "objects" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "type", + "values" + ] + } + ] + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "json", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "json" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": {}, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "jsonschema", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "jsonschema" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "schema": { + "type": "object" + }, + "ui_schema": { + "type": "object" + }, + "default_from_schema": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "relation", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "relation" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "reference": { + "type": "string" + }, + "target": { + "type": "string" + }, + "target_field": { + "type": "string", + "default": "id" + }, + "target_field_type": { + "type": "string", + "enum": [ + "text", + "integer" + ], + "default": "integer" + }, + "on_delete": { + "type": "string", + "enum": [ + "cascade", + "set null", + "set default", + "restrict", + "no action" + ], + "default": "set null" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + }, + "required": [ + "reference", + "target" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "title": "media", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "media" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "entity": { + "type": "string" + }, + "min_items": { + "type": "number" + }, + "max_items": { + "type": "number" + }, + "mime_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + }, + "required": [ + "type" + ] + } + ] + } + } + } + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_entities_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to update" + }, + "value": { + "type": "object", + "properties": {} + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_entities_remove", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to remove" + } + }, + "required": [ + "key" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_relations_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to get" + }, + "secrets": { + "type": "boolean", + "description": "(optional) include secrets in the response config", + "default": false + }, + "schema": { + "type": "boolean", + "description": "(optional) include the schema in the response", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_relations_add", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to add" + }, + "value": { + "anyOf": [ + { + "type": "object", + "title": "1:1", + "additionalProperties": false, + "properties": { + "type": { + "const": "1:1" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "sourceCardinality": { + "type": "number" + }, + "with_limit": { + "type": "number", + "default": 5 + }, + "fieldConfig": { + "type": "object", + "properties": { + "label": { + "type": "string" + } + }, + "required": [ + "label" + ] + }, + "mappedBy": { + "type": "string" + }, + "inversedBy": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + }, + "required": [ + "type", + "source", + "target" + ] + }, + { + "type": "object", + "title": "n:1", + "additionalProperties": false, + "properties": { + "type": { + "const": "n:1" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "sourceCardinality": { + "type": "number" + }, + "with_limit": { + "type": "number", + "default": 5 + }, + "fieldConfig": { + "type": "object", + "properties": { + "label": { + "type": "string" + } + }, + "required": [ + "label" + ] + }, + "mappedBy": { + "type": "string" + }, + "inversedBy": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + }, + "required": [ + "type", + "source", + "target" + ] + }, + { + "type": "object", + "title": "m:n", + "additionalProperties": false, + "properties": { + "type": { + "const": "m:n" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "connectionTable": { + "type": "string" + }, + "connectionTableMappedName": { + "type": "string" + }, + "mappedBy": { + "type": "string" + }, + "inversedBy": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + }, + "required": [ + "type", + "source", + "target" + ] + }, + { + "type": "object", + "title": "poly", + "additionalProperties": false, + "properties": { + "type": { + "const": "poly" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "targetCardinality": { + "type": "number" + }, + "mappedBy": { + "type": "string" + }, + "inversedBy": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + }, + "required": [ + "type", + "source", + "target" + ] + } + ] + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_relations_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to update" + }, + "value": { + "type": "object", + "properties": {} + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_relations_remove", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to remove" + } + }, + "required": [ + "key" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_indices_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to get" + }, + "secrets": { + "type": "boolean", + "description": "(optional) include secrets in the response config", + "default": false + }, + "schema": { + "type": "boolean", + "description": "(optional) include the schema in the response", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_indices_add", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to add" + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "entity": { + "type": "string" + }, + "fields": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "unique": { + "type": "boolean", + "default": false + } + }, + "required": [ + "entity", + "fields" + ] + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_indices_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to update" + }, + "value": { + "type": "object", + "properties": {} + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_data_indices_remove", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to remove" + } + }, + "required": [ + "key" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_get", + "title": "Get Authentication", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "Path to the property to get, e.g. `key.subkey`", + "pattern": "^[a-zA-Z0-9_.]{0,}$" + }, + "depth": { + "type": "number", + "description": "Limit the depth of the response" + }, + "secrets": { + "type": "boolean", + "description": "Include secrets in the response config", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_update", + "title": "Update Authentication", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "full": { + "type": "boolean", + "default": false + }, + "return_config": { + "type": "boolean", + "description": "If the new configuration should be returned", + "default": false + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "basepath": { + "type": "string", + "default": "/api/auth" + }, + "entity_name": { + "type": "string", + "default": "users" + }, + "allow_register": { + "type": "boolean", + "default": true + }, + "jwt": { + "type": "object", + "default": {}, + "additionalProperties": false, + "properties": { + "secret": { + "type": "string", + "default": "" + }, + "alg": { + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512" + ], + "default": "HS256" + }, + "expires": { + "type": "number" + }, + "issuer": { + "type": "string" + }, + "fields": { + "type": "array", + "default": [ + "id", + "email", + "role" + ], + "items": { + "type": "string" + } + } + } + }, + "cookie": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "default": "/" + }, + "sameSite": { + "type": "string", + "enum": [ + "strict", + "lax", + "none" + ], + "default": "lax" + }, + "secure": { + "type": "boolean", + "default": true + }, + "httpOnly": { + "type": "boolean", + "default": true + }, + "expires": { + "type": "number", + "default": 604800 + }, + "partitioned": { + "type": "boolean", + "default": false + }, + "renew": { + "type": "boolean", + "default": true + }, + "pathSuccess": { + "type": "string", + "default": "/" + }, + "pathLoggedOut": { + "type": "string", + "default": "/" + } + } + }, + "guard": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false + } + } + } + } + } + }, + "required": [ + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_strategies_get", + "title": "Get Strategies", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to get" + }, + "secrets": { + "type": "boolean", + "description": "(optional) include secrets in the response config", + "default": false + }, + "schema": { + "type": "boolean", + "description": "(optional) include the schema in the response", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_strategies_add", + "title": "Add Strategies", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to add" + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "config": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "config" + ] + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_strategies_update", + "title": "Update Strategies", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to update" + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "config": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "config" + ] + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_strategies_remove", + "title": "Get Strategies", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to remove" + } + }, + "required": [ + "key" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_roles_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to get" + }, + "secrets": { + "type": "boolean", + "description": "(optional) include secrets in the response config", + "default": false + }, + "schema": { + "type": "boolean", + "description": "(optional) include the schema in the response", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_roles_add", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to add" + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_default": { + "type": "boolean" + }, + "implicit_allow": { + "type": "boolean" + } + } + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_roles_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to update" + }, + "value": { + "type": "object", + "properties": {} + } + }, + "required": [ + "key", + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_auth_roles_remove", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "description": "key to remove" + } + }, + "required": [ + "key" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_media_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "Path to the property to get, e.g. `key.subkey`", + "pattern": "^[a-zA-Z0-9_.]{0,}$" + }, + "depth": { + "type": "number", + "description": "Limit the depth of the response" + }, + "secrets": { + "type": "boolean", + "description": "Include secrets in the response config", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_media_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "full": { + "type": "boolean", + "default": false + }, + "return_config": { + "type": "boolean", + "description": "If the new configuration should be returned", + "default": false + }, + "value": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "basepath": { + "type": "string", + "default": "/api/media" + }, + "entity_name": { + "type": "string", + "default": "media" + }, + "storage": { + "type": "object", + "default": {}, + "additionalProperties": false, + "properties": { + "body_max_size": { + "type": "number", + "description": "Max size of the body in bytes. Leave blank for unlimited." + } + } + } + } + } + }, + "required": [ + "value" + ] + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_media_adapter_get", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "secrets": { + "type": "boolean", + "description": "Include secrets in the response config", + "default": false + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + }, + { + "name": "config_media_adapter_update", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "full": { + "type": "boolean", + "default": false + }, + "value": { + "anyOf": [ + { + "type": "object", + "title": "AWS S3", + "description": "AWS S3 or compatible storage", + "additionalProperties": false, + "properties": { + "type": { + "const": "s3" + }, + "config": { + "type": "object", + "title": "AWS S3", + "description": "AWS S3 or compatible storage", + "properties": { + "access_key": { + "type": "string" + }, + "secret_access_key": { + "type": "string" + }, + "url": { + "type": "string", + "description": "URL to S3 compatible endpoint without trailing slash", + "examples": [ + "https://{account_id}.r2.cloudflarestorage.com/{bucket}", + "https://{bucket}.s3.{region}.amazonaws.com" + ], + "pattern": "^https?://(?:.*)?[^/.]+$" + } + }, + "required": [ + "access_key", + "secret_access_key", + "url" + ] + } + }, + "required": [ + "type", + "config" + ] + }, + { + "type": "object", + "title": "Cloudinary", + "description": "Cloudinary media storage", + "additionalProperties": false, + "properties": { + "type": { + "const": "cloudinary" + }, + "config": { + "type": "object", + "title": "Cloudinary", + "description": "Cloudinary media storage", + "properties": { + "cloud_name": { + "type": "string" + }, + "api_key": { + "type": "string" + }, + "api_secret": { + "type": "string" + }, + "upload_preset": { + "type": "string" + } + }, + "required": [ + "cloud_name", + "api_key", + "api_secret" + ] + } + }, + "required": [ + "type", + "config" + ] + }, + { + "type": "object", + "title": "Local", + "description": "Local file system storage", + "additionalProperties": false, + "properties": { + "type": { + "const": "local" + }, + "config": { + "type": "object", + "title": "Local", + "description": "Local file system storage", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "default": "./" + } + }, + "required": [ + "path" + ] + } + }, + "required": [ + "type", + "config" + ] + } + ] + } + } + }, + "annotations": { + "destructiveHint": true, + "idempotentHint": true + } + } + ], + "resources": [ + { + "uri": "bknd://data/entities", + "name": "data_entities", + "title": "Entities", + "description": "Retrieve all entities" + }, + { + "uri": "bknd://data/relations", + "name": "data_relations", + "title": "Relations", + "description": "Retrieve all relations" + }, + { + "uri": "bknd://data/indices", + "name": "data_indices", + "title": "Indices", + "description": "Retrieve all indices" + }, + { + "uri": "bknd://system/config", + "name": "system_config" + }, + { + "uriTemplate": "bknd://system/config/{module}", + "name": "system_config_module" + }, + { + "uri": "bknd://system/schema", + "name": "system_schema" + }, + { + "uriTemplate": "bknd://system/schema/{module}", + "name": "system_schema_module" + } + ] +} \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index 9db3d56..4d64549 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -6,7 +6,6 @@ "packages": { "": { "name": "bknd-docs", - "version": "0.0.0", "hasInstallScript": true, "dependencies": { "@iconify/react": "^6.0.0", @@ -36,6 +35,7 @@ "eslint": "^8", "eslint-config-next": "15.3.5", "fumadocs-docgen": "^2.1.0", + "jsonv-ts": "^0.7.0", "postcss": "^8.5.6", "rimraf": "^6.0.1", "tailwindcss": "^4.1.11", @@ -6816,6 +6816,17 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hono": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.9.0.tgz", + "integrity": "sha512-JAUc4Sqi3lhby2imRL/67LMcJFKiCu7ZKghM7iwvltVZzxEC5bVJCsAa4NTnSfmWGb+N2eOVtFE586R+K3fejA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -7525,6 +7536,19 @@ "node": ">=0.10.0" } }, + "node_modules/jsonv-ts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/jsonv-ts/-/jsonv-ts-0.7.0.tgz", + "integrity": "sha512-zN5/KMs1WOs+0IbYiZF7mVku4dum8LKP9xv8VqgVm+PBz5VZuU1V8iLQhI991ogUbhGHHlOCwqxnxQUuvCPbQA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "hono": "*" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", diff --git a/docs/package.json b/docs/package.json index 486c83c..02d15bf 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,9 +4,10 @@ "scripts": { "dev": "next dev", "dev:turbo": "next dev --turbo", - "build": "bun generate:openapi && next build", + "build": "bun generate:openapi && bun generate:mcp && next build", "start": "next start", "generate:openapi": "bun scripts/generate-openapi.mjs", + "generate:mcp": "bun scripts/generate-mcp.ts", "postinstall": "fumadocs-mdx", "preview": "npm run build && wrangler dev", "cf:preview": "wrangler dev", @@ -42,6 +43,7 @@ "eslint": "^8", "eslint-config-next": "15.3.5", "fumadocs-docgen": "^2.1.0", + "jsonv-ts": "^0.7.0", "postcss": "^8.5.6", "rimraf": "^6.0.1", "tailwindcss": "^4.1.11", diff --git a/docs/scripts/generate-mcp.ts b/docs/scripts/generate-mcp.ts new file mode 100644 index 0000000..34e798b --- /dev/null +++ b/docs/scripts/generate-mcp.ts @@ -0,0 +1,65 @@ +import type { Tool, Resource } from "jsonv-ts/mcp"; +import { rimraf } from "rimraf"; + +const config = { + mcpConfig: "./mcp.json", + outFile: "./content/docs/(documentation)/modules/server/mcp.mdx", +}; + +async function generate() { + console.info("Generating MCP documentation..."); + await cleanup(); + const mcpConfig = await Bun.file(config.mcpConfig).json(); + const document = await generateDocument(mcpConfig); + await Bun.write(config.outFile, document); + console.info("MCP documentation generated."); +} + +async function generateDocument({ + tools, + resources, +}: { + tools: ReturnType[]; + resources: ReturnType[]; +}) { + return `--- +title: "MCP" +description: "Built-in full featured MCP server." +tags: ["documentation"] +--- +import { JsonSchemaTypeTable } from '@/components/McpTool'; + +## Tools + +${tools + .map( + (t) => ` +### ${t.name} + +${t.description ?? ""} + +`, + ) + .join("\n")} + + +## Resources + +${resources + .map( + (r) => ` + +### ${r.name} + +${r.description ?? ""} +`, + ) + .join("\n")} +`; +} + +async function cleanup() { + await rimraf(config.outFile); +} + +void generate();