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.
Files changed (2) hide show
  1. package/bin/onboard.mjs +74 -20
  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.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', 'settings.json'),
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 the universal snippet as fallback for any other MCP client.
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 this to its config under \`mcpServers\`:${RESET}`)
262
+ log(`${DIM}Using a different MCP client? Add ONE of these to its config under \`mcpServers\`:${RESET}`)
214
263
  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
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blackops-onboard",
3
- "version": "0.4.0",
3
+ "version": "0.7.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": {