@kywi-software/mcp 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT-PATTERNS.md +58 -2
- package/dist/site/server.d.ts.map +1 -1
- package/dist/site/server.js +2 -0
- package/dist/site/server.js.map +1 -1
- package/dist/site/tools/menus.d.ts +67 -0
- package/dist/site/tools/menus.d.ts.map +1 -0
- package/dist/site/tools/menus.js +85 -0
- package/dist/site/tools/menus.js.map +1 -0
- package/package.json +3 -3
package/AGENT-PATTERNS.md
CHANGED
|
@@ -260,8 +260,8 @@ everywhere.
|
|
|
260
260
|
| `name` | Purpose |
|
|
261
261
|
|---|---|
|
|
262
262
|
| `breadcrumbs` | Trail of ancestor links |
|
|
263
|
-
| `navMenu` | A named menu (`menuSlug`)
|
|
264
|
-
| `navigation` | Site navigation
|
|
263
|
+
| `navMenu` | A named menu (`menuSlug`) in a `variant`: horizontal / mega / vertical / footer |
|
|
264
|
+
| `navigation` | Site navigation in the same four variants, to a depth |
|
|
265
265
|
| `siteMap` | Nested list of the site tree |
|
|
266
266
|
| `searchBox` | Compact search input posting to a results URL |
|
|
267
267
|
| `search` | Full search form (method, label, results URL) |
|
|
@@ -328,6 +328,62 @@ page", "Case study" — so new pages start from a consistent skeleton. Hand-writ
|
|
|
328
328
|
JSX stays fine for genuinely fixed chrome (a bespoke 404, legal boilerplate) —
|
|
329
329
|
but if marketing will ever want to swap a headline, it's a layout page.
|
|
330
330
|
|
|
331
|
+
### Navigation: menus vs. the site tree
|
|
332
|
+
|
|
333
|
+
Two sources feed every nav module, and both resolve to the exact same
|
|
334
|
+
render-ready shape — `navMenu`/`navigation` never know which one produced it:
|
|
335
|
+
|
|
336
|
+
- **Menus** (Menus admin) — an owner-managed, named list of items. Each item
|
|
337
|
+
either links a content node (its href resolves live off that page — publish,
|
|
338
|
+
rename, or move the page and the menu link follows) or carries an external
|
|
339
|
+
URL, plus a label, an optional description (used by `mega` panels) and an
|
|
340
|
+
open-in-new-window flag. Reference one from a `navMenu` module's `menuSlug`
|
|
341
|
+
prop.
|
|
342
|
+
- **The site tree** — every page with `isNav` checked (Site Tree admin), in
|
|
343
|
+
`sortOrder`. A page's nav label is its `menuTitle` when the editor set one,
|
|
344
|
+
else its `title` — so a long page title ("Plans and Pricing for Teams") can
|
|
345
|
+
show a short nav label ("Pricing") without a second, independently-maintained
|
|
346
|
+
copy of the link. `navigation` and `siteMap` modules render this tree, to
|
|
347
|
+
`depth` levels.
|
|
348
|
+
|
|
349
|
+
**NEVER hardcode the site nav in chrome.** A hand-typed `<nav>` with pasted
|
|
350
|
+
hrefs is exactly the failure mode this exists to close: a prior marketing
|
|
351
|
+
build's hardcoded header had 7 of its 13 links quietly 404ing, because nothing
|
|
352
|
+
checked them against the pages that actually existed. A menu item's href is
|
|
353
|
+
derived from its linked page, so it cannot drift the way a pasted URL can; a
|
|
354
|
+
site-tree nav module can't drift either, because it IS the tree. Build the
|
|
355
|
+
header/footer from a `navMenu`/`navigation` module (or, in a scaffolded app's
|
|
356
|
+
own chrome, from `scope.menus`/`scope.nav` directly — see below) and never from
|
|
357
|
+
a list of `<a>` tags an agent typed out.
|
|
358
|
+
|
|
359
|
+
`navMenu`'s `menuSlug` resolves server-side against a real, owner-managed menu.
|
|
360
|
+
Both `navMenu` and `navigation` still accept hand-authored `items` JSON — as
|
|
361
|
+
the fallback when a `menuSlug` names a menu that doesn't (yet) exist, or as a
|
|
362
|
+
deliberate one-off override — but a named menu or the site tree is how a
|
|
363
|
+
*maintained* nav gets built, not hand-typed JSON.
|
|
364
|
+
|
|
365
|
+
**Variants** (`variant` prop, shared by `navMenu` and `navigation`):
|
|
366
|
+
`horizontal` (dropdown panels — a top bar), `mega` (full-width multi-column
|
|
367
|
+
panels with descriptions — reach for this when a top-level item has several
|
|
368
|
+
children worth previewing), `vertical` (nested fly-outs — a sidebar), `footer`
|
|
369
|
+
(grouped columns of links, no interactivity). Every variant is JS-off safe:
|
|
370
|
+
links always render server-side, and below the mobile breakpoint the bar
|
|
371
|
+
collapses behind a native `<details>` hamburger. The interactive variants carry
|
|
372
|
+
`data-kywi-nav` + `data-kywi-nav-variant`; `@kywi-software/js` auto-boots
|
|
373
|
+
hover-intent, keyboard and outside-click handling on any element with
|
|
374
|
+
`data-kywi-nav` — nothing to wire up beyond loading `/kywi.js`.
|
|
375
|
+
|
|
376
|
+
**A scaffolded app's own header/footer follow the same rule.**
|
|
377
|
+
`app/(site)/layout.tsx` renders the `'main'` menu when the site has one,
|
|
378
|
+
falling back to the `isNav` site tree — so a fresh install has a working header
|
|
379
|
+
with zero manual wiring, and an owner who later builds a `'main'` menu in the
|
|
380
|
+
admin overrides it with no code change. The footer renders a `'footer'` menu
|
|
381
|
+
only when one exists (no tree fallback there — an unbuilt footer menu should be
|
|
382
|
+
empty, not suddenly cluttered with the whole site tree). Both render through
|
|
383
|
+
the same shared nav renderer as the `navigation` module, never hand-rolled
|
|
384
|
+
markup, so the header and footer get the identical CSS and
|
|
385
|
+
`@kywi-software/js` enhancement as any nav module placed in the page body.
|
|
386
|
+
|
|
331
387
|
## 6. Forms: always the Forms builder
|
|
332
388
|
|
|
333
389
|
Never hand-code a `<form>`. Build forms in the Forms admin (fields, multi-step,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/site/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/site/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAYjD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CA8BpE"}
|
package/dist/site/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerModuleTypesTool } from '../module-types.js';
|
|
|
7
7
|
import { registerContentTools } from './tools/content.js';
|
|
8
8
|
import { registerLayoutTools } from './tools/layout.js';
|
|
9
9
|
import { registerFeedTools } from './tools/feeds.js';
|
|
10
|
+
import { registerMenuTools } from './tools/menus.js';
|
|
10
11
|
import { registerSearchTools } from './tools/search.js';
|
|
11
12
|
import { registerMediaTools } from './tools/media.js';
|
|
12
13
|
import { registerUserTools } from './tools/users.js';
|
|
@@ -68,6 +69,7 @@ export function createSiteMcpServer(config) {
|
|
|
68
69
|
registerContentTools(server, sdk, config.baseUrl, config.apiKey);
|
|
69
70
|
registerLayoutTools(server, sdk);
|
|
70
71
|
registerFeedTools(server, sdk);
|
|
72
|
+
registerMenuTools(server, sdk);
|
|
71
73
|
registerSearchTools(server, sdk);
|
|
72
74
|
registerMediaTools(server, sdk);
|
|
73
75
|
registerUserTools(server, sdk);
|
package/dist/site/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/site/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAiB,EACjB,GAAiB,EACjB,OAAe;IAEf,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAIpB,uBAAuB,CAAC,CAAA;QAE9B,IAAI,OAAO,IAAK,GAA+B;YAAE,OAAO,CAAC,CAAA;QAEzD,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC1B,MAAM,YAAY,GAAiC,EAAE,CAAA;YACrD,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;gBAC9B,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ;oBACvC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;oBAClC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACjD,CAAC;YAED,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;gBACvC,WAAW,EAAE,gBAAgB,EAAE,CAAC,KAAK,EAAE;gBACvC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;aACpC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBAChB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAA+B,CAAC,CAAA;gBAC/E,OAAO,UAAU,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC,CAAC,CAAA;YAEF,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;gBACrC,WAAW,EAAE,QAAQ,EAAE,CAAC,KAAK,UAAU;gBACvC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;oBACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;oBACjC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;oBAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;iBAC9B,CAAC;aACH,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBAChB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;gBAC/D,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;YAC3B,CAAC,CAAC,CAAA;YAEF,KAAK,IAAI,CAAC,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAA;IACV,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAqB;IACvD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,EACrD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;IAED,MAAM,GAAG,GAAG,aAAa,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CAAA;IAEF,wEAAwE;IACxE,oBAAoB,CAAC,MAAM,CAAC,CAAA;IAC5B,yEAAyE;IACzE,iEAAiE;IACjE,uBAAuB,CAAC,MAAM,CAAC,CAAA;IAC/B,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAChE,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAChC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAChC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/B,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE9B,uFAAuF;IACvF,mCAAmC;IACnC,+CAA+C;IAC/C,iEAAiE;IAEjE,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/site/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { KywiSdkScope } from '@kywi-software/sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Menu item as an agent writes it. Narrower than the SDK's `MenuItemInput`:
|
|
5
|
+
* tools link by `contentSlug` (stable across page moves) or `externalUrl`,
|
|
6
|
+
* never by raw content id.
|
|
7
|
+
*/
|
|
8
|
+
export interface MenuItemInputArg {
|
|
9
|
+
label: string;
|
|
10
|
+
contentSlug?: string | undefined;
|
|
11
|
+
externalUrl?: string | undefined;
|
|
12
|
+
description?: string | undefined;
|
|
13
|
+
openInNewWindow?: boolean | undefined;
|
|
14
|
+
children?: MenuItemInputArg[] | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns a message describing the first item that does not set exactly one of
|
|
18
|
+
* contentSlug / externalUrl, or null when the whole tree is valid. The API
|
|
19
|
+
* enforces this too; failing here names the offending item so an agent can fix
|
|
20
|
+
* it without decoding a 400.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findLinkTargetError(items: MenuItemInputArg[], ancestors?: string[]): string | null;
|
|
23
|
+
export declare function handleListMenus(sdk: KywiSdkScope, args: {
|
|
24
|
+
siteId?: string | undefined;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text";
|
|
28
|
+
text: string;
|
|
29
|
+
}];
|
|
30
|
+
isError?: undefined;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function handleGetMenu(sdk: KywiSdkScope, args: {
|
|
33
|
+
slug: string;
|
|
34
|
+
siteId?: string | undefined;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}];
|
|
40
|
+
isError?: undefined;
|
|
41
|
+
} | {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text";
|
|
44
|
+
text: string;
|
|
45
|
+
}];
|
|
46
|
+
isError: true;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function handleUpsertMenu(sdk: KywiSdkScope, args: {
|
|
49
|
+
slug: string;
|
|
50
|
+
name: string;
|
|
51
|
+
siteId?: string | undefined;
|
|
52
|
+
items: MenuItemInputArg[];
|
|
53
|
+
}): Promise<{
|
|
54
|
+
content: [{
|
|
55
|
+
type: "text";
|
|
56
|
+
text: string;
|
|
57
|
+
}];
|
|
58
|
+
isError?: undefined;
|
|
59
|
+
} | {
|
|
60
|
+
content: [{
|
|
61
|
+
type: "text";
|
|
62
|
+
text: string;
|
|
63
|
+
}];
|
|
64
|
+
isError: true;
|
|
65
|
+
}>;
|
|
66
|
+
export declare function registerMenuTools(server: McpServer, sdk: KywiSdkScope): void;
|
|
67
|
+
//# sourceMappingURL=menus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../../src/site/tools/menus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGtD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EAAE,EACzB,SAAS,GAAE,MAAM,EAAO,GACvB,MAAM,GAAG,IAAI,CAiBf;AAID,wBAAsB,eAAe,CACnC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE;;;;;;GAItC;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE;;;;;;;;;;;;GAOpD;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,gBAAgB,EAAE,CAAA;CAAE;;;;;;;;;;;;GAY7F;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,CAyB5E"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, errorResult } from '../../shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a message describing the first item that does not set exactly one of
|
|
5
|
+
* contentSlug / externalUrl, or null when the whole tree is valid. The API
|
|
6
|
+
* enforces this too; failing here names the offending item so an agent can fix
|
|
7
|
+
* it without decoding a 400.
|
|
8
|
+
*/
|
|
9
|
+
export function findLinkTargetError(items, ancestors = []) {
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const path = [...ancestors, item.label].join(' > ');
|
|
12
|
+
const hasSlug = item.contentSlug !== undefined && item.contentSlug !== '';
|
|
13
|
+
const hasUrl = item.externalUrl !== undefined && item.externalUrl !== '';
|
|
14
|
+
if (hasSlug && hasUrl) {
|
|
15
|
+
return `Menu item "${path}" sets both contentSlug and externalUrl — set exactly one.`;
|
|
16
|
+
}
|
|
17
|
+
if (!hasSlug && !hasUrl) {
|
|
18
|
+
return `Menu item "${path}" sets neither contentSlug nor externalUrl — set exactly one.`;
|
|
19
|
+
}
|
|
20
|
+
if (item.children?.length) {
|
|
21
|
+
const childError = findLinkTargetError(item.children, [...ancestors, item.label]);
|
|
22
|
+
if (childError)
|
|
23
|
+
return childError;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// ─── Exported handler functions (testable without server) ───────────────────
|
|
29
|
+
export async function handleListMenus(sdk, args) {
|
|
30
|
+
const menus = await sdk.menus.list({ siteId: args.siteId });
|
|
31
|
+
return textResult({ menus, total: menus.length });
|
|
32
|
+
}
|
|
33
|
+
export async function handleGetMenu(sdk, args) {
|
|
34
|
+
const menu = await sdk.menus.get(args.slug, { siteId: args.siteId });
|
|
35
|
+
if (!menu) {
|
|
36
|
+
return errorResult(`Menu '${args.slug}' not found`);
|
|
37
|
+
}
|
|
38
|
+
return textResult(menu);
|
|
39
|
+
}
|
|
40
|
+
export async function handleUpsertMenu(sdk, args) {
|
|
41
|
+
const linkError = findLinkTargetError(args.items);
|
|
42
|
+
if (linkError)
|
|
43
|
+
return errorResult(linkError);
|
|
44
|
+
const menu = await sdk.menus.upsert({
|
|
45
|
+
slug: args.slug,
|
|
46
|
+
name: args.name,
|
|
47
|
+
items: args.items,
|
|
48
|
+
...(args.siteId ? { siteId: args.siteId } : {}),
|
|
49
|
+
});
|
|
50
|
+
return textResult(menu);
|
|
51
|
+
}
|
|
52
|
+
// ─── Registration ───────────────────────────────────────────────────────────
|
|
53
|
+
const menuItemSchema = z.lazy(() => z.object({
|
|
54
|
+
label: z.string().describe('Visible link text (always required — it wins over the page title)'),
|
|
55
|
+
contentSlug: z.string().optional().describe('Slug of a page in this site to link to; href resolves live from the page path'),
|
|
56
|
+
externalUrl: z.string().optional().describe('Absolute off-site URL, e.g. https://github.com/…'),
|
|
57
|
+
description: z.string().optional().describe('Supporting copy shown in mega-menu panels'),
|
|
58
|
+
openInNewWindow: z.boolean().optional().describe('Open the link in a new tab'),
|
|
59
|
+
children: z.array(menuItemSchema).optional().describe('Nested sub-items (dropdown / mega-panel column)'),
|
|
60
|
+
}));
|
|
61
|
+
export function registerMenuTools(server, sdk) {
|
|
62
|
+
server.registerTool('list_menus', {
|
|
63
|
+
description: "List the site's named navigation menus (slug + name). Use this to discover which menus exist before editing one.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
siteId: z.string().optional().describe('Site ID (uses default if omitted)'),
|
|
66
|
+
},
|
|
67
|
+
}, async (args) => handleListMenus(sdk, args));
|
|
68
|
+
server.registerTool('get_menu', {
|
|
69
|
+
description: 'Get one menu by slug with its full nested item tree, including items whose linked content is unpublished or deleted (flagged `resolvable: false` / `contentMissing: true`) so broken nav links are visible.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
slug: z.string().describe('Menu slug, e.g. "main" or "footer"'),
|
|
72
|
+
siteId: z.string().optional().describe('Site ID (uses default if omitted)'),
|
|
73
|
+
},
|
|
74
|
+
}, async (args) => handleGetMenu(sdk, args));
|
|
75
|
+
server.registerTool('upsert_menu', {
|
|
76
|
+
description: 'Create or update a navigation menu and replace its entire item tree in one idempotent call. Items are nested; each item links to a page via `contentSlug` (preferred — the href stays correct when the page moves) OR to an off-site address via `externalUrl`. Exactly one of the two is required per item. Re-running with the same input is a no-op.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
slug: z.string().describe('Menu slug, e.g. "main" or "footer"'),
|
|
79
|
+
name: z.string().describe('Human-readable menu name shown in the admin'),
|
|
80
|
+
siteId: z.string().optional().describe('Site ID (uses default if omitted)'),
|
|
81
|
+
items: z.array(menuItemSchema).describe('The menu\'s complete nested item tree — this replaces all existing items'),
|
|
82
|
+
},
|
|
83
|
+
}, async (args) => handleUpsertMenu(sdk, args));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=menus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menus.js","sourceRoot":"","sources":["../../../src/site/tools/menus.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kywi-software/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Model Context Protocol servers for Kywi CMS — a site server (content/nav/media over the REST API) and a developer server (kywi.config.ts scaffolding and mutation).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
49
49
|
"zod": "^3.25.0",
|
|
50
|
-
"@kywi-software/core": "0.
|
|
51
|
-
"@kywi-software/sdk": "0.
|
|
50
|
+
"@kywi-software/core": "0.7.0",
|
|
51
|
+
"@kywi-software/sdk": "0.7.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^20.0.0",
|