@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 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.3.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.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
  }
@@ -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
  *