dsh-mesh-node 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +8 -2
- package/package.json +43 -4
- package/src/cli.js +1246 -0
- package/src/core/acl.js +81 -0
- package/src/core/audit.js +75 -0
- package/src/core/autostart.js +119 -0
- package/src/core/cluster.js +340 -0
- package/src/core/config.js +622 -0
- package/src/core/desktop-proxy.js +85 -0
- package/src/core/ds-web.js +394 -0
- package/src/core/host-agent.js +263 -0
- package/src/core/jobs.js +259 -0
- package/src/core/pairing.js +141 -0
- package/src/core/peer-client.js +273 -0
- package/src/core/peer-doctor.js +294 -0
- package/src/core/peer-http.js +61 -0
- package/src/core/repair.js +93 -0
- package/src/core/restart-policy.js +21 -0
- package/src/core/result.js +29 -0
- package/src/core/session-profile.js +146 -0
- package/src/core/tls.js +160 -0
- package/src/core/util.js +68 -0
- package/src/e2e-autostart.js +58 -0
- package/src/e2e-channel.js +384 -0
- package/src/e2e-cluster.js +417 -0
- package/src/e2e-host.js +411 -0
- package/src/e2e-onboard.js +232 -0
- package/src/e2e-pair.js +317 -0
- package/src/e2e-plugin.js +395 -0
- package/src/e2e-repair.js +297 -0
- package/src/index.js +20 -0
- package/src/peer-bridge.js +124 -0
- package/src/selftest.js +872 -0
- package/src/server.js +313 -0
- package/src/tools/alerts.js +80 -0
- package/src/tools/channel.js +253 -0
- package/src/tools/cluster.js +235 -0
- package/src/tools/config-patch.js +182 -0
- package/src/tools/control-apps.js +469 -0
- package/src/tools/control-path.js +160 -0
- package/src/tools/control.js +232 -0
- package/src/tools/core.js +79 -0
- package/src/tools/delegate.js +653 -0
- package/src/tools/fs.js +436 -0
- package/src/tools/git.js +266 -0
- package/src/tools/host.js +305 -0
- package/src/tools/index.js +122 -0
- package/src/tools/repair-fs.js +130 -0
- package/src/tools/repair.js +317 -0
- package/src/tools/session-history.js +60 -0
- package/src/tools/shell.js +66 -0
- package/src/tools/tasks.js +117 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { startMeshServer, buildMcpServer } from './server.js'
|
|
2
|
+
export {
|
|
3
|
+
MESH_VERSION,
|
|
4
|
+
CONFIG_VERSION,
|
|
5
|
+
DEFAULT_ALLOW,
|
|
6
|
+
meshHome,
|
|
7
|
+
loadConfig,
|
|
8
|
+
saveConfig,
|
|
9
|
+
loadSecrets,
|
|
10
|
+
configPath,
|
|
11
|
+
secretsPath,
|
|
12
|
+
defaultConfig,
|
|
13
|
+
createConfigLoader,
|
|
14
|
+
verifyBearer,
|
|
15
|
+
isInsideRoots
|
|
16
|
+
} from './core/config.js'
|
|
17
|
+
export { makeAudit, tailAudit, auditPath } from './core/audit.js'
|
|
18
|
+
export { JobRunner, pruneJobs, TERMINAL_STATES } from './core/jobs.js'
|
|
19
|
+
export { buildAll, toolNames, isAllowed, dispatch, registerTools } from './tools/index.js'
|
|
20
|
+
export { runSelftest } from './selftest.js'
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio MCP bridge to a paired peer.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: the overlay from `peer patch` uses the streamable-http
|
|
5
|
+
* transport, so a self-signed peer only works when the DSH process itself has
|
|
6
|
+
* the peer certificate in NODE_EXTRA_CA_CERTS. When that env is missing (or the
|
|
7
|
+
* overlay was written after the process started), the mcp-client reports only
|
|
8
|
+
* "server is disconnected" and the model never sees a single tool — a silent
|
|
9
|
+
* failure that cost a full debugging session on 2026-10-20.
|
|
10
|
+
*
|
|
11
|
+
* This bridge is launched by DSH as a stdio child. It performs the pinned TLS
|
|
12
|
+
* handshake itself (see core/peer-client.js), so the DSH process needs no CA
|
|
13
|
+
* trust store, and a dead peer becomes a visible child exit that the client's
|
|
14
|
+
* reconnect policy handles instead of an unexplained disconnected server.
|
|
15
|
+
*
|
|
16
|
+
* Wire it with `node src/cli.js peer patch --transport stdio`, then restart the
|
|
17
|
+
* DSH instance so the overlay entry is loaded (overlays are read at boot).
|
|
18
|
+
*/
|
|
19
|
+
import { McpServer, fromJsonSchema } from '@modelcontextprotocol/server'
|
|
20
|
+
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'
|
|
21
|
+
import { MESH_VERSION } from './core/config.js'
|
|
22
|
+
import { connectPeer } from './core/peer-client.js'
|
|
23
|
+
|
|
24
|
+
const EMPTY_OBJECT_SCHEMA = { type: 'object', properties: {} }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Peer tool JSON Schema -> the Standard Schema that McpServer.registerTool
|
|
28
|
+
* accepts. Tools with no arguments (or a non-object schema) get an empty
|
|
29
|
+
* object schema: passing a schema the SDK cannot convert would otherwise drop
|
|
30
|
+
* the tool entirely.
|
|
31
|
+
*/
|
|
32
|
+
export function toToolInputSchema(inputSchema) {
|
|
33
|
+
if (!inputSchema || typeof inputSchema !== 'object' || inputSchema.type !== 'object') {
|
|
34
|
+
return fromJsonSchema(EMPTY_OBJECT_SCHEMA)
|
|
35
|
+
}
|
|
36
|
+
const properties = inputSchema.properties
|
|
37
|
+
if (!properties || typeof properties !== 'object' || Object.keys(properties).length === 0) {
|
|
38
|
+
return fromJsonSchema(EMPTY_OBJECT_SCHEMA)
|
|
39
|
+
}
|
|
40
|
+
return fromJsonSchema(inputSchema)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Start the stdio bridge and resolve once it is serving.
|
|
45
|
+
*
|
|
46
|
+
* @param peer - paired peer record {name, url, token, certFingerprint}.
|
|
47
|
+
* @param options.serverName - local MCP namespace (DSH shows `mcp__<serverName>__*`).
|
|
48
|
+
* @param options.timeoutMs - per MCP request timeout for calls to the peer.
|
|
49
|
+
* @param options.log - logger; MUST write to stderr, stdout carries the protocol.
|
|
50
|
+
* @returns handles for tests plus the proxied/skipped tool names.
|
|
51
|
+
*/
|
|
52
|
+
export async function runPeerBridge(peer, { serverName, timeoutMs = 120000, log = console } = {}) {
|
|
53
|
+
const name = String(peer?.name || peer?.id || 'peer')
|
|
54
|
+
const namespace = String(serverName || `dsh-mesh-${name}`)
|
|
55
|
+
.replace(/[^A-Za-z0-9_-]/g, '-')
|
|
56
|
+
.slice(0, 32)
|
|
57
|
+
const connection = await connectPeer(peer, { timeoutMs, clientName: 'dsh-mesh-peer-bridge' })
|
|
58
|
+
let listed
|
|
59
|
+
try {
|
|
60
|
+
listed = await connection.client.listTools()
|
|
61
|
+
} catch (error) {
|
|
62
|
+
await connection.close()
|
|
63
|
+
throw error
|
|
64
|
+
}
|
|
65
|
+
const peerTools = listed?.tools ?? []
|
|
66
|
+
const server = new McpServer(
|
|
67
|
+
{ name: namespace, version: MESH_VERSION },
|
|
68
|
+
{
|
|
69
|
+
instructions: `Tools proxied from dsh-mesh peer "${name}" (${peer.url}). This process pins the peer certificate itself, so it does not depend on NODE_EXTRA_CA_CERTS in the DSH process.`
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
const skipped = []
|
|
73
|
+
for (const tool of peerTools) {
|
|
74
|
+
let inputSchema
|
|
75
|
+
try {
|
|
76
|
+
inputSchema = toToolInputSchema(tool.inputSchema)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
skipped.push({ tool: tool.name, reason: error?.message || String(error) })
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
server.registerTool(
|
|
82
|
+
tool.name,
|
|
83
|
+
{ description: tool.description || `proxied dsh-mesh tool ${tool.name}`, inputSchema },
|
|
84
|
+
async (args) => {
|
|
85
|
+
try {
|
|
86
|
+
return await connection.client.callTool({ name: tool.name, arguments: args ?? {} })
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const code = typeof error?.code === 'string' ? `[${error.code}] ` : ''
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: `peer bridge call failed: ${code}${error?.message || error}` }],
|
|
91
|
+
isError: true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const transport = new StdioServerTransport()
|
|
99
|
+
let closing = false
|
|
100
|
+
const close = async (reason) => {
|
|
101
|
+
if (closing) return
|
|
102
|
+
closing = true
|
|
103
|
+
log.error?.(`[peer-bridge] ${reason} — exiting`)
|
|
104
|
+
try {
|
|
105
|
+
await server.close()
|
|
106
|
+
} catch {
|
|
107
|
+
/* already closed */
|
|
108
|
+
}
|
|
109
|
+
await connection.close()
|
|
110
|
+
// The bridge is a child of DSH: never linger after the pipe is gone.
|
|
111
|
+
process.exit(0)
|
|
112
|
+
}
|
|
113
|
+
transport.onclose = () => {
|
|
114
|
+
void close('stdio closed by the DSH client')
|
|
115
|
+
}
|
|
116
|
+
connection.client.onclose = () => {
|
|
117
|
+
void close('peer connection dropped')
|
|
118
|
+
}
|
|
119
|
+
await server.connect(transport)
|
|
120
|
+
log.error?.(
|
|
121
|
+
`[peer-bridge] peer="${name}" serverName="${namespace}" tools=${peerTools.length}${skipped.length ? ` skipped=${skipped.length}` : ''}`
|
|
122
|
+
)
|
|
123
|
+
return { server, connection, namespace, toolCount: peerTools.length, skipped }
|
|
124
|
+
}
|