blackops-onboard 0.4.0 → 0.7.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/bin/onboard.mjs +74 -20
- package/package.json +1 -1
package/bin/onboard.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import path from 'node:path'
|
|
|
16
16
|
import os from 'node:os'
|
|
17
17
|
import readline from 'node:readline/promises'
|
|
18
18
|
|
|
19
|
-
const VERSION = '0.
|
|
19
|
+
const VERSION = '0.7.0'
|
|
20
20
|
const INSTALL_URL = process.env.BLACKOPS_INSTALL_URL || 'https://blackopscenter.com/api/install'
|
|
21
21
|
const CONFIG_DIR = path.join(os.homedir(), '.blackops')
|
|
22
22
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json')
|
|
@@ -68,15 +68,35 @@ function writeJsonAtomic(filePath, value) {
|
|
|
68
68
|
// All known clients use the same `mcpServers.<name>.{type,url,headers}`
|
|
69
69
|
// JSON shape, so a single writer covers them.
|
|
70
70
|
|
|
71
|
+
// `transport: 'http'` writes the native streamable HTTP entry that
|
|
72
|
+
// Claude Code and Cursor both understand:
|
|
73
|
+
// { type: 'http', url, headers: { Authorization: 'Bearer ...' } }
|
|
74
|
+
//
|
|
75
|
+
// `transport: 'stdio-bridge'` writes an `mcp-remote` stdio shim, used for
|
|
76
|
+
// Claude Desktop and Cline which only speak stdio MCP today:
|
|
77
|
+
// { command: 'npx', args: ['-y', 'mcp-remote', url, '--header', 'Authorization: Bearer ...'] }
|
|
78
|
+
|
|
71
79
|
function getClientConfigs(homedir, platform) {
|
|
72
80
|
const APPDATA = process.env.APPDATA || ''
|
|
73
81
|
return [
|
|
74
82
|
{
|
|
83
|
+
// Claude Code's canonical config path is ~/.claude.json (single file at
|
|
84
|
+
// home), NOT ~/.claude/settings.json. The latter holds permissions/hooks
|
|
85
|
+
// but Claude Code only reads mcpServers from ~/.claude.json.
|
|
75
86
|
id: 'claude-code',
|
|
76
87
|
label: 'Claude Code',
|
|
77
|
-
path: path.join(homedir, '.claude
|
|
88
|
+
path: path.join(homedir, '.claude.json'),
|
|
89
|
+
transport: 'http',
|
|
78
90
|
alwaysInstall: true,
|
|
79
91
|
},
|
|
92
|
+
{
|
|
93
|
+
// Legacy/secondary path kept for any client that still reads it.
|
|
94
|
+
// Harmless duplicate; cheap insurance against tool-discovery drift.
|
|
95
|
+
id: 'claude-code-legacy',
|
|
96
|
+
label: 'Claude Code (legacy settings.json)',
|
|
97
|
+
path: path.join(homedir, '.claude', 'settings.json'),
|
|
98
|
+
transport: 'http',
|
|
99
|
+
},
|
|
80
100
|
{
|
|
81
101
|
id: 'claude-desktop',
|
|
82
102
|
label: 'Claude Desktop',
|
|
@@ -86,20 +106,52 @@ function getClientConfigs(homedir, platform) {
|
|
|
86
106
|
: platform === 'win32'
|
|
87
107
|
? path.join(APPDATA, 'Claude', 'claude_desktop_config.json')
|
|
88
108
|
: path.join(homedir, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
109
|
+
transport: 'stdio-bridge',
|
|
89
110
|
},
|
|
90
111
|
{
|
|
91
112
|
id: 'cursor',
|
|
92
113
|
label: 'Cursor',
|
|
93
114
|
path: path.join(homedir, '.cursor', 'mcp.json'),
|
|
115
|
+
transport: 'http',
|
|
94
116
|
},
|
|
95
117
|
{
|
|
96
118
|
id: 'cline',
|
|
97
119
|
label: 'Cline (VS Code)',
|
|
98
120
|
path: path.join(homedir, '.cline', 'mcp_settings.json'),
|
|
121
|
+
transport: 'stdio-bridge',
|
|
99
122
|
},
|
|
100
123
|
]
|
|
101
124
|
}
|
|
102
125
|
|
|
126
|
+
function buildServerEntry(transport, { mcpUrl, token }) {
|
|
127
|
+
if (transport === 'stdio-bridge') {
|
|
128
|
+
// --transport http-only avoids mcp-remote's SSE-fallback path (which has
|
|
129
|
+
// historically hung for several minutes on slow tool calls).
|
|
130
|
+
// --timeout 30000 caps any single request at 30s instead of the 4-minute
|
|
131
|
+
// default, so a stuck call surfaces as an error the agent can recover
|
|
132
|
+
// from instead of a silent hang.
|
|
133
|
+
return {
|
|
134
|
+
command: 'npx',
|
|
135
|
+
args: [
|
|
136
|
+
'-y',
|
|
137
|
+
'mcp-remote',
|
|
138
|
+
mcpUrl,
|
|
139
|
+
'--header',
|
|
140
|
+
`Authorization: Bearer ${token}`,
|
|
141
|
+
'--transport',
|
|
142
|
+
'http-only',
|
|
143
|
+
'--timeout',
|
|
144
|
+
'30000',
|
|
145
|
+
],
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
type: 'http',
|
|
150
|
+
url: mcpUrl,
|
|
151
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
103
155
|
function clientIsInstalled(cfg) {
|
|
104
156
|
if (cfg.alwaysInstall) return true
|
|
105
157
|
// File exists OR its parent dir exists (covers cases where the user
|
|
@@ -112,11 +164,7 @@ function clientIsInstalled(cfg) {
|
|
|
112
164
|
function injectMcpServer(cfg, { mcpServerName, mcpUrl, token }) {
|
|
113
165
|
const existing = readJsonSafe(cfg.path) ?? {}
|
|
114
166
|
existing.mcpServers = existing.mcpServers ?? {}
|
|
115
|
-
existing.mcpServers[mcpServerName] = {
|
|
116
|
-
type: 'http',
|
|
117
|
-
url: mcpUrl,
|
|
118
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
119
|
-
}
|
|
167
|
+
existing.mcpServers[mcpServerName] = buildServerEntry(cfg.transport, { mcpUrl, token })
|
|
120
168
|
writeJsonAtomic(cfg.path, existing)
|
|
121
169
|
}
|
|
122
170
|
|
|
@@ -208,22 +256,28 @@ async function main() {
|
|
|
208
256
|
err(`Could not write ${f.client.label} config: ${f.error}`)
|
|
209
257
|
}
|
|
210
258
|
|
|
211
|
-
// 4. Always print
|
|
259
|
+
// 4. Always print universal snippets as fallback for any other MCP client.
|
|
260
|
+
// Two formats — pick whichever your client supports.
|
|
212
261
|
log()
|
|
213
|
-
log(`${DIM}Using a different MCP client? Add
|
|
262
|
+
log(`${DIM}Using a different MCP client? Add ONE of these to its config under \`mcpServers\`:${RESET}`)
|
|
214
263
|
log()
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
264
|
+
log(`${DIM}// HTTP transport (Claude Code, Cursor, etc.):${RESET}`)
|
|
265
|
+
log(
|
|
266
|
+
`${DIM}${JSON.stringify(
|
|
267
|
+
{ [config.mcp_server_name]: buildServerEntry('http', { mcpUrl: config.mcp_url, token: config.token }) },
|
|
268
|
+
null,
|
|
269
|
+
2
|
|
270
|
+
)}${RESET}`
|
|
271
|
+
)
|
|
272
|
+
log()
|
|
273
|
+
log(`${DIM}// stdio bridge (Claude Desktop, Cline, anything stdio-only):${RESET}`)
|
|
274
|
+
log(
|
|
275
|
+
`${DIM}${JSON.stringify(
|
|
276
|
+
{ [config.mcp_server_name]: buildServerEntry('stdio-bridge', { mcpUrl: config.mcp_url, token: config.token }) },
|
|
277
|
+
null,
|
|
278
|
+
2
|
|
279
|
+
)}${RESET}`
|
|
225
280
|
)
|
|
226
|
-
log(`${DIM}${snippet}${RESET}`)
|
|
227
281
|
|
|
228
282
|
// 5. Tell the user the one thing to say next. Single line, copy-pasteable.
|
|
229
283
|
log()
|
package/package.json
CHANGED