@docsector/docsector-reader 1.3.1 β 1.5.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/README.md +149 -0
- package/bin/docsector.js +33 -1
- package/package.json +1 -1
- package/src/index.js +34 -0
- package/src/quasar.factory.js +272 -13
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ Transform Markdown content into beautiful, navigable documentation sites β wit
|
|
|
29
29
|
- πΊοΈ **Sitemap Generation** β Automatic `sitemap.xml` generation at build time with all page URLs (requires `siteUrl` in config)
|
|
30
30
|
- π€ **AI-Friendly robots.txt** β Scaffold includes a `robots.txt` explicitly allowing 23 AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, etc.)
|
|
31
31
|
- π§ **Content Signals** β Optional `Content-Signal` directive for declaring AI usage policy (`ai-train`, `search`, `ai-input`) in `robots.txt`
|
|
32
|
+
- π§© **Agent Skills Discovery Index** β Optional `/.well-known/agent-skills/index.json` with RFC v0.2.0 schema and SHA-256 digests
|
|
33
|
+
- πͺͺ **MCP Server Card** β Optional `/.well-known/mcp/server-card.json` for MCP server discovery before connection
|
|
32
34
|
- π **Homepage Link Headers** β Auto-generated `Link` response headers for agent discovery (`api-catalog`, `service-doc`, `service-desc`, `describedby`) per RFC 8288 / RFC 9727
|
|
33
35
|
- π **MCP Server** β Auto-generated [MCP](https://modelcontextprotocol.io) server at `/mcp` for AI assistant integration (Claude Desktop, VS Code, etc.)
|
|
34
36
|
- π **llms.txt / llms-full.txt** β Auto-generated [llms.txt](https://llmstxt.org) index and full-content file for LLM discovery (requires `siteUrl` in config)
|
|
@@ -140,6 +142,69 @@ curl -X POST http://localhost:8788/mcp \
|
|
|
140
142
|
|
|
141
143
|
---
|
|
142
144
|
|
|
145
|
+
## πͺͺ MCP Server Card Discovery
|
|
146
|
+
|
|
147
|
+
Docsector Reader can publish an MCP Server Card at:
|
|
148
|
+
|
|
149
|
+
- `/.well-known/mcp/server-card.json`
|
|
150
|
+
|
|
151
|
+
This supports pre-connection MCP discovery, exposing:
|
|
152
|
+
|
|
153
|
+
- `serverInfo` (`name`, `version`)
|
|
154
|
+
- MCP transport endpoint (defaults to `/mcp`)
|
|
155
|
+
- `capabilities` for tools/resources/prompts
|
|
156
|
+
|
|
157
|
+
When MCP is enabled, tool capabilities are derived from the generated server:
|
|
158
|
+
|
|
159
|
+
- `search_{toolSuffix}`
|
|
160
|
+
- `get_page_{toolSuffix}`
|
|
161
|
+
|
|
162
|
+
### Configure
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
export default {
|
|
166
|
+
// ...other config
|
|
167
|
+
|
|
168
|
+
mcp: {
|
|
169
|
+
serverName: 'my-docs',
|
|
170
|
+
toolSuffix: 'my_docs'
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
mcpServerCard: {
|
|
174
|
+
enabled: true,
|
|
175
|
+
path: '/.well-known/mcp/server-card.json',
|
|
176
|
+
transportEndpoint: '/mcp',
|
|
177
|
+
transportType: 'streamable-http',
|
|
178
|
+
protocolVersion: '2025-03-26',
|
|
179
|
+
capabilities: {
|
|
180
|
+
tools: { supported: true },
|
|
181
|
+
resources: { supported: false },
|
|
182
|
+
prompts: { supported: false }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Validate
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx docsector build
|
|
192
|
+
cat dist/spa/.well-known/mcp/server-card.json
|
|
193
|
+
cat dist/spa/_headers
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
External validation:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
curl -X POST https://isitagentready.com/api/scan \
|
|
200
|
+
-H 'Content-Type: application/json' \
|
|
201
|
+
-d '{"url":"https://YOUR-SITE.com"}'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Check `checks.discovery.mcpServerCard.status` equals `"pass"`.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
143
208
|
## οΏ½ llms.txt (LLM Discovery)
|
|
144
209
|
|
|
145
210
|
Docsector Reader automatically generates [llms.txt](https://llmstxt.org) files at build time when `siteUrl` is configured (same requirement as sitemap.xml).
|
|
@@ -333,6 +398,68 @@ Check `checks.botAccessControl.contentSignals.status` equals `"pass"`.
|
|
|
333
398
|
|
|
334
399
|
---
|
|
335
400
|
|
|
401
|
+
## π§© Agent Skills Discovery Index
|
|
402
|
+
|
|
403
|
+
Docsector Reader can publish a discovery index at:
|
|
404
|
+
|
|
405
|
+
- `/.well-known/agent-skills/index.json`
|
|
406
|
+
|
|
407
|
+
The generated payload follows Agent Skills Discovery RFC v0.2.0 and includes:
|
|
408
|
+
|
|
409
|
+
- `$schema`
|
|
410
|
+
- `skills[]` entries with `name`, `type`, `description`, `url`, `digest`
|
|
411
|
+
|
|
412
|
+
When `digest` is omitted in config, Docsector computes it automatically from the referenced local artifact and writes it as:
|
|
413
|
+
|
|
414
|
+
- `sha256:{hex}`
|
|
415
|
+
|
|
416
|
+
### Configure
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
export default {
|
|
420
|
+
// ...other config
|
|
421
|
+
|
|
422
|
+
agentSkills: {
|
|
423
|
+
enabled: true,
|
|
424
|
+
path: '/.well-known/agent-skills/index.json',
|
|
425
|
+
schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
|
|
426
|
+
skills: [
|
|
427
|
+
{
|
|
428
|
+
name: 'my-docs-mcp',
|
|
429
|
+
type: 'skill-md',
|
|
430
|
+
description: 'Search and fetch docs pages via MCP.',
|
|
431
|
+
url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Notes:
|
|
439
|
+
|
|
440
|
+
- `name` must be lowercase alphanumeric plus hyphens.
|
|
441
|
+
- `type` must be `skill-md` or `archive`.
|
|
442
|
+
- `url` should point to a locally published artifact when auto-digest is used.
|
|
443
|
+
|
|
444
|
+
### Validate
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
npx docsector build
|
|
448
|
+
cat dist/spa/.well-known/agent-skills/index.json
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
External validation:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
curl -X POST https://isitagentready.com/api/scan \
|
|
455
|
+
-H 'Content-Type: application/json' \
|
|
456
|
+
-d '{"url":"https://YOUR-SITE.com"}'
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Check `checks.discovery.agentSkills.status` equals `"pass"`.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
336
463
|
## οΏ½π Quick Start
|
|
337
464
|
|
|
338
465
|
### π¦ Install
|
|
@@ -487,6 +614,14 @@ export default {
|
|
|
487
614
|
agentFallback: true
|
|
488
615
|
},
|
|
489
616
|
|
|
617
|
+
mcpServerCard: {
|
|
618
|
+
enabled: true,
|
|
619
|
+
path: '/.well-known/mcp/server-card.json',
|
|
620
|
+
transportEndpoint: '/mcp',
|
|
621
|
+
transportType: 'streamable-http',
|
|
622
|
+
protocolVersion: '2025-03-26'
|
|
623
|
+
},
|
|
624
|
+
|
|
490
625
|
contentSignals: {
|
|
491
626
|
enabled: true,
|
|
492
627
|
aiTrain: 'yes',
|
|
@@ -496,6 +631,20 @@ export default {
|
|
|
496
631
|
applyToAllBlocks: false
|
|
497
632
|
},
|
|
498
633
|
|
|
634
|
+
agentSkills: {
|
|
635
|
+
enabled: true,
|
|
636
|
+
path: '/.well-known/agent-skills/index.json',
|
|
637
|
+
schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
|
|
638
|
+
skills: [
|
|
639
|
+
{
|
|
640
|
+
name: 'my-docs-mcp',
|
|
641
|
+
type: 'skill-md',
|
|
642
|
+
description: 'Search and fetch docs pages via MCP.',
|
|
643
|
+
url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
|
|
644
|
+
}
|
|
645
|
+
]
|
|
646
|
+
},
|
|
647
|
+
|
|
499
648
|
languages: [
|
|
500
649
|
{ image: '/images/flags/united-states-of-america.png', label: 'English (US)', value: 'en-US' },
|
|
501
650
|
{ image: '/images/flags/brazil.png', label: 'PortuguΓͺs (BR)', value: 'pt-BR' }
|
package/bin/docsector.js
CHANGED
|
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
|
|
|
23
23
|
const args = process.argv.slice(2)
|
|
24
24
|
const command = args[0]
|
|
25
25
|
|
|
26
|
-
const VERSION = '1.
|
|
26
|
+
const VERSION = '1.5.0'
|
|
27
27
|
|
|
28
28
|
const HELP = `
|
|
29
29
|
Docsector Reader v${VERSION}
|
|
@@ -133,6 +133,21 @@ export default {
|
|
|
133
133
|
// toolSuffix: 'my_docs'
|
|
134
134
|
// },
|
|
135
135
|
|
|
136
|
+
// @ MCP Server Card discovery (optional)
|
|
137
|
+
// Publishes /.well-known/mcp/server-card.json for pre-connection discovery.
|
|
138
|
+
// mcpServerCard: {
|
|
139
|
+
// enabled: true,
|
|
140
|
+
// path: '/.well-known/mcp/server-card.json',
|
|
141
|
+
// transportEndpoint: '/mcp',
|
|
142
|
+
// transportType: 'streamable-http',
|
|
143
|
+
// protocolVersion: '2025-03-26',
|
|
144
|
+
// capabilities: {
|
|
145
|
+
// tools: { supported: true },
|
|
146
|
+
// resources: { supported: false },
|
|
147
|
+
// prompts: { supported: false }
|
|
148
|
+
// }
|
|
149
|
+
// },
|
|
150
|
+
|
|
136
151
|
// @ Homepage Link headers for agent discovery (optional)
|
|
137
152
|
// linkHeaders: {
|
|
138
153
|
// enabled: true,
|
|
@@ -181,6 +196,23 @@ export default {
|
|
|
181
196
|
// applyToAllBlocks: false
|
|
182
197
|
// },
|
|
183
198
|
|
|
199
|
+
// @ Agent Skills discovery index (optional)
|
|
200
|
+
// Publishes /.well-known/agent-skills/index.json (RFC v0.2.0)
|
|
201
|
+
// and computes sha256 digests from local artifacts.
|
|
202
|
+
// agentSkills: {
|
|
203
|
+
// enabled: true,
|
|
204
|
+
// path: '/.well-known/agent-skills/index.json',
|
|
205
|
+
// schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
|
|
206
|
+
// skills: [
|
|
207
|
+
// {
|
|
208
|
+
// name: 'my-docs-mcp',
|
|
209
|
+
// type: 'skill-md',
|
|
210
|
+
// description: 'Search and read docs pages through MCP.',
|
|
211
|
+
// url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
|
|
212
|
+
// }
|
|
213
|
+
// ]
|
|
214
|
+
// },
|
|
215
|
+
|
|
184
216
|
// @ Languages
|
|
185
217
|
languages: [
|
|
186
218
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
|
|
5
5
|
"productName": "Docsector Reader",
|
|
6
6
|
"author": "Rodrigo de Araujo Vieira",
|
package/src/index.js
CHANGED
|
@@ -73,6 +73,20 @@
|
|
|
73
73
|
* @param {'yes'|'no'|boolean} [config.contentSignals.aiInput='yes'] - Permission for AI input/inference-time consumption
|
|
74
74
|
* @param {string} [config.contentSignals.userAgent='*'] - Target User-agent block for directive injection
|
|
75
75
|
* @param {boolean} [config.contentSignals.applyToAllBlocks=false] - When true, applies directive to every User-agent block
|
|
76
|
+
* @param {Object} [config.agentSkills] - Agent Skills discovery index settings
|
|
77
|
+
* @param {boolean} [config.agentSkills.enabled=false] - Enables generation of Agent Skills discovery index
|
|
78
|
+
* @param {string} [config.agentSkills.path='/.well-known/agent-skills/index.json'] - Output URI path for Agent Skills index
|
|
79
|
+
* @param {string} [config.agentSkills.schema='https://schemas.agentskills.io/discovery/0.2.0/schema.json'] - JSON Schema identifier for index payload
|
|
80
|
+
* @param {Array<{name:string,type:'skill-md'|'archive',description:string,url:string,digest?:string}>} [config.agentSkills.skills=[]] - Skills to publish in discovery index
|
|
81
|
+
* @param {Object} [config.mcpServerCard] - MCP Server Card discovery settings
|
|
82
|
+
* @param {boolean} [config.mcpServerCard.enabled=false] - Enables generation of MCP Server Card discovery document
|
|
83
|
+
* @param {string} [config.mcpServerCard.path='/.well-known/mcp/server-card.json'] - Output URI path for MCP Server Card
|
|
84
|
+
* @param {string} [config.mcpServerCard.transportEndpoint='/mcp'] - MCP transport endpoint exposed by the server
|
|
85
|
+
* @param {string} [config.mcpServerCard.transportType='streamable-http'] - Transport type label for discovery metadata
|
|
86
|
+
* @param {string} [config.mcpServerCard.protocolVersion='2025-03-26'] - Protocol version advertised by the server card
|
|
87
|
+
* @param {Object} [config.mcpServerCard.capabilities] - Optional capability overrides for tools/resources/prompts
|
|
88
|
+
* @param {Array<Object>} [config.mcpServerCard.remotes=[]] - Optional additional remotes to include in the server card
|
|
89
|
+
* @param {Object} [config.mcpServerCard.metadata] - Optional additional metadata merged into the server card payload
|
|
76
90
|
* @returns {Object} Resolved Docsector configuration
|
|
77
91
|
*/
|
|
78
92
|
export function createDocsector (config = {}) {
|
|
@@ -156,6 +170,26 @@ export function createDocsector (config = {}) {
|
|
|
156
170
|
userAgent: '*',
|
|
157
171
|
applyToAllBlocks: false,
|
|
158
172
|
...config.contentSignals
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
agentSkills: {
|
|
176
|
+
enabled: false,
|
|
177
|
+
path: '/.well-known/agent-skills/index.json',
|
|
178
|
+
schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
|
|
179
|
+
skills: [],
|
|
180
|
+
...config.agentSkills
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
mcpServerCard: {
|
|
184
|
+
enabled: false,
|
|
185
|
+
path: '/.well-known/mcp/server-card.json',
|
|
186
|
+
transportEndpoint: '/mcp',
|
|
187
|
+
transportType: 'streamable-http',
|
|
188
|
+
protocolVersion: '2025-03-26',
|
|
189
|
+
capabilities: null,
|
|
190
|
+
remotes: [],
|
|
191
|
+
metadata: null,
|
|
192
|
+
...config.mcpServerCard
|
|
159
193
|
}
|
|
160
194
|
}
|
|
161
195
|
}
|
package/src/quasar.factory.js
CHANGED
|
@@ -755,6 +755,23 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
755
755
|
const webBotAuthSignatureLabel = webBotAuthConfig.signatureLabel || 'sig1'
|
|
756
756
|
const contentSignalsConfig = config.contentSignals || {}
|
|
757
757
|
const contentSignalsEnabled = contentSignalsConfig.enabled === true
|
|
758
|
+
const agentSkillsConfig = config.agentSkills || {}
|
|
759
|
+
const agentSkillsEnabled = agentSkillsConfig.enabled === true
|
|
760
|
+
const mcpServerCardConfig = config.mcpServerCard || {}
|
|
761
|
+
const mcpServerCardEnabled = mcpServerCardConfig.enabled === true
|
|
762
|
+
|
|
763
|
+
const toUrl = (href) => {
|
|
764
|
+
if (!href) return null
|
|
765
|
+
if (/^https?:\/\//i.test(href)) return href
|
|
766
|
+
const normalizedHref = href.startsWith('/') ? href : `/${href}`
|
|
767
|
+
return siteUrl ? `${siteUrl}${normalizedHref}` : normalizedHref
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const normalizeLocalPath = (href) => {
|
|
771
|
+
if (!href || /^https?:\/\//i.test(href)) return null
|
|
772
|
+
const path = href.startsWith('/') ? href.slice(1) : href
|
|
773
|
+
return path || null
|
|
774
|
+
}
|
|
758
775
|
|
|
759
776
|
if (markdownNegotiationEnabled || webBotAuthEnabled) {
|
|
760
777
|
const functionsDir = resolve(projectRoot, 'functions')
|
|
@@ -1022,6 +1039,129 @@ export async function onRequest (context) {
|
|
|
1022
1039
|
}
|
|
1023
1040
|
}
|
|
1024
1041
|
|
|
1042
|
+
if (agentSkillsEnabled) {
|
|
1043
|
+
const agentSkillsPath = agentSkillsConfig.path || '/.well-known/agent-skills/index.json'
|
|
1044
|
+
const agentSkillsSchema = agentSkillsConfig.schema || 'https://schemas.agentskills.io/discovery/0.2.0/schema.json'
|
|
1045
|
+
const indexDistPath = normalizeLocalPath(agentSkillsPath)
|
|
1046
|
+
|
|
1047
|
+
if (!indexDistPath) {
|
|
1048
|
+
console.warn(`\x1b[33m[docsector]\x1b[0m Skipped Agent Skills index generation: path must be a local URI path, got "${agentSkillsPath}"`)
|
|
1049
|
+
} else {
|
|
1050
|
+
const indexHref = agentSkillsPath.startsWith('/') ? agentSkillsPath : `/${agentSkillsPath}`
|
|
1051
|
+
const configuredSkills = Array.isArray(agentSkillsConfig.skills)
|
|
1052
|
+
? agentSkillsConfig.skills
|
|
1053
|
+
: []
|
|
1054
|
+
|
|
1055
|
+
const normalizedSkills = configuredSkills.map((skill, index) => {
|
|
1056
|
+
if (!skill || typeof skill !== 'object') {
|
|
1057
|
+
throw new Error(`[docsector] agentSkills.skills[${index}] must be an object`)
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const name = String(skill.name || '').trim().toLowerCase()
|
|
1061
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
|
|
1062
|
+
throw new Error(`[docsector] agentSkills.skills[${index}].name must be lowercase alphanumeric and hyphen-separated`)
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const type = normalizeAgentSkillType(skill.type, index)
|
|
1066
|
+
const description = String(skill.description || '').trim()
|
|
1067
|
+
if (!description) {
|
|
1068
|
+
throw new Error(`[docsector] agentSkills.skills[${index}].description is required`)
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const url = toUrl(skill.url)
|
|
1072
|
+
if (!url) {
|
|
1073
|
+
throw new Error(`[docsector] agentSkills.skills[${index}].url is required`)
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
let digest = normalizeAgentSkillDigest(skill.digest)
|
|
1077
|
+
if (!digest) {
|
|
1078
|
+
const artifactPath = resolveAgentSkillArtifactPath(url, { siteUrl, distDir })
|
|
1079
|
+
if (!artifactPath || !existsSync(artifactPath)) {
|
|
1080
|
+
throw new Error(`[docsector] Unable to compute digest for agentSkills.skills[${index}] (${name}). Artifact not found at ${url}`)
|
|
1081
|
+
}
|
|
1082
|
+
const artifactContents = readFileSync(artifactPath)
|
|
1083
|
+
digest = `sha256:${createHash('sha256').update(artifactContents).digest('hex')}`
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return {
|
|
1087
|
+
name,
|
|
1088
|
+
type,
|
|
1089
|
+
description,
|
|
1090
|
+
url,
|
|
1091
|
+
digest
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
const agentSkillsIndex = {
|
|
1096
|
+
$schema: agentSkillsSchema,
|
|
1097
|
+
skills: normalizedSkills
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const indexDir = resolve(distDir, indexDistPath, '..')
|
|
1101
|
+
mkdirSync(indexDir, { recursive: true })
|
|
1102
|
+
writeFileSync(
|
|
1103
|
+
resolve(distDir, indexDistPath),
|
|
1104
|
+
JSON.stringify(agentSkillsIndex, null, 2) + '\n'
|
|
1105
|
+
)
|
|
1106
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Generated ${indexHref}`)
|
|
1107
|
+
|
|
1108
|
+
const headersWithSkills = readFileSync(headersPath, 'utf-8')
|
|
1109
|
+
if (!headersWithSkills.includes(indexHref)) {
|
|
1110
|
+
const skillsHeaders = `${indexHref}\n Content-Type: application/json; charset=utf-8\n`
|
|
1111
|
+
writeFileSync(headersPath, headersWithSkills.trimEnd() + '\n\n' + skillsHeaders)
|
|
1112
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for ${indexHref}`)
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (mcpServerCardEnabled) {
|
|
1118
|
+
if (!config.mcp) {
|
|
1119
|
+
console.warn('\x1b[33m[docsector]\x1b[0m Skipped MCP Server Card generation: mcp config is not enabled')
|
|
1120
|
+
} else {
|
|
1121
|
+
const mcpServerCardPath = mcpServerCardConfig.path || '/.well-known/mcp/server-card.json'
|
|
1122
|
+
const cardDistPath = normalizeLocalPath(mcpServerCardPath)
|
|
1123
|
+
|
|
1124
|
+
if (!cardDistPath) {
|
|
1125
|
+
console.warn(`\x1b[33m[docsector]\x1b[0m Skipped MCP Server Card generation: path must be a local URI path, got "${mcpServerCardPath}"`)
|
|
1126
|
+
} else {
|
|
1127
|
+
const cardHref = mcpServerCardPath.startsWith('/') ? mcpServerCardPath : `/${mcpServerCardPath}`
|
|
1128
|
+
const mcpServerName = config.mcp.serverName || config.branding?.name || 'docs'
|
|
1129
|
+
const mcpServerVersion = config.branding?.version || '1.0.0'
|
|
1130
|
+
const mcpToolSuffix = config.mcp.toolSuffix || 'docs'
|
|
1131
|
+
const transportEndpoint = mcpServerCardConfig.transportEndpoint || '/mcp'
|
|
1132
|
+
const endpoint = toUrl(transportEndpoint)
|
|
1133
|
+
|
|
1134
|
+
if (!endpoint) {
|
|
1135
|
+
console.warn('\x1b[33m[docsector]\x1b[0m Skipped MCP Server Card generation: unable to resolve transport endpoint URL')
|
|
1136
|
+
} else {
|
|
1137
|
+
const cardPayload = buildMcpServerCardPayload({
|
|
1138
|
+
config,
|
|
1139
|
+
mcpServerCardConfig,
|
|
1140
|
+
serverName: mcpServerName,
|
|
1141
|
+
serverVersion: mcpServerVersion,
|
|
1142
|
+
endpoint,
|
|
1143
|
+
toolSuffix: mcpToolSuffix
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1146
|
+
const cardDir = resolve(distDir, cardDistPath, '..')
|
|
1147
|
+
mkdirSync(cardDir, { recursive: true })
|
|
1148
|
+
writeFileSync(
|
|
1149
|
+
resolve(distDir, cardDistPath),
|
|
1150
|
+
JSON.stringify(cardPayload, null, 2) + '\n'
|
|
1151
|
+
)
|
|
1152
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Generated ${cardHref}`)
|
|
1153
|
+
|
|
1154
|
+
const headersWithServerCard = readFileSync(headersPath, 'utf-8')
|
|
1155
|
+
if (!headersWithServerCard.includes(cardHref)) {
|
|
1156
|
+
const serverCardHeaders = `${cardHref}\n Content-Type: application/json; charset=utf-8\n`
|
|
1157
|
+
writeFileSync(headersPath, headersWithServerCard.trimEnd() + '\n\n' + serverCardHeaders)
|
|
1158
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for ${cardHref}`)
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1025
1165
|
console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for .md files`)
|
|
1026
1166
|
|
|
1027
1167
|
// Add homepage Link headers for agent discovery (RFC 8288 / RFC 9727)
|
|
@@ -1081,19 +1221,6 @@ export async function onRequest (context) {
|
|
|
1081
1221
|
const apiCatalogEnabled = apiCatalogConfig.enabled !== false
|
|
1082
1222
|
const apiCatalogPath = (apiCatalogConfig.path || apiCatalogHref || '/.well-known/api-catalog')
|
|
1083
1223
|
|
|
1084
|
-
const toUrl = (href) => {
|
|
1085
|
-
if (!href) return null
|
|
1086
|
-
if (/^https?:\/\//i.test(href)) return href
|
|
1087
|
-
const normalizedHref = href.startsWith('/') ? href : `/${href}`
|
|
1088
|
-
return siteUrl ? `${siteUrl}${normalizedHref}` : normalizedHref
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
const normalizeLocalPath = (href) => {
|
|
1092
|
-
if (!href || /^https?:\/\//i.test(href)) return null
|
|
1093
|
-
const path = href.startsWith('/') ? href.slice(1) : href
|
|
1094
|
-
return path || null
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
1224
|
if (apiCatalogEnabled && apiCatalogPath) {
|
|
1098
1225
|
const catalogDistPath = normalizeLocalPath(apiCatalogPath)
|
|
1099
1226
|
|
|
@@ -1376,6 +1503,138 @@ function applyContentSignalsToRobots (robotsContent, { contentSignalLine, userAg
|
|
|
1376
1503
|
return updated.join('\n').replace(/\n+$/g, '') + '\n'
|
|
1377
1504
|
}
|
|
1378
1505
|
|
|
1506
|
+
function normalizeAgentSkillType (type, index) {
|
|
1507
|
+
const normalizedType = String(type || '').trim()
|
|
1508
|
+
if (normalizedType !== 'skill-md' && normalizedType !== 'archive') {
|
|
1509
|
+
throw new Error(`[docsector] agentSkills.skills[${index}].type must be "skill-md" or "archive"`)
|
|
1510
|
+
}
|
|
1511
|
+
return normalizedType
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function normalizeAgentSkillDigest (digest) {
|
|
1515
|
+
if (digest === null || digest === undefined || digest === '') return null
|
|
1516
|
+
const normalizedDigest = String(digest).trim().toLowerCase()
|
|
1517
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(normalizedDigest)) {
|
|
1518
|
+
throw new Error('[docsector] agentSkills.skills[*].digest must follow "sha256:{hex}"')
|
|
1519
|
+
}
|
|
1520
|
+
return normalizedDigest
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
function resolveAgentSkillArtifactPath (artifactUrl, { siteUrl, distDir }) {
|
|
1524
|
+
if (!artifactUrl || typeof artifactUrl !== 'string') {
|
|
1525
|
+
return null
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
let pathname = null
|
|
1529
|
+
|
|
1530
|
+
if (/^https?:\/\//i.test(artifactUrl)) {
|
|
1531
|
+
if (!siteUrl) return null
|
|
1532
|
+
|
|
1533
|
+
let artifact
|
|
1534
|
+
let base
|
|
1535
|
+
try {
|
|
1536
|
+
artifact = new URL(artifactUrl)
|
|
1537
|
+
base = new URL(siteUrl)
|
|
1538
|
+
} catch {
|
|
1539
|
+
return null
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (artifact.origin !== base.origin) {
|
|
1543
|
+
return null
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
pathname = artifact.pathname
|
|
1547
|
+
} else {
|
|
1548
|
+
pathname = artifactUrl.startsWith('/') ? artifactUrl : `/${artifactUrl}`
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
const relativePath = pathname.replace(/^\/+/, '')
|
|
1552
|
+
if (!relativePath) return null
|
|
1553
|
+
|
|
1554
|
+
return resolve(distDir, relativePath)
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function mergeObjects (base, override) {
|
|
1558
|
+
if (!override || typeof override !== 'object' || Array.isArray(override)) {
|
|
1559
|
+
return base
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
return { ...base, ...override }
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function buildMcpServerCardPayload ({
|
|
1566
|
+
config,
|
|
1567
|
+
mcpServerCardConfig,
|
|
1568
|
+
serverName,
|
|
1569
|
+
serverVersion,
|
|
1570
|
+
endpoint,
|
|
1571
|
+
toolSuffix
|
|
1572
|
+
}) {
|
|
1573
|
+
const transportType = mcpServerCardConfig.transportType || 'streamable-http'
|
|
1574
|
+
const protocolVersion = mcpServerCardConfig.protocolVersion || '2025-03-26'
|
|
1575
|
+
const baseTools = [
|
|
1576
|
+
`search_${toolSuffix}`,
|
|
1577
|
+
`get_page_${toolSuffix}`
|
|
1578
|
+
]
|
|
1579
|
+
|
|
1580
|
+
const defaultCapabilities = {
|
|
1581
|
+
tools: {
|
|
1582
|
+
supported: true,
|
|
1583
|
+
names: baseTools
|
|
1584
|
+
},
|
|
1585
|
+
resources: {
|
|
1586
|
+
supported: false
|
|
1587
|
+
},
|
|
1588
|
+
prompts: {
|
|
1589
|
+
supported: false
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const capabilitiesOverride = (mcpServerCardConfig.capabilities && typeof mcpServerCardConfig.capabilities === 'object' && !Array.isArray(mcpServerCardConfig.capabilities))
|
|
1594
|
+
? mcpServerCardConfig.capabilities
|
|
1595
|
+
: {}
|
|
1596
|
+
const capabilities = {
|
|
1597
|
+
...capabilitiesOverride,
|
|
1598
|
+
tools: mergeObjects(defaultCapabilities.tools, capabilitiesOverride.tools),
|
|
1599
|
+
resources: mergeObjects(defaultCapabilities.resources, capabilitiesOverride.resources),
|
|
1600
|
+
prompts: mergeObjects(defaultCapabilities.prompts, capabilitiesOverride.prompts)
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const primaryRemote = {
|
|
1604
|
+
transportType,
|
|
1605
|
+
endpoint,
|
|
1606
|
+
supportedProtocolVersions: [protocolVersion]
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const customRemotes = Array.isArray(mcpServerCardConfig.remotes)
|
|
1610
|
+
? mcpServerCardConfig.remotes
|
|
1611
|
+
: []
|
|
1612
|
+
const remotes = [primaryRemote, ...customRemotes]
|
|
1613
|
+
|
|
1614
|
+
const payload = {
|
|
1615
|
+
serverInfo: {
|
|
1616
|
+
name: serverName,
|
|
1617
|
+
version: serverVersion
|
|
1618
|
+
},
|
|
1619
|
+
title: config.branding?.name || serverName,
|
|
1620
|
+
description: config.branding?.description || null,
|
|
1621
|
+
transport: {
|
|
1622
|
+
type: transportType,
|
|
1623
|
+
endpoint
|
|
1624
|
+
},
|
|
1625
|
+
remotes,
|
|
1626
|
+
capabilities,
|
|
1627
|
+
tools: baseTools.map(name => ({ name }))
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const metadata = mcpServerCardConfig.metadata
|
|
1631
|
+
if (metadata && typeof metadata === 'object' && !Array.isArray(metadata)) {
|
|
1632
|
+
return { ...payload, ...metadata }
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
return payload
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1379
1638
|
/**
|
|
1380
1639
|
* Create a complete Quasar configuration for a docsector-reader consumer project.
|
|
1381
1640
|
*
|