@@ -19,6 +19,18 @@ import { SITE_URL } from './markdownNegotiation'
1919// described in prose and through the two response media types instead.
2020const MARKDOWN_DESCRIPTION = 'Every documentation page is available as Markdown. Append `.md` to the URL, or send `Accept: text/markdown` on the HTML URL. Known AI agent user agents receive Markdown by default.'
2121
22+ /** Nitro's JSON error payload, returned by every `/api/**` failure. */
23+ function jsonError ( description : string ) {
24+ return {
25+ description,
26+ content : {
27+ 'application/json' : {
28+ schema : { $ref : '#/components/schemas/Error' }
29+ }
30+ }
31+ }
32+ }
33+
2234function json ( schemaRef : string , description : string ) {
2335 return {
2436 description,
@@ -72,6 +84,9 @@ export function createOpenApiDocument(options: { version: string, url?: string }
7284 }
7385 } ,
7486 servers : [ { url, description : 'Production' } ] ,
87+ // Everything here is public and read-only: an empty requirement tells
88+ // agents no credentials are needed, rather than leaving them to guess.
89+ security : [ ] ,
7590 tags : [
7691 { name : 'Documentation' , description : 'Documentation pages as Markdown.' } ,
7792 { name : 'Discovery' , description : 'Machine-readable indexes and agent metadata.' } ,
@@ -174,6 +189,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
174189 operationId : 'getSitemapXml' ,
175190 tags : [ 'Discovery' ] ,
176191 summary : 'XML sitemap' ,
192+ description : 'Every indexable page, in the sitemaps.org XML format. `/sitemap.md` is the same index as Markdown links.' ,
177193 responses : {
178194 200 : {
179195 description : 'Sitemap in the sitemaps.org XML format.' ,
@@ -215,6 +231,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
215231 operationId : 'getOpenApiDocument' ,
216232 tags : [ 'Discovery' ] ,
217233 summary : 'This OpenAPI document' ,
234+ description : 'This document. It is regenerated on every deploy, so `info.version` tracks the published `@nuxt/ui` release.' ,
218235 responses : {
219236 200 : {
220237 description : 'OpenAPI 3.1 document.' ,
@@ -232,7 +249,11 @@ export function createOpenApiDocument(options: { version: string, url?: string }
232249 responses : {
233250 200 : {
234251 description : 'Linkset document.' ,
235- content : { 'application/linkset+json' : { schema : { type : 'object' } } }
252+ content : {
253+ 'application/linkset+json' : {
254+ schema : { $ref : '#/components/schemas/Linkset' }
255+ }
256+ }
236257 }
237258 }
238259 }
@@ -246,7 +267,11 @@ export function createOpenApiDocument(options: { version: string, url?: string }
246267 responses : {
247268 200 : {
248269 description : 'MCP server card.' ,
249- content : { 'application/json' : { schema : { type : 'object' } } }
270+ content : {
271+ 'application/json' : {
272+ schema : { $ref : '#/components/schemas/McpServerCard' }
273+ }
274+ }
250275 }
251276 }
252277 }
@@ -260,7 +285,11 @@ export function createOpenApiDocument(options: { version: string, url?: string }
260285 responses : {
261286 200 : {
262287 description : 'Skills index.' ,
263- content : { 'application/json' : { schema : { type : 'object' } } }
288+ content : {
289+ 'application/json' : {
290+ schema : { $ref : '#/components/schemas/SkillsIndex' }
291+ }
292+ }
264293 }
265294 }
266295 }
@@ -275,19 +304,19 @@ export function createOpenApiDocument(options: { version: string, url?: string }
275304 required : true ,
276305 content : {
277306 'application/json' : {
278- schema : { type : 'object' , description : 'JSON-RPC 2.0 request. ' }
307+ schema : { $ref : '#/components/schemas/JsonRpcRequest ' }
279308 }
280309 }
281310 } ,
282311 responses : {
283312 200 : {
284- description : 'JSON-RPC 2.0 response, or an SSE stream.' ,
313+ description : 'JSON-RPC 2.0 response, or an SSE stream of them .' ,
285314 content : {
286- 'application/json' : { schema : { type : 'object ' } } ,
315+ 'application/json' : { schema : { $ref : '#/components/schemas/JsonRpcResponse ' } } ,
287316 'text/event-stream' : { schema : { type : 'string' } }
288317 }
289318 } ,
290- 400 : { description : 'Unknown MCP tool requested through `x-mcp-tools`.' }
319+ 400 : jsonError ( 'Unknown MCP tool requested through `x-mcp-tools`.' )
291320 }
292321 }
293322 } ,
@@ -296,6 +325,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
296325 operationId : 'getNavigation' ,
297326 tags : [ 'Content' ] ,
298327 summary : 'Documentation navigation tree' ,
328+ description : 'The documentation navigation tree as rendered in the sidebar: nested items carrying the page title, path, framework and category.' ,
299329 responses : {
300330 200 : {
301331 description : 'Nested navigation items.' ,
@@ -313,6 +343,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
313343 operationId : 'getModuleStats' ,
314344 tags : [ 'Content' ] ,
315345 summary : 'Module stats, team and contributors' ,
346+ description : 'npm downloads and GitHub stars for `@nuxt/ui`, plus the team and contributor lists shown on the homepage. Cached for an hour.' ,
316347 responses : { 200 : json ( 'Module' , 'Download and star counts, team members and contributors.' ) }
317348 }
318349 } ,
@@ -321,6 +352,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
321352 operationId : 'getComponentExample' ,
322353 tags : [ 'Content' ] ,
323354 summary : 'Source of a documentation example component' ,
355+ description : 'The single file component behind an example on a documentation page. Names are listed by the `list-examples` MCP tool and accepted in PascalCase, camelCase or kebab-case, with an optional `.json` suffix.' ,
324356 parameters : [
325357 {
326358 name : 'component' ,
@@ -333,7 +365,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
333365 ] ,
334366 responses : {
335367 200 : json ( 'ComponentExample' , 'Source code of the example component.' ) ,
336- 404 : { description : 'Example not found.' }
368+ 404 : jsonError ( 'No example component with that name.' )
337369 }
338370 }
339371 } ,
@@ -342,6 +374,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
342374 operationId : 'getCountries' ,
343375 tags : [ 'Data' ] ,
344376 summary : 'Countries' ,
377+ description : 'Countries with their ISO 3166-1 alpha-2 code and flag emoji, the dataset behind the country select examples.' ,
345378 responses : {
346379 200 : {
347380 description : 'Countries with their ISO 3166-1 alpha-2 code and flag.' ,
@@ -359,6 +392,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
359392 operationId : 'getPhoneCodes' ,
360393 tags : [ 'Data' ] ,
361394 summary : 'Phone dial codes' ,
395+ description : 'Countries with their dial code and phone number mask, the dataset behind the phone input examples.' ,
362396 responses : {
363397 200 : {
364398 description : 'Countries with their dial code and phone number mask.' ,
@@ -376,6 +410,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
376410 operationId : 'getLocales' ,
377411 tags : [ 'Data' ] ,
378412 summary : 'Locales' ,
413+ description : 'Every locale Nuxt UI ships a translation for, mapped to its flag emoji.' ,
379414 responses : {
380415 200 : {
381416 description : 'Map of locale tag to flag emoji, for example `{ "fr-FR": "🇫🇷" }`.' ,
@@ -454,7 +489,7 @@ export function createOpenApiDocument(options: { version: string, url?: string }
454489 }
455490 }
456491 } ,
457- 400 : { description : 'The `path` query parameter is missing.' }
492+ 400 : jsonError ( 'The `path` query parameter is missing.' )
458493 }
459494 }
460495 }
@@ -560,6 +595,154 @@ export function createOpenApiDocument(options: { version: string, url?: string }
560595 } ,
561596 required : [ 'sha' , 'date' , 'message' ]
562597 } ,
598+ Linkset : {
599+ type : 'object' ,
600+ description : 'RFC 9727 linkset. Each entry anchors a resource and points at its description and documentation.' ,
601+ properties : {
602+ linkset : {
603+ type : 'array' ,
604+ items : {
605+ type : 'object' ,
606+ properties : {
607+ 'anchor' : { type : 'string' , format : 'uri' } ,
608+ 'service-desc' : { $ref : '#/components/schemas/LinksetTargets' } ,
609+ 'service-doc' : { $ref : '#/components/schemas/LinksetTargets' }
610+ } ,
611+ required : [ 'anchor' ]
612+ }
613+ }
614+ } ,
615+ required : [ 'linkset' ]
616+ } ,
617+ LinksetTargets : {
618+ type : 'array' ,
619+ items : {
620+ type : 'object' ,
621+ properties : {
622+ href : { type : 'string' , format : 'uri' } ,
623+ type : { type : 'string' , description : 'Media type of the target.' }
624+ } ,
625+ required : [ 'href' ]
626+ }
627+ } ,
628+ McpServerCard : {
629+ type : 'object' ,
630+ description : 'Describes the MCP server, following the Model Context Protocol server card schema.' ,
631+ properties : {
632+ $schema : { type : 'string' , format : 'uri' } ,
633+ serverInfo : {
634+ type : 'object' ,
635+ properties : {
636+ name : { type : 'string' } ,
637+ version : { type : 'string' } ,
638+ title : { type : 'string' } ,
639+ description : { type : 'string' } ,
640+ homepage : { type : 'string' , format : 'uri' } ,
641+ documentation : { type : 'string' , format : 'uri' } ,
642+ license : { type : 'string' } ,
643+ repository : { type : 'string' , format : 'uri' }
644+ } ,
645+ required : [ 'name' , 'version' ]
646+ } ,
647+ endpoints : {
648+ type : 'array' ,
649+ items : {
650+ type : 'object' ,
651+ properties : {
652+ type : { type : 'string' , example : 'streamable-http' } ,
653+ url : { type : 'string' , format : 'uri' }
654+ } ,
655+ required : [ 'type' , 'url' ]
656+ }
657+ } ,
658+ capabilities : { type : 'object' , additionalProperties : true } ,
659+ tools : { $ref : '#/components/schemas/McpDefinitions' } ,
660+ resources : { $ref : '#/components/schemas/McpDefinitions' } ,
661+ prompts : { $ref : '#/components/schemas/McpDefinitions' } ,
662+ authentication : {
663+ type : 'object' ,
664+ properties : { required : { type : 'boolean' } }
665+ }
666+ } ,
667+ required : [ 'serverInfo' , 'endpoints' ]
668+ } ,
669+ McpDefinitions : {
670+ type : 'array' ,
671+ items : {
672+ type : 'object' ,
673+ properties : {
674+ name : { type : 'string' } ,
675+ description : { type : 'string' } ,
676+ uri : { type : 'string' , description : 'Resources only.' }
677+ } ,
678+ required : [ 'name' ]
679+ }
680+ } ,
681+ SkillsIndex : {
682+ type : 'object' ,
683+ description : 'Agent skills published by this site, served under `/.well-known/skills/{name}/`.' ,
684+ properties : {
685+ skills : {
686+ type : 'array' ,
687+ items : {
688+ type : 'object' ,
689+ properties : {
690+ name : { type : 'string' } ,
691+ description : { type : 'string' } ,
692+ files : {
693+ type : 'array' ,
694+ description : 'Paths relative to the skill directory.' ,
695+ items : { type : 'string' }
696+ }
697+ } ,
698+ required : [ 'name' , 'description' , 'files' ]
699+ }
700+ }
701+ } ,
702+ required : [ 'skills' ]
703+ } ,
704+ JsonRpcRequest : {
705+ type : 'object' ,
706+ description : 'JSON-RPC 2.0 request. Use an MCP client rather than building these by hand.' ,
707+ properties : {
708+ jsonrpc : { type : 'string' , const : '2.0' } ,
709+ id : { oneOf : [ { type : 'string' } , { type : 'integer' } ] } ,
710+ method : { type : 'string' , example : 'tools/call' } ,
711+ params : { type : 'object' , additionalProperties : true }
712+ } ,
713+ required : [ 'jsonrpc' , 'method' ]
714+ } ,
715+ JsonRpcResponse : {
716+ type : 'object' ,
717+ description : 'JSON-RPC 2.0 response. A tool that fails answers with `result.isError` set rather than an `error` member.' ,
718+ properties : {
719+ jsonrpc : { type : 'string' , const : '2.0' } ,
720+ id : { oneOf : [ { type : 'string' } , { type : 'integer' } ] } ,
721+ result : { type : 'object' , additionalProperties : true } ,
722+ error : {
723+ type : 'object' ,
724+ properties : {
725+ code : { type : 'integer' } ,
726+ message : { type : 'string' }
727+ } ,
728+ required : [ 'code' , 'message' ]
729+ }
730+ } ,
731+ required : [ 'jsonrpc' ]
732+ } ,
733+ Error : {
734+ type : 'object' ,
735+ description : 'Error payload returned by the JSON endpoints. Documentation pages answer errors as Markdown instead, and browsers get the HTML error page.' ,
736+ properties : {
737+ error : { type : 'boolean' , const : true } ,
738+ url : { type : 'string' , description : 'The requested URL.' } ,
739+ statusCode : { type : 'integer' , example : 404 } ,
740+ statusMessage : { type : 'string' , description : 'Machine-readable reason phrase.' , example : 'Example not found!' } ,
741+ message : { type : 'string' , description : 'Human-readable message.' , example : 'Example not found!' } ,
742+ data : { type : 'object' , description : 'Extra context, when the endpoint provides any.' , additionalProperties : true }
743+ } ,
744+ required : [ 'error' , 'statusCode' , 'statusMessage' , 'message' ]
745+ } ,
563746 GitHubObject : {
564747 type : 'object' ,
565748 description : 'Object as returned by the GitHub REST API, passed through unchanged.' ,
0 commit comments