dsh-plugin-capabilities 0.3.7 → 0.3.8
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 +2 -0
- package/lib/client.js +63 -20
- package/lib/client.js.map +2 -2
- package/lib/index.js +70 -30
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
- package/src/client/MarketTab.tsx +1 -1
- package/src/client/McpTab.tsx +59 -19
- package/src/client/index.ts +5 -4
- package/src/client/locales.ts +16 -4
- package/src/index.ts +2 -1
- package/src/mcp.test.ts +66 -1
- package/src/mcp.ts +78 -18
- package/src/profile.ts +6 -2
- package/src/routes.ts +36 -21
package/src/routes.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { dshLaunch, restartOwnedByShell, scheduleRestart, trustedRestartRequest
|
|
|
12
12
|
import { deleteSkill, setSkillPolicy, updateSkillFile, userSkillsDir, validateSkillInput, writeSkill, type SkillInput } from './skills.ts'
|
|
13
13
|
import { findRootByUrl, loadState, pluginStateDir, removeSkillRoot, type SkillRootEntry } from './state.ts'
|
|
14
14
|
import { removeTree } from './rmtree.ts'
|
|
15
|
-
import {
|
|
15
|
+
import { listMcpScoped, mcpScopeDir, removeMcp, setMcpDisabled, upsertMcp, validateMcpInput, type McpInput, type McpScope } from './mcp.ts'
|
|
16
16
|
import type { CapabilitiesHost, HostSkill } from './types.ts'
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -57,10 +57,17 @@ function toRootView(entry: SkillRootEntry): SkillRootEntry & { live: boolean } {
|
|
|
57
57
|
|
|
58
58
|
export interface CapabilitiesRoutesConfig {
|
|
59
59
|
profileDirPath: string
|
|
60
|
+
/** The dsh home directory: holds the machine-wide `cordis.patch.yml`. */
|
|
61
|
+
dshHomePath: string
|
|
60
62
|
/** Remount the host-plane skill provider after root-set changes. */
|
|
61
63
|
remountProvider: () => Promise<void>
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/** The request's target patch layer; unknown values fall back to the profile. */
|
|
67
|
+
function readScope(value: unknown): McpScope {
|
|
68
|
+
return value === 'global' ? 'global' : 'profile'
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
/** Register the manager's routes; returns the disposer removing them all. */
|
|
65
72
|
export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: CapabilitiesRoutesConfig): () => void {
|
|
66
73
|
const disposers = [
|
|
@@ -408,7 +415,7 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
408
415
|
sendJson(response, 502, { error: 'market index unavailable (offline?)' })
|
|
409
416
|
return
|
|
410
417
|
}
|
|
411
|
-
const existing = new Set(
|
|
418
|
+
const existing = new Set(listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map(row => row.serverName))
|
|
412
419
|
sendJson(response, 200, {
|
|
413
420
|
source: index.source,
|
|
414
421
|
servers: (index.servers ?? []).map(server => ({ ...server, installed: existing.has(server.id) })),
|
|
@@ -469,7 +476,7 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
469
476
|
sendJson(response, 404, { error: 'server not found in the market index' })
|
|
470
477
|
return
|
|
471
478
|
}
|
|
472
|
-
if (
|
|
479
|
+
if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some(row => row.serverName === server.id)) {
|
|
473
480
|
sendJson(response, 409, { error: 'already installed' })
|
|
474
481
|
return
|
|
475
482
|
}
|
|
@@ -503,7 +510,12 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
503
510
|
response.end()
|
|
504
511
|
return
|
|
505
512
|
}
|
|
506
|
-
|
|
513
|
+
const listing = listMcpScoped(config.profileDirPath, config.dshHomePath)
|
|
514
|
+
sendJson(response, 200, {
|
|
515
|
+
servers: listing.servers,
|
|
516
|
+
...(listing.globalError !== undefined ? { globalError: listing.globalError } : {}),
|
|
517
|
+
restartNeeded: true,
|
|
518
|
+
})
|
|
507
519
|
},
|
|
508
520
|
}),
|
|
509
521
|
|
|
@@ -521,14 +533,15 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
521
533
|
return
|
|
522
534
|
}
|
|
523
535
|
try {
|
|
524
|
-
const input = (await readJsonBody(request)) as McpInput
|
|
536
|
+
const input = (await readJsonBody(request)) as McpInput & { scope?: unknown }
|
|
525
537
|
const invalid = validateMcpInput(input)
|
|
526
538
|
if (invalid !== null) {
|
|
527
539
|
sendJson(response, 400, { error: invalid })
|
|
528
540
|
return
|
|
529
541
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
542
|
+
const scope = readScope(input.scope)
|
|
543
|
+
const id = upsertMcp(mcpScopeDir(scope, config.profileDirPath, config.dshHomePath), input)
|
|
544
|
+
sendJson(response, 200, { ok: true, id, scope, restartNeeded: true })
|
|
532
545
|
} catch (error) {
|
|
533
546
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
|
|
534
547
|
}
|
|
@@ -549,12 +562,13 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
549
562
|
return
|
|
550
563
|
}
|
|
551
564
|
try {
|
|
552
|
-
const body = (await readJsonBody(request)) as { id?: unknown; disabled?: unknown }
|
|
565
|
+
const body = (await readJsonBody(request)) as { id?: unknown; disabled?: unknown; scope?: unknown }
|
|
553
566
|
if (typeof body.id !== 'string' || typeof body.disabled !== 'boolean') {
|
|
554
567
|
sendJson(response, 400, { error: 'id and disabled are required' })
|
|
555
568
|
return
|
|
556
569
|
}
|
|
557
|
-
const
|
|
570
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath)
|
|
571
|
+
const ok = setMcpDisabled(dir, body.id, body.disabled)
|
|
558
572
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: 'server row not found' })
|
|
559
573
|
} catch (error) {
|
|
560
574
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
|
|
@@ -576,12 +590,13 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
576
590
|
return
|
|
577
591
|
}
|
|
578
592
|
try {
|
|
579
|
-
const body = (await readJsonBody(request)) as { id?: unknown }
|
|
593
|
+
const body = (await readJsonBody(request)) as { id?: unknown; scope?: unknown }
|
|
580
594
|
if (typeof body.id !== 'string') {
|
|
581
595
|
sendJson(response, 400, { error: 'id is required' })
|
|
582
596
|
return
|
|
583
597
|
}
|
|
584
|
-
const
|
|
598
|
+
const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath)
|
|
599
|
+
const ok = removeMcp(dir, body.id)
|
|
585
600
|
sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: 'server row not found' })
|
|
586
601
|
} catch (error) {
|
|
587
602
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
|
|
@@ -597,13 +612,14 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
597
612
|
response.end()
|
|
598
613
|
return
|
|
599
614
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
615
|
+
try {
|
|
616
|
+
sendJson(response, 200, {
|
|
617
|
+
servers: scanAllMcp(),
|
|
618
|
+
// ServerNames present in either patch layer, so the browser can
|
|
619
|
+
// grey out existing ones.
|
|
620
|
+
existing: listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map(row => row.serverName),
|
|
621
|
+
})
|
|
622
|
+
} catch (error) {
|
|
607
623
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
|
|
608
624
|
}
|
|
609
625
|
},
|
|
@@ -633,9 +649,8 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
|
|
|
633
649
|
const results: Array<{ name: string; ok: boolean; error?: string }> = []
|
|
634
650
|
for (const server of scanAllMcp()) {
|
|
635
651
|
if (!wanted.has(`${server.agent}/${server.name}`)) continue
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
results.push({ name: server.name, ok: false, error: 'already in profile' })
|
|
652
|
+
if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some(row => row.serverName === server.name)) {
|
|
653
|
+
results.push({ name: server.name, ok: false, error: 'already configured' })
|
|
639
654
|
continue
|
|
640
655
|
}
|
|
641
656
|
const input: McpInput = {
|