blackops-onboard 0.4.0 → 0.6.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.
Files changed (2) hide show
  1. package/bin/onboard.mjs +62 -19
  2. 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.4.0'
19
+ const VERSION = '0.6.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,6 +68,14 @@ 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 [
@@ -75,6 +83,7 @@ function getClientConfigs(homedir, platform) {
75
83
  id: 'claude-code',
76
84
  label: 'Claude Code',
77
85
  path: path.join(homedir, '.claude', 'settings.json'),
86
+ transport: 'http',
78
87
  alwaysInstall: true,
79
88
  },
80
89
  {
@@ -86,20 +95,52 @@ function getClientConfigs(homedir, platform) {
86
95
  : platform === 'win32'
87
96
  ? path.join(APPDATA, 'Claude', 'claude_desktop_config.json')
88
97
  : path.join(homedir, '.config', 'Claude', 'claude_desktop_config.json'),
98
+ transport: 'stdio-bridge',
89
99
  },
90
100
  {
91
101
  id: 'cursor',
92
102
  label: 'Cursor',
93
103
  path: path.join(homedir, '.cursor', 'mcp.json'),
104
+ transport: 'http',
94
105
  },
95
106
  {
96
107
  id: 'cline',
97
108
  label: 'Cline (VS Code)',
98
109
  path: path.join(homedir, '.cline', 'mcp_settings.json'),
110
+ transport: 'stdio-bridge',
99
111
  },
100
112
  ]
101
113
  }
102
114
 
115
+ function buildServerEntry(transport, { mcpUrl, token }) {
116
+ if (transport === 'stdio-bridge') {
117
+ // --transport http-only avoids mcp-remote's SSE-fallback path (which has
118
+ // historically hung for several minutes on slow tool calls).
119
+ // --timeout 30000 caps any single request at 30s instead of the 4-minute
120
+ // default, so a stuck call surfaces as an error the agent can recover
121
+ // from instead of a silent hang.
122
+ return {
123
+ command: 'npx',
124
+ args: [
125
+ '-y',
126
+ 'mcp-remote',
127
+ mcpUrl,
128
+ '--header',
129
+ `Authorization: Bearer ${token}`,
130
+ '--transport',
131
+ 'http-only',
132
+ '--timeout',
133
+ '30000',
134
+ ],
135
+ }
136
+ }
137
+ return {
138
+ type: 'http',
139
+ url: mcpUrl,
140
+ headers: { Authorization: `Bearer ${token}` },
141
+ }
142
+ }
143
+
103
144
  function clientIsInstalled(cfg) {
104
145
  if (cfg.alwaysInstall) return true
105
146
  // File exists OR its parent dir exists (covers cases where the user
@@ -112,11 +153,7 @@ function clientIsInstalled(cfg) {
112
153
  function injectMcpServer(cfg, { mcpServerName, mcpUrl, token }) {
113
154
  const existing = readJsonSafe(cfg.path) ?? {}
114
155
  existing.mcpServers = existing.mcpServers ?? {}
115
- existing.mcpServers[mcpServerName] = {
116
- type: 'http',
117
- url: mcpUrl,
118
- headers: { Authorization: `Bearer ${token}` },
119
- }
156
+ existing.mcpServers[mcpServerName] = buildServerEntry(cfg.transport, { mcpUrl, token })
120
157
  writeJsonAtomic(cfg.path, existing)
121
158
  }
122
159
 
@@ -208,22 +245,28 @@ async function main() {
208
245
  err(`Could not write ${f.client.label} config: ${f.error}`)
209
246
  }
210
247
 
211
- // 4. Always print the universal snippet as fallback for any other MCP client.
248
+ // 4. Always print universal snippets as fallback for any other MCP client.
249
+ // Two formats — pick whichever your client supports.
212
250
  log()
213
- log(`${DIM}Using a different MCP client? Add this to its config under \`mcpServers\`:${RESET}`)
251
+ log(`${DIM}Using a different MCP client? Add ONE of these to its config under \`mcpServers\`:${RESET}`)
214
252
  log()
215
- const snippet = JSON.stringify(
216
- {
217
- [config.mcp_server_name]: {
218
- type: 'http',
219
- url: config.mcp_url,
220
- headers: { Authorization: `Bearer ${config.token}` },
221
- },
222
- },
223
- null,
224
- 2
253
+ log(`${DIM}// HTTP transport (Claude Code, Cursor, etc.):${RESET}`)
254
+ log(
255
+ `${DIM}${JSON.stringify(
256
+ { [config.mcp_server_name]: buildServerEntry('http', { mcpUrl: config.mcp_url, token: config.token }) },
257
+ null,
258
+ 2
259
+ )}${RESET}`
260
+ )
261
+ log()
262
+ log(`${DIM}// stdio bridge (Claude Desktop, Cline, anything stdio-only):${RESET}`)
263
+ log(
264
+ `${DIM}${JSON.stringify(
265
+ { [config.mcp_server_name]: buildServerEntry('stdio-bridge', { mcpUrl: config.mcp_url, token: config.token }) },
266
+ null,
267
+ 2
268
+ )}${RESET}`
225
269
  )
226
- log(`${DIM}${snippet}${RESET}`)
227
270
 
228
271
  // 5. Tell the user the one thing to say next. Single line, copy-pasteable.
229
272
  log()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blackops-onboard",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Agent-first onboarding for BlackOps Center. Run via `npx blackops-onboard` to provision your account through Claude Code.",
5
5
  "type": "module",
6
6
  "publishConfig": {