Skip to content

Commit 5c773e3

Browse files
committed
docs(openapi): type the error model, schemas and describe every operation
Adds Nitro's JSON error payload as a schema and references it from the API error responses, types the linkset, MCP server card, skills index and JSON-RPC payloads, gives all 22 operations a description, and declares `security: []` so agents know no credentials are needed. Validates clean under `redocly lint`.
1 parent 6fbbbbe commit 5c773e3

1 file changed

Lines changed: 192 additions & 9 deletions

File tree

docs/server/utils/openapi.ts

Lines changed: 192 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ import { SITE_URL } from './markdownNegotiation'
1919
// described in prose and through the two response media types instead.
2020
const 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+
2234
function 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

Comments
 (0)