Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sync/dep-parity.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$note": "Upstream's pinned versions for every dependency both trees declare in the SAME section, snapshotted at `cursor`. The sync ports deltas, which is correct per commit and lets a one-time divergence become permanent: once a version is off upstream's line, every later `chore(deps)` batch skips it, because those ports bump only where this fork already matched upstream's pre-image. `prettier` sat at ^3.8.4 against upstream's ^3.9.6 for that reason, through four ported batches, until this file was written. Section-aware on purpose: 18 packages are declared on both sides but in different sections — the whole `@tiptap/*` family is a peer `^3` upstream and a dependency `^3.29.2` here, and `ai` is a peer there and a devDependency here. Those are structural divergences, not drift, and comparing a peer range against a dependency range says nothing. They are absent from this file by construction rather than by omission. Guarded by `test/utils/dep-parity.spec.ts`. Refresh with `node .sync/dep-parity.mjs <path-to-nuxt-ui-mirror> [cursor]`, which preserves `exceptions`.",
"cursor": "bb55709fee1da7752895e27f44384d3c5c130425",
"cursor": "9ef3ee394339ea477e34e122f22f0c5b039ee37d",
"manifests": {
"package.json": {
"dependencies": {
Expand Down
84 changes: 84 additions & 0 deletions .sync/log/9ef3ee394339ea477e34e122f22f0c5b039ee37d.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Skip: docs: improve agent readiness

**Upstream:** `9ef3ee394339ea477e34e122f22f0c5b039ee37d` (nuxt/ui, #6878)
**Decision:** skip by maintainer decision — but it exposed a fork defect, fixed here

## Upstream change

19 files, +1387/−139: docs-site infrastructure for machine readers. Content
negotiation so `/docs/**` can return markdown to an `Accept: text/markdown`
client (`markdownNegotiation.ts`, 318 lines), a generated OpenAPI document
(`openapi.ts`, 668 lines) served at `/openapi.json`, a nitro error handler,
a markdown middleware, and refinements to `.well-known/api-catalog`,
`.well-known/mcp/server-card.json`, the `raw/` routes, sitemaps and llms.txt.

## Why skipped

Not applicable as a body of work: this fork's agent surface is its **own**, and
larger. `docs/server/mcp/` carries 27 files — 12 tools, 4 resources, 2 prompts —
against upstream's smaller set, wired through `@nuxtjs/mcp-toolkit` at `/mcp/`.
Porting upstream's plumbing would mean re-deciding architecture we already have.
The OpenAPI half in particular describes upstream's `/api/**` surface, which is
not ours.

Skipped on maintainer decision, with the queue advancing past it.

## What it did expose

Reading the commit turned up a real inconsistency **of our own**, unrelated to
whether the port lands.

`docs/nuxt.config.ts` advertises six discovery endpoints from `/` in an RFC 8288
`Link` header. Four are prerendered and present in the static build. **Two never
existed at all:**

```
</.well-known/api-catalog>; rel="api-catalog"
</.well-known/mcp/server-card.json>; rel="service-desc"
```

Nothing served them — no route under `docs/server/routes/`, nothing in
`.output/public/.well-known/` but `skills`. `raw/index.md.get.ts:24` names the
server-card URL in a comment as though it existed. The headers were evidently
adopted without the routes behind them.

Severity, stated honestly: the site is published with `nuxt generate` to GitHub
Pages, where `routeRules.headers` are not emitted at all — so the advertisement
is inert in production today, and this was never a live 404. It is still a
config claiming endpoints the site does not have.

## The fix

Both routes now exist and are **prerendered**, so they land in the static build
alongside the four endpoints the same header already names — the fork's own
convention is "advertise what we prerender", and these two were simply the
outliers.

- **`api-catalog.get.ts`** — an RFC 9727 linkset naming only what this site
actually serves. Upstream anchors an `openapi.json` in theirs; we have none, so
it is absent by construction rather than by oversight. `service-doc` points at
`/docs/getting-started/` because this fork has **no** MCP docs page — upstream
links `/docs/getting-started/ai/mcp`, and `nuxt.config.ts:710` already carries
that path commented out for the same reason.
- **`server-card.json.get.ts`** — reads its tool, resource and prompt lists from
the running server via `listMcpDefinitions`, so the card cannot drift from what
`docs/server/mcp/` registers.

That listing goes through the `#nuxt-mcp-toolkit/tools.mjs` virtual module, which
**does not resolve during prerender** — the first attempt failed the build with
`Package import specifier "#nuxt-mcp-toolkit/tools.mjs" is not defined`. Since
the card has to exist on static hosting, the import is attempted and degrades:
served by a real server it carries the full catalogue, prerendered it omits those
three arrays. A card without them is still valid — a client discovers the
catalogue by calling `tools/list` on the endpoint, which is the normal MCP flow.

## Verify

`docs:generate` goes from 1262 to **1264** routes, and both files land with
correct absolute URLs (`serverInfo.version` resolves to `2.12.0` from
`pkg.version`). Every href the catalogue names was then resolved against the
build: all present, the single exception being `/mcp/` itself, which is a live
server route rather than a static file. All six endpoints the `Link` header
advertises now exist.

`lint` · `typecheck` · `test` · `docs:generate` — green.
8 changes: 7 additions & 1 deletion .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"upstream": "nuxt/ui",
"branch": "v4",
"cursor": "bb55709fee1da7752895e27f44384d3c5c130425",
"cursor": "9ef3ee394339ea477e34e122f22f0c5b039ee37d",
"_cursor_note": "cursor = last upstream commit ported into b24ui. The sync is manual by decision: one commit at a time, oldest-first, each with a `.sync/log/<sha>.md` journal and an entry in `processed`. There is no dispatcher, no porter workflow and no kill-switch — `.sync/PORTING.md` is the whole procedure. `processed` is maintained per port (backfilled #68-#72 on 2026-06-09).",
"processed": {
"2799fa6f2b25ce3eb15e050f3ef7c57d0d9a2fdb": {
Expand Down Expand Up @@ -1649,6 +1649,12 @@
"b24ui_sha": "95d730458ea144269a07b4250a6cacc7692ad23c",
"decision": "no-op",
"summary": "docs(icons): document what `clientBundle.scan` covers (nuxt/ui #6880) — NO-OP: the mechanism being documented does not exist here. Upstream adds 40 lines to both icons integration pages explaining the limits of @nuxt/icon's client bundle scanner: it reads only .vue/.jsx/.tsx/.md/.mdc/.mdx/.yml/.yaml, matches icon names only as literal strings (so a list of links in a .ts file is missed, as is anything built like `i-lucide-${name}`), how clientBundle.scan.globInclude widens that list by replacing the default, and that runtime-only names cannot be bundled and are fetched from /api/_nuxt_icon under SSR or the Iconify API otherwise. None of that machinery exists in this fork, checked rather than assumed: our icons are @bitrix24/b24icons Vue COMPONENTS, imported by name and bundled by the compiler, so there is no string-literal icon name to scan for and the failure mode the paragraph warns about cannot arise; clientBundle, globInclude and /api/_nuxt_icon appear nowhere in docs/ or src/; @nuxt/icon survives only as a commented-out block at src/module.ts:139 and as prose inside an unrelated Accordion example; and both our icons pages document @bitrix24/b24icons with automatic setup and no scanner to configure. Same call and same reason as 08e75317, which the ledger records as a port of only its non-icon half."
},
"9ef3ee394339ea477e34e122f22f0c5b039ee37d": {
"pr": "pending-merge",
"b24ui_sha": "pending-merge",
"decision": "skip",
"summary": "docs: improve agent readiness (nuxt/ui #6878) — SKIP by maintainer decision, but it exposed a fork defect that is fixed in the same PR. Upstream adds 19 files, +1387/-139 of docs-site infrastructure for machine readers: content negotiation so /docs/** can return markdown to an Accept: text/markdown client (markdownNegotiation.ts, 318 lines), a generated OpenAPI document (openapi.ts, 668 lines) at /openapi.json, a nitro error handler, a markdown middleware, and refinements to the .well-known routes, raw/ routes, sitemaps and llms.txt. Not applicable as a body of work: this fork's agent surface is its own and larger — docs/server/mcp/ carries 27 files (12 tools, 4 resources, 2 prompts) wired through @nuxtjs/mcp-toolkit at /mcp/, and the OpenAPI half describes upstream's /api/** surface, not ours. WHAT IT EXPOSED: docs/nuxt.config.ts advertises six discovery endpoints from / in an RFC 8288 Link header; four are prerendered and present in the static build, but /.well-known/api-catalog and /.well-known/mcp/server-card.json never existed at all — no route under docs/server/routes/, nothing in .output/public/.well-known/ but skills, while raw/index.md.get.ts:24 names the server-card URL in a comment as though it existed. Stated honestly, the severity is limited: the site ships via nuxt generate to GitHub Pages where routeRules.headers are not emitted, so the advertisement is inert in production and this was never a live 404 — but the config still claimed endpoints the site does not have. THE FIX: both routes now exist and are prerendered, landing in the static build alongside the four the same header already names (the fork's convention is 'advertise what we prerender'). api-catalog is an RFC 9727 linkset naming only what this site serves — upstream anchors an openapi.json in theirs and we have none, so it is absent by construction; service-doc points at /docs/getting-started/ because this fork has no MCP docs page, which nuxt.config.ts:710 already reflects with that path commented out. server-card reads its tool/resource/prompt lists from the running server via listMcpDefinitions so the card cannot drift from what docs/server/mcp/ registers — but that listing goes through the #nuxt-mcp-toolkit/tools.mjs virtual module which does not resolve during prerender (the first attempt failed the build with 'Package import specifier not defined'), so the import is attempted and degrades: full catalogue when served by a real server, those three arrays omitted when prerendered. A card without them is still valid since a client discovers the catalogue via tools/list on the endpoint. Verified: docs:generate goes 1262 -> 1264 routes, both files land with correct absolute URLs and serverInfo.version resolving to 2.12.0, and every href the catalogue names was resolved against the build — all present except /mcp/ itself, which is a live server route rather than a static file. All six advertised endpoints now exist."
}
}
}
6 changes: 5 additions & 1 deletion docs/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,11 @@ const pagesService = [
'/api/locales.json',
'/404.html',
'/sitemap.xml',
'/sitemap.md'
'/sitemap.md',
// Advertised from `/` by the agent-discovery `Link` header below; prerendered
// so they exist on static hosting like the other endpoints that header names.
'/.well-known/api-catalog',
'/.well-known/mcp/server-card.json'
]

const extraAllowedHosts = (process?.env.NUXT_ALLOWED_HOSTS?.split(',').map((s: string) => s.trim()).filter(Boolean)) ?? []
Expand Down
49 changes: 49 additions & 0 deletions docs/server/routes/.well-known/api-catalog.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* RFC 9727 api-catalog: a linkset naming the machine-readable descriptions of
* this site, advertised from `/` by the `Link` header in `nuxt.config.ts`.
*
* Only endpoints this site actually serves are listed. Upstream anchors an
* `openapi.json` here too; this fork has none, so it is absent by construction
* rather than by omission.
*/
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const site = `${config.public.canonicalUrl}${config.public.baseUrl}`

const linkset = {
linkset: [
{
'anchor': `${site}/mcp/`,
'service-desc': [
{
href: `${site}/.well-known/mcp/server-card.json`,
type: 'application/json'
}
],
'service-doc': [
{
href: `${site}/docs/getting-started/`,
type: 'text/html'
}
]
},
{
'anchor': `${site}/docs`,
'service-desc': [
{ href: `${site}/llms.txt`, type: 'text/plain' },
{ href: `${site}/llms-full.txt`, type: 'text/plain' },
{ href: `${site}/sitemap.md`, type: 'text/markdown' }
],
'service-doc': [
{
href: `${site}/docs`,
type: 'text/html'
}
]
}
]
}

setResponseHeader(event, 'Content-Type', 'application/linkset+json; charset=utf-8')
return linkset
})
69 changes: 69 additions & 0 deletions docs/server/routes/.well-known/mcp/server-card.json.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* MCP server card, advertised from `/` by the `Link` header in `nuxt.config.ts`
* as `rel="service-desc"`.
*
* The tool, resource and prompt lists are read from the running server so the
* card cannot drift from what `docs/server/mcp/` registers — but that listing
* goes through the `#nuxt-mcp-toolkit/tools.mjs` virtual module, which does not
* resolve during prerender. This site is published statically, so the card must
* still exist there: the listing is attempted and, when it is unavailable, the
* card is emitted without those three arrays rather than failing the build.
*
* A card without them is still valid — a client discovers the catalogue by
* calling `tools/list` on the endpoint below, which is the normal MCP flow.
*/
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const site = `${config.public.canonicalUrl}${config.public.baseUrl}`

let definitions: {
tools: { name: string, description?: string }[]
resources: { name: string, uri: string, description?: string }[]
prompts: { name: string, description?: string }[]
} | undefined

try {
const { listMcpDefinitions } = await import('@nuxtjs/mcp-toolkit/server')
definitions = await listMcpDefinitions({ event }) as typeof definitions
} catch {
// Prerender: the virtual tools module is not resolvable. Fall through.
}

setResponseHeader(event, 'Content-Type', 'application/json; charset=utf-8')

return {
$schema: 'https://modelcontextprotocol.io/schema/server-card/v1',
serverInfo: {
name: 'Bitrix24 UI',
version: config.public.version,
title: 'Bitrix24 UI MCP Server',
description: 'MCP server providing tools, resources and prompts to help AI agents build with Bitrix24 UI — search components and composables, retrieve documentation, fetch component metadata, and list starter templates.',
homepage: site,
documentation: `${site}/docs/getting-started/`,
license: 'MIT',
repository: 'https://github.com/bitrix24/b24ui'
},
endpoints: [
{
type: 'streamable-http',
url: `${site}/mcp/`
}
],
capabilities: {
tools: { listChanged: false },
resources: { listChanged: false, subscribe: false },
prompts: { listChanged: false },
logging: {}
},
...(definitions
? {
tools: definitions.tools.map(tool => ({ name: tool.name, description: tool.description })),
resources: definitions.resources.map(resource => ({ name: resource.name, uri: resource.uri, description: resource.description })),
prompts: definitions.prompts.map(prompt => ({ name: prompt.name, description: prompt.description }))
}
: {}),
authentication: {
required: false
}
}
})