@teambit/cli-mcp-server 0.0.0-00149e000ea14bbdcb0391c0d576b4a2b40f8cde
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.docs.mdx +117 -0
- package/dist/README.docs.mdx +117 -0
- package/dist/cli-mcp-server.aspect.d.ts +2 -0
- package/dist/cli-mcp-server.aspect.js +18 -0
- package/dist/cli-mcp-server.aspect.js.map +1 -0
- package/dist/cli-mcp-server.main.runtime.d.ts +123 -0
- package/dist/cli-mcp-server.main.runtime.js +1519 -0
- package/dist/cli-mcp-server.main.runtime.js.map +1 -0
- package/dist/cli-mcp-server.spec.d.ts +1 -0
- package/dist/cli-mcp-server.spec.js +740 -0
- package/dist/cli-mcp-server.spec.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.cmd.d.ts +32 -0
- package/dist/mcp-server.cmd.js +59 -0
- package/dist/mcp-server.cmd.js.map +1 -0
- package/dist/preview-1784553800273.js +7 -0
- package/dist/remote-component-utils.d.ts +9 -0
- package/dist/remote-component-utils.js +155 -0
- package/dist/remote-component-utils.js.map +1 -0
- package/dist/rules-cmd.d.ts +21 -0
- package/dist/rules-cmd.js +74 -0
- package/dist/rules-cmd.js.map +1 -0
- package/dist/setup-cmd.d.ts +20 -0
- package/dist/setup-cmd.js +59 -0
- package/dist/setup-cmd.js.map +1 -0
- package/package.json +76 -0
- package/types/asset.d.ts +41 -0
- package/types/style.d.ts +46 -0
|
@@ -0,0 +1,1519 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.CliMcpServerMain = void 0;
|
|
7
|
+
function _cli() {
|
|
8
|
+
const data = require("@teambit/cli");
|
|
9
|
+
_cli = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _child_process() {
|
|
15
|
+
const data = _interopRequireDefault(require("child_process"));
|
|
16
|
+
_child_process = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _stripAnsi() {
|
|
22
|
+
const data = _interopRequireDefault(require("strip-ansi"));
|
|
23
|
+
_stripAnsi = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _fsExtra() {
|
|
29
|
+
const data = _interopRequireDefault(require("fs-extra"));
|
|
30
|
+
_fsExtra = function () {
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function _commentJson() {
|
|
36
|
+
const data = require("comment-json");
|
|
37
|
+
_commentJson = function () {
|
|
38
|
+
return data;
|
|
39
|
+
};
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
function _path() {
|
|
43
|
+
const data = _interopRequireDefault(require("path"));
|
|
44
|
+
_path = function () {
|
|
45
|
+
return data;
|
|
46
|
+
};
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
function _cliMcpServer() {
|
|
50
|
+
const data = require("./cli-mcp-server.aspect");
|
|
51
|
+
_cliMcpServer = function () {
|
|
52
|
+
return data;
|
|
53
|
+
};
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
function _mcpServer() {
|
|
57
|
+
const data = require("./mcp-server.cmd");
|
|
58
|
+
_mcpServer = function () {
|
|
59
|
+
return data;
|
|
60
|
+
};
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
function _mcp() {
|
|
64
|
+
const data = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
65
|
+
_mcp = function () {
|
|
66
|
+
return data;
|
|
67
|
+
};
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
function _stdio() {
|
|
71
|
+
const data = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
72
|
+
_stdio = function () {
|
|
73
|
+
return data;
|
|
74
|
+
};
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
function _zod() {
|
|
78
|
+
const data = require("zod");
|
|
79
|
+
_zod = function () {
|
|
80
|
+
return data;
|
|
81
|
+
};
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
function _logger() {
|
|
85
|
+
const data = require("@teambit/logger");
|
|
86
|
+
_logger = function () {
|
|
87
|
+
return data;
|
|
88
|
+
};
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
function _scope() {
|
|
92
|
+
const data = require("@teambit/scope.network");
|
|
93
|
+
_scope = function () {
|
|
94
|
+
return data;
|
|
95
|
+
};
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
function _scopeModules() {
|
|
99
|
+
const data = require("@teambit/scope.modules.find-scope-path");
|
|
100
|
+
_scopeModules = function () {
|
|
101
|
+
return data;
|
|
102
|
+
};
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
function _legacy() {
|
|
106
|
+
const data = require("@teambit/legacy.constants");
|
|
107
|
+
_legacy = function () {
|
|
108
|
+
return data;
|
|
109
|
+
};
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
function _nodeFetch() {
|
|
113
|
+
const data = _interopRequireDefault(require("node-fetch"));
|
|
114
|
+
_nodeFetch = function () {
|
|
115
|
+
return data;
|
|
116
|
+
};
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
function _setupCmd() {
|
|
120
|
+
const data = require("./setup-cmd");
|
|
121
|
+
_setupCmd = function () {
|
|
122
|
+
return data;
|
|
123
|
+
};
|
|
124
|
+
return data;
|
|
125
|
+
}
|
|
126
|
+
function _rulesCmd() {
|
|
127
|
+
const data = require("./rules-cmd");
|
|
128
|
+
_rulesCmd = function () {
|
|
129
|
+
return data;
|
|
130
|
+
};
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
function _remoteComponentUtils() {
|
|
134
|
+
const data = require("./remote-component-utils");
|
|
135
|
+
_remoteComponentUtils = function () {
|
|
136
|
+
return data;
|
|
137
|
+
};
|
|
138
|
+
return data;
|
|
139
|
+
}
|
|
140
|
+
function _mcp2() {
|
|
141
|
+
const data = require("@teambit/mcp.mcp-config-writer");
|
|
142
|
+
_mcp2 = function () {
|
|
143
|
+
return data;
|
|
144
|
+
};
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
148
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
149
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
150
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
151
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
152
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable import/extensions */ /* eslint-disable import/no-unresolved */
|
|
153
|
+
class CliMcpServerMain {
|
|
154
|
+
constructor(cli, logger) {
|
|
155
|
+
this.cli = cli;
|
|
156
|
+
this.logger = logger;
|
|
157
|
+
_defineProperty(this, "bitBin", 'bit');
|
|
158
|
+
_defineProperty(this, "_http", void 0);
|
|
159
|
+
_defineProperty(this, "isConsumerProjectMode", false);
|
|
160
|
+
_defineProperty(this, "serverPort", void 0);
|
|
161
|
+
_defineProperty(this, "serverUrl", void 0);
|
|
162
|
+
_defineProperty(this, "serverToken", void 0);
|
|
163
|
+
_defineProperty(this, "serverProcess", null);
|
|
164
|
+
// In-flight init promise so concurrent callers share a single bit-server
|
|
165
|
+
// discovery/spawn. Without this, parallel tool calls race in the
|
|
166
|
+
// `if (!this.serverPort)` block below — each spawns its own bit-server, the
|
|
167
|
+
// second `writeServerToken` overwrites the first, and the earlier caller's
|
|
168
|
+
// cached token no longer matches its server (401 "requires authentication").
|
|
169
|
+
_defineProperty(this, "serverInitPromise", void 0);
|
|
170
|
+
// Whitelist of commands that are considered read-only/query operations
|
|
171
|
+
_defineProperty(this, "readOnlyCommands", new Set(['status', 'list', 'info', 'show', 'schema', 'artifacts', 'diff', 'log', 'graph', 'deps get', 'deps blame', 'why', 'config get', 'envs list', 'envs get', 'remote list', 'templates', 'cat-component', 'cat-lane', 'cat-object', 'cat-scope', 'lane show', 'lane list', 'lane diff', 'lane history', 'lane history-diff', 'test', 'help', 'version']));
|
|
172
|
+
// Validate the default bitBin on construction
|
|
173
|
+
this.bitBin = this.validateBitBin(this.bitBin);
|
|
174
|
+
}
|
|
175
|
+
async getHttp() {
|
|
176
|
+
if (!this._http) {
|
|
177
|
+
this._http = await _scope().Http.connect(_legacy().SYMPHONY_GRAPHQL, _legacy().CENTRAL_BIT_HUB_NAME);
|
|
178
|
+
}
|
|
179
|
+
return this._http;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Discover or spawn a bit-server, then read its bearer token. Concurrent
|
|
184
|
+
* callers share a single in-flight promise so they can't race each other
|
|
185
|
+
* into spawning duplicate servers (which would otherwise overwrite the
|
|
186
|
+
* shared `server-token.txt`).
|
|
187
|
+
*/
|
|
188
|
+
async ensureBitServer(cwd) {
|
|
189
|
+
if (this.serverPort) return;
|
|
190
|
+
if (!this.serverInitPromise) {
|
|
191
|
+
this.serverInitPromise = (async () => {
|
|
192
|
+
const existingPort = await this.getBitServerPort(cwd);
|
|
193
|
+
if (existingPort) {
|
|
194
|
+
this.serverPort = existingPort;
|
|
195
|
+
} else {
|
|
196
|
+
this.logger.debug('[MCP-DEBUG] No bit-server found, attempting to start one');
|
|
197
|
+
const startedPort = await this.startBitServer(cwd);
|
|
198
|
+
if (startedPort) this.serverPort = startedPort;
|
|
199
|
+
}
|
|
200
|
+
if (this.serverPort) {
|
|
201
|
+
this.serverUrl = this.buildServerUrl(this.serverPort);
|
|
202
|
+
// bit-server 1.13.166+ requires a bearer token; older versions don't
|
|
203
|
+
// write the token file and getBitServerToken returns undefined.
|
|
204
|
+
this.serverToken = this.getBitServerToken(cwd);
|
|
205
|
+
}
|
|
206
|
+
})().finally(() => {
|
|
207
|
+
// On failure (no port resolved), clear so a later call can retry.
|
|
208
|
+
if (!this.serverPort) this.serverInitPromise = undefined;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
await this.serverInitPromise;
|
|
212
|
+
}
|
|
213
|
+
async getBitServerPort(cwd, skipValidatePortFlag = false) {
|
|
214
|
+
try {
|
|
215
|
+
const args = ['cli-server-port'];
|
|
216
|
+
if (skipValidatePortFlag) {
|
|
217
|
+
args.push(String(skipValidatePortFlag));
|
|
218
|
+
}
|
|
219
|
+
const result = _child_process().default.spawnSync(this.bitBin, args, {
|
|
220
|
+
cwd,
|
|
221
|
+
env: _objectSpread(_objectSpread({}, process.env), {}, {
|
|
222
|
+
BIT_CLI_SERVER: 'true'
|
|
223
|
+
}),
|
|
224
|
+
encoding: 'utf8'
|
|
225
|
+
});
|
|
226
|
+
if (result.error) {
|
|
227
|
+
throw result.error;
|
|
228
|
+
}
|
|
229
|
+
if (result.status !== 0) {
|
|
230
|
+
throw new Error(`Command failed with status ${result.status}: ${result.stderr}`);
|
|
231
|
+
}
|
|
232
|
+
const existingPort = result.stdout.trim();
|
|
233
|
+
if (!existingPort) return undefined;
|
|
234
|
+
return parseInt(existingPort, 10);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
this.logger.error(`[MCP-DEBUG] error getting existing port from bit server at ${cwd}. err: ${err.message}`);
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Build the URL used to dial the local bit-server. Mirrors the api-server's
|
|
243
|
+
* bind host: `BIT_SERVER_HOST` lets hosted environments (e.g. cloud
|
|
244
|
+
* workspaces) reach bit-server on a non-loopback interface. `0.0.0.0` /
|
|
245
|
+
* `::` are bind-only wildcards (not valid destinations) so dial loopback
|
|
246
|
+
* instead; raw IPv6 addresses get bracketed for URL safety.
|
|
247
|
+
*/
|
|
248
|
+
buildServerUrl(port) {
|
|
249
|
+
const override = process.env.BIT_SERVER_HOST?.trim();
|
|
250
|
+
let host;
|
|
251
|
+
if (!override || override === '0.0.0.0') host = '127.0.0.1';else if (override === '::') host = '[::1]';else if (override.includes(':') && override.split(':').length > 2 && !override.startsWith('[')) host = `[${override}]`;else host = override;
|
|
252
|
+
return `http://${host}:${port}/api`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Read the bearer token written by bit-server (1.13.166+) for authenticating
|
|
257
|
+
* to the local HTTP API. Returns undefined for older bit-server versions
|
|
258
|
+
* that don't write a token file (no auth required), or when the workspace
|
|
259
|
+
* has no scope yet. Other read errors (EACCES, etc.) surface so the user
|
|
260
|
+
* sees the real cause instead of a misleading 401.
|
|
261
|
+
*/
|
|
262
|
+
getBitServerToken(cwd) {
|
|
263
|
+
const scopePath = (0, _scopeModules().findScopePath)(cwd);
|
|
264
|
+
if (!scopePath) return undefined;
|
|
265
|
+
const filePath = _path().default.join(scopePath, 'server-token.txt');
|
|
266
|
+
try {
|
|
267
|
+
const token = _fsExtra().default.readFileSync(filePath, 'utf8').trim();
|
|
268
|
+
return token || undefined;
|
|
269
|
+
} catch (err) {
|
|
270
|
+
if (err.code === 'ENOENT') return undefined;
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Start a new bit-server process
|
|
277
|
+
*/
|
|
278
|
+
async startBitServer(cwd) {
|
|
279
|
+
this.logger.debug('[MCP-DEBUG] Starting new bit-server process');
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
try {
|
|
282
|
+
const serverProcess = _child_process().default.spawn(this.bitBin, ['server'], {
|
|
283
|
+
cwd,
|
|
284
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
285
|
+
detached: false
|
|
286
|
+
});
|
|
287
|
+
this.serverProcess = serverProcess;
|
|
288
|
+
let serverStarted = false;
|
|
289
|
+
let outputBuffer = '';
|
|
290
|
+
const timeout = setTimeout(() => {
|
|
291
|
+
if (!serverStarted) {
|
|
292
|
+
this.logger.error('[MCP-DEBUG] Timeout waiting for bit-server to start');
|
|
293
|
+
serverProcess.kill();
|
|
294
|
+
resolve(null);
|
|
295
|
+
}
|
|
296
|
+
}, 30000); // 30 second timeout
|
|
297
|
+
|
|
298
|
+
serverProcess.stdout?.on('data', data => {
|
|
299
|
+
const output = data.toString();
|
|
300
|
+
outputBuffer += output;
|
|
301
|
+
this.logger.debug(`[MCP-DEBUG] bit-server stdout: ${output}`);
|
|
302
|
+
if (output.includes('listening on port')) {
|
|
303
|
+
clearTimeout(timeout);
|
|
304
|
+
if (!serverStarted) {
|
|
305
|
+
serverStarted = true;
|
|
306
|
+
// Extract the port from the output
|
|
307
|
+
const portMatch = output.match(/listening on port (\d+)/);
|
|
308
|
+
if (portMatch && portMatch[1]) {
|
|
309
|
+
const port = parseInt(portMatch[1], 10);
|
|
310
|
+
this.logger.debug(`[MCP-DEBUG] bit-server started on port ${port}`);
|
|
311
|
+
this.serverPort = port;
|
|
312
|
+
this.serverUrl = this.buildServerUrl(port);
|
|
313
|
+
resolve(port);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
serverProcess.stderr?.on('data', data => {
|
|
319
|
+
const error = data.toString();
|
|
320
|
+
outputBuffer += error;
|
|
321
|
+
this.logger.debug(`[MCP-DEBUG] bit-server stderr: ${error}`);
|
|
322
|
+
});
|
|
323
|
+
serverProcess.on('error', err => {
|
|
324
|
+
clearTimeout(timeout);
|
|
325
|
+
this.logger.error(`[MCP-DEBUG] Failed to start bit-server: ${err.message}`);
|
|
326
|
+
reject(err);
|
|
327
|
+
});
|
|
328
|
+
serverProcess.on('exit', (code, signal) => {
|
|
329
|
+
clearTimeout(timeout);
|
|
330
|
+
if (!serverStarted) {
|
|
331
|
+
this.logger.error(`[MCP-DEBUG] bit-server exited with code ${code}, signal ${signal}`);
|
|
332
|
+
this.logger.debug(`[MCP-DEBUG] bit-server output: ${outputBuffer}`);
|
|
333
|
+
resolve(null);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const killServerProcess = () => {
|
|
337
|
+
if (this.serverProcess && !this.serverProcess.killed) {
|
|
338
|
+
this.logger.debug('[MCP-DEBUG] Killing bit-server process');
|
|
339
|
+
this.serverProcess.kill();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Handle process cleanup
|
|
344
|
+
process.on('exit', () => {
|
|
345
|
+
killServerProcess();
|
|
346
|
+
});
|
|
347
|
+
process.on('SIGINT', () => {
|
|
348
|
+
killServerProcess();
|
|
349
|
+
process.exit();
|
|
350
|
+
});
|
|
351
|
+
process.on('SIGTERM', () => {
|
|
352
|
+
killServerProcess();
|
|
353
|
+
process.exit();
|
|
354
|
+
});
|
|
355
|
+
} catch (err) {
|
|
356
|
+
this.logger.error(`[MCP-DEBUG] Error spawning bit-server: ${err.message}`);
|
|
357
|
+
reject(err);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Call bit-server API endpoint using cli-raw route
|
|
364
|
+
*/
|
|
365
|
+
async callBitServerAPI(command, args = [], flags = {}, cwd, isReTrying = false) {
|
|
366
|
+
return this.callBitServerAPIWithRoute('cli-raw', command, args, flags, cwd, isReTrying);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Call bit-server API endpoint using IDE route
|
|
371
|
+
*/
|
|
372
|
+
async callBitServerIDEAPI(method, args = [], cwd, isReTrying = false) {
|
|
373
|
+
return this.callBitServerAPIWithRoute('ide', method, args, {}, cwd, isReTrying, true);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Generic method to call bit-server API with different routes
|
|
378
|
+
*/
|
|
379
|
+
async callBitServerAPIWithRoute(route, commandOrMethod, argsOrParams = [], flags = {}, cwd, isReTrying = false, isIDERoute = false) {
|
|
380
|
+
if (!this.serverPort) {
|
|
381
|
+
if (!cwd) throw new Error('CWD is required to call bit-server API');
|
|
382
|
+
await this.ensureBitServer(cwd);
|
|
383
|
+
}
|
|
384
|
+
if (!this.serverUrl) {
|
|
385
|
+
throw new Error('Unable to connect to bit-server. Please ensure you are in a valid Bit workspace.');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Resolve the real path to handle symlinks (e.g., /tmp -> /private/tmp on macOS)
|
|
389
|
+
const realCwd = _fsExtra().default.realpathSync(cwd);
|
|
390
|
+
let body;
|
|
391
|
+
let url;
|
|
392
|
+
if (isIDERoute) {
|
|
393
|
+
// For IDE route, use the method name and args directly
|
|
394
|
+
body = {
|
|
395
|
+
args: argsOrParams
|
|
396
|
+
};
|
|
397
|
+
url = `${this.serverUrl}/${route}/${commandOrMethod}`;
|
|
398
|
+
} else {
|
|
399
|
+
// For CLI route, build command array with flags
|
|
400
|
+
const commandArray = [commandOrMethod, ...argsOrParams];
|
|
401
|
+
|
|
402
|
+
// Convert flags to command line arguments
|
|
403
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
404
|
+
if (value === true) {
|
|
405
|
+
commandArray.push(`--${key}`);
|
|
406
|
+
} else if (value !== false && value !== undefined) {
|
|
407
|
+
commandArray.push(`--${key}`, String(value));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
body = {
|
|
411
|
+
command: commandArray,
|
|
412
|
+
pwd: realCwd
|
|
413
|
+
};
|
|
414
|
+
url = `${this.serverUrl}/${route}`;
|
|
415
|
+
}
|
|
416
|
+
const headers = {
|
|
417
|
+
'Content-Type': 'application/json'
|
|
418
|
+
};
|
|
419
|
+
if (this.serverToken) headers.Authorization = `Bearer ${this.serverToken}`;
|
|
420
|
+
try {
|
|
421
|
+
const response = await (0, _nodeFetch().default)(url, {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
headers,
|
|
424
|
+
body: JSON.stringify(body)
|
|
425
|
+
});
|
|
426
|
+
if (response.status === 401 && !isReTrying) {
|
|
427
|
+
// Token may be stale (server restarted with a new one); refresh and retry once.
|
|
428
|
+
this.logger.debug('[MCP-DEBUG] 401 from bit-server, refreshing token and retrying');
|
|
429
|
+
this.serverToken = this.getBitServerToken(cwd);
|
|
430
|
+
return this.callBitServerAPIWithRoute(route, commandOrMethod, argsOrParams, flags, cwd, true, isIDERoute);
|
|
431
|
+
}
|
|
432
|
+
if (!response.ok) {
|
|
433
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
434
|
+
try {
|
|
435
|
+
const errorJson = await response.json();
|
|
436
|
+
errorMessage = errorJson.message || errorJson || errorMessage;
|
|
437
|
+
} catch {
|
|
438
|
+
// Ignore JSON parse errors
|
|
439
|
+
}
|
|
440
|
+
throw new Error(errorMessage);
|
|
441
|
+
}
|
|
442
|
+
return await response.json();
|
|
443
|
+
} catch (err) {
|
|
444
|
+
if (err.code === 'ECONNREFUSED' && !isReTrying) {
|
|
445
|
+
// Server is no longer running, reset cached values and try to restart
|
|
446
|
+
this.serverPort = undefined;
|
|
447
|
+
this.serverUrl = undefined;
|
|
448
|
+
this.serverToken = undefined;
|
|
449
|
+
this.serverInitPromise = undefined;
|
|
450
|
+
this.logger.debug('[MCP-DEBUG] Connection refused, attempting to restart bit-server');
|
|
451
|
+
return this.callBitServerAPIWithRoute(route, commandOrMethod, argsOrParams, flags, cwd, true, isIDERoute);
|
|
452
|
+
}
|
|
453
|
+
throw err;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async runMcpServer(options) {
|
|
457
|
+
this.logger.debug(`[MCP-DEBUG] Starting MCP server with options: ${JSON.stringify(options)}. CWD: ${process.cwd()}`);
|
|
458
|
+
const commands = this.cli.commands;
|
|
459
|
+
|
|
460
|
+
// Validate and set bitBin with security checks
|
|
461
|
+
if (options.bitBin) {
|
|
462
|
+
this.bitBin = this.validateBitBin(options.bitBin);
|
|
463
|
+
}
|
|
464
|
+
// If no bitBin provided, keep the default 'bit'
|
|
465
|
+
|
|
466
|
+
// Tools to always exclude
|
|
467
|
+
const alwaysExcludeTools = new Set(['login', 'logout', 'completion', 'mcp-server', 'start', 'run-action', 'watch', 'run', 'resume-export', 'server', 'serve-preview']);
|
|
468
|
+
|
|
469
|
+
// Parse command strings from flag options
|
|
470
|
+
let additionalCommandsSet;
|
|
471
|
+
if (options.includeAdditional) {
|
|
472
|
+
additionalCommandsSet = new Set(options.includeAdditional.split(',').map(cmd => cmd.trim()));
|
|
473
|
+
this.logger.debug(`[MCP-DEBUG] Including additional commands: ${Array.from(additionalCommandsSet).join(', ')}`);
|
|
474
|
+
}
|
|
475
|
+
const server = new (_mcp().McpServer)({
|
|
476
|
+
name: 'bit-cli-mcp',
|
|
477
|
+
version: '0.0.1'
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Set of tools for consumer projects (non-Bit workspaces)
|
|
481
|
+
const consumerProjectTools = new Set();
|
|
482
|
+
const consumerProject = Boolean(options.consumerProject);
|
|
483
|
+
|
|
484
|
+
// Store consumer project mode globally in the class
|
|
485
|
+
this.isConsumerProjectMode = consumerProject;
|
|
486
|
+
|
|
487
|
+
// Validate flags combination
|
|
488
|
+
if (consumerProject) {
|
|
489
|
+
this.logger.debug(`[MCP-DEBUG] Running MCP server in consumer project mode (for non-Bit workspaces) with tools: bit_remote_search, bit_remote_component_details`);
|
|
490
|
+
if (options.includeAdditional) {
|
|
491
|
+
this.logger.debug(`[MCP-DEBUG] Additional tools enabled in consumer project mode: ${options.includeAdditional}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const filterOptions = {
|
|
495
|
+
additionalCommandsSet,
|
|
496
|
+
alwaysExcludeTools,
|
|
497
|
+
consumerProject,
|
|
498
|
+
consumerProjectTools
|
|
499
|
+
};
|
|
500
|
+
commands.forEach(cmd => {
|
|
501
|
+
const cmdName = (0, _cli().getCommandName)(cmd);
|
|
502
|
+
if (this.shouldIncludeCommand(cmdName, filterOptions)) {
|
|
503
|
+
this.registerToolForCommand(server, cmd);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Process sub-commands
|
|
507
|
+
if (cmd.commands && cmd.commands.length) {
|
|
508
|
+
this.processSubCommands(server, cmd, filterOptions);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Always register remote-search tool
|
|
513
|
+
this.registerRemoteSearchTool(server);
|
|
514
|
+
|
|
515
|
+
// In consumer project mode, only register bit_remote_search and bit_remote_component_details
|
|
516
|
+
// All other tools should not be available in consumer project mode
|
|
517
|
+
if (consumerProject) {
|
|
518
|
+
// Register the new combined remote component details tool
|
|
519
|
+
this.registerRemoteComponentDetailsTool(server);
|
|
520
|
+
} else {
|
|
521
|
+
// Register the bit_workspace_info tool
|
|
522
|
+
this.registerWorkspaceInfoTool(server);
|
|
523
|
+
|
|
524
|
+
// Register the bit_component_details tool
|
|
525
|
+
this.registerComponentDetailsTool(server);
|
|
526
|
+
|
|
527
|
+
// Register the bit_create tool
|
|
528
|
+
this.registerCreateTool(server);
|
|
529
|
+
|
|
530
|
+
// Register command discovery and help tools
|
|
531
|
+
this.registerCommandsListTool(server);
|
|
532
|
+
this.registerCommandHelpTool(server);
|
|
533
|
+
this.registerQueryTool(server);
|
|
534
|
+
this.registerExecuteTool(server);
|
|
535
|
+
}
|
|
536
|
+
await server.connect(new (_stdio().StdioServerTransport)());
|
|
537
|
+
}
|
|
538
|
+
shouldIncludeCommand(cmdName, options) {
|
|
539
|
+
// Always exclude certain commands
|
|
540
|
+
if (options.alwaysExcludeTools.has(cmdName)) return false;
|
|
541
|
+
|
|
542
|
+
// Consumer project mode: only include consumer project tools + any additional specified
|
|
543
|
+
if (options.consumerProject) {
|
|
544
|
+
const shouldInclude = options.consumerProjectTools.has(cmdName) || (options.additionalCommandsSet?.has(cmdName) ?? false);
|
|
545
|
+
if (shouldInclude) {
|
|
546
|
+
this.logger.debug(`[MCP-DEBUG] Including command in consumer project mode: ${cmdName}`);
|
|
547
|
+
}
|
|
548
|
+
return shouldInclude;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Default mode: only include additional specified commands (no default tools anymore)
|
|
552
|
+
return options.additionalCommandsSet?.has(cmdName) ?? false;
|
|
553
|
+
}
|
|
554
|
+
buildZodSchema(config) {
|
|
555
|
+
const schema = {
|
|
556
|
+
// Add cwd parameter as mandatory to all commands
|
|
557
|
+
cwd: _zod().z.string().describe('Path to workspace')
|
|
558
|
+
};
|
|
559
|
+
config.argsData.forEach(arg => {
|
|
560
|
+
const desc = arg.description || `Positional argument: ${arg.nameRaw}`;
|
|
561
|
+
if (arg.isArray) {
|
|
562
|
+
schema[arg.nameCamelCase] = arg.required ? _zod().z.array(_zod().z.string()).describe(desc) : _zod().z.array(_zod().z.string()).optional().describe(desc);
|
|
563
|
+
} else {
|
|
564
|
+
schema[arg.nameCamelCase] = arg.required ? _zod().z.string().describe(desc) : _zod().z.string().optional().describe(desc);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
config.flagsData.forEach(flag => {
|
|
568
|
+
const type = flag.type;
|
|
569
|
+
schema[flag.name] = type === 'string' ? _zod().z.string().optional().describe(flag.description) : _zod().z.boolean().optional().describe(flag.description);
|
|
570
|
+
});
|
|
571
|
+
return schema;
|
|
572
|
+
}
|
|
573
|
+
buildCommandArgs(config, params) {
|
|
574
|
+
// Split the command name on spaces to properly handle subcommands
|
|
575
|
+
const args = config.name.split(' ');
|
|
576
|
+
|
|
577
|
+
// Add positional arguments in order
|
|
578
|
+
config.argsData.forEach(arg => {
|
|
579
|
+
const val = params[arg.nameCamelCase];
|
|
580
|
+
if (val === undefined) return;
|
|
581
|
+
if (arg.isArray && Array.isArray(val)) {
|
|
582
|
+
val.forEach(item => args.push(item));
|
|
583
|
+
} else {
|
|
584
|
+
args.push(val);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Add options as flags
|
|
589
|
+
config.flagsData.forEach(flag => {
|
|
590
|
+
const name = flag.name;
|
|
591
|
+
const type = flag.type;
|
|
592
|
+
const val = params[name];
|
|
593
|
+
if (val === undefined) return;
|
|
594
|
+
if (type === 'boolean' && val) {
|
|
595
|
+
args.push(`--${name}`);
|
|
596
|
+
} else if (type === 'string' && val) {
|
|
597
|
+
// Check if the string value contains spaces and quote it if necessary
|
|
598
|
+
const stringValue = String(val);
|
|
599
|
+
if (stringValue.includes(' ')) {
|
|
600
|
+
args.push(`--${name}`, `"${stringValue}"`);
|
|
601
|
+
} else {
|
|
602
|
+
args.push(`--${name}`, stringValue);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
return args;
|
|
607
|
+
}
|
|
608
|
+
getToolName(name) {
|
|
609
|
+
// replace white spaces (\s) and dashes (-) with underscores (_)
|
|
610
|
+
return `bit_${name}`.replace(/[-\s]/g, '_');
|
|
611
|
+
}
|
|
612
|
+
registerToolForCommand(server, cmd, parentCmd) {
|
|
613
|
+
const cmdName = parentCmd ? `${(0, _cli().getCommandName)(parentCmd)} ${(0, _cli().getCommandName)(cmd)}` : (0, _cli().getCommandName)(cmd);
|
|
614
|
+
const toolName = this.getToolName(cmdName);
|
|
615
|
+
|
|
616
|
+
// Modify description for show and schema commands in consumer project mode
|
|
617
|
+
let description = `${cmd.description}${cmd.extendedDescription ? `.\n(${cmd.extendedDescription})` : ''}`;
|
|
618
|
+
if (this.isConsumerProjectMode && (cmdName === 'show' || cmdName === 'schema')) {
|
|
619
|
+
description += `\n(In consumer project mode, --remote flag is automatically added)`;
|
|
620
|
+
}
|
|
621
|
+
const config = {
|
|
622
|
+
name: cmdName,
|
|
623
|
+
description,
|
|
624
|
+
argsData: (0, _cli().getArgsData)(cmd),
|
|
625
|
+
flagsData: (0, _cli().getFlagsData)(cmd)
|
|
626
|
+
};
|
|
627
|
+
const schema = this.buildZodSchema(config);
|
|
628
|
+
server.tool(toolName, config.description, schema, async params => {
|
|
629
|
+
const argsToRun = this.buildCommandArgs(config, params);
|
|
630
|
+
|
|
631
|
+
// Special handling for consumer projects - auto-add --remote flag for show and schema commands
|
|
632
|
+
if (this.isConsumerProjectMode && (cmdName === 'show' || cmdName === 'schema')) {
|
|
633
|
+
if (!argsToRun.includes('--remote')) {
|
|
634
|
+
this.logger.debug(`[MCP-DEBUG] Auto-adding --remote flag for ${cmdName} in consumer project mode`);
|
|
635
|
+
argsToRun.push('--remote');
|
|
636
|
+
}
|
|
637
|
+
if (cmdName === 'show' && !argsToRun.includes('--legacy')) {
|
|
638
|
+
this.logger.debug(`[MCP-DEBUG] Auto-adding --legacy flag for ${cmdName} in consumer project mode`);
|
|
639
|
+
argsToRun.push('--legacy');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return this.runBit(argsToRun, params.cwd);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Read and parse workspace.jsonc file from a given directory
|
|
648
|
+
*/
|
|
649
|
+
async readWorkspaceJsonc(workspaceDir) {
|
|
650
|
+
try {
|
|
651
|
+
const workspaceJsoncPath = _path().default.join(workspaceDir, 'workspace.jsonc');
|
|
652
|
+
const fileExists = await _fsExtra().default.pathExists(workspaceJsoncPath);
|
|
653
|
+
if (!fileExists) {
|
|
654
|
+
this.logger.debug(`[MCP-DEBUG] workspace.jsonc not found at ${workspaceJsoncPath}`);
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
const content = await _fsExtra().default.readFile(workspaceJsoncPath, 'utf-8');
|
|
658
|
+
return (0, _commentJson().parse)(content);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
this.logger.debug(`[MCP-DEBUG] Failed to read workspace.jsonc: ${error}`);
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Extract owner from defaultScope in workspace.jsonc
|
|
667
|
+
* If defaultScope contains a dot, split by dot and take the first part
|
|
668
|
+
*/
|
|
669
|
+
extractOwnerFromWorkspace(workspaceConfig) {
|
|
670
|
+
try {
|
|
671
|
+
const workspaceSection = workspaceConfig?.['teambit.workspace/workspace'];
|
|
672
|
+
const defaultScope = workspaceSection?.defaultScope;
|
|
673
|
+
if (!defaultScope || typeof defaultScope !== 'string') {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// If defaultScope contains a dot, split by dot and take the first part (owner)
|
|
678
|
+
if (defaultScope.includes('.')) {
|
|
679
|
+
const parts = defaultScope.split('.');
|
|
680
|
+
return parts[0];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// If no dot, the entire defaultScope is treated as the owner
|
|
684
|
+
return defaultScope;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
this.logger.debug(`[MCP-DEBUG] Failed to extract owner from workspace config: ${error}`);
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
registerRemoteSearchTool(server) {
|
|
691
|
+
const toolName = 'bit_remote_search';
|
|
692
|
+
const description = `Search for components in remote scopes using parallel queries for efficient discovery. Always provide multiple search terms - either variations/synonyms of one component type, or different components needed for a task. Examples: ["button", "btn", "click"] for variations, or ["input", "button", "validation"] for form components.`;
|
|
693
|
+
const schema = {
|
|
694
|
+
queries: _zod().z.array(_zod().z.string()).describe(`Array of search query strings for parallel searching. Each query should be a single keyword or a few broad keywords. Search for variations/synonyms of one component type, or different components needed for a task. Examples: ["btn", "button"] for variations, or ["table", "pagination", "filter"] for data display features.`),
|
|
695
|
+
cwd: _zod().z.string().optional().describe('Path to workspace directory'),
|
|
696
|
+
owners: _zod().z.array(_zod().z.string()).optional().describe('Filter results by specific owners or organizations. AVOID using this parameter - let the system automatically extract the owner from workspace.jsonc defaultScope for better relevance. Only use when you need to override the automatic behavior or search across different owners'),
|
|
697
|
+
skipAutoOwner: _zod().z.boolean().optional().describe('Set to true to disable automatic owner extraction from workspace.jsonc. When false or omitted, the system will try to automatically extract owner from workspace defaultScope')
|
|
698
|
+
};
|
|
699
|
+
server.tool(toolName, description, schema, async params => {
|
|
700
|
+
const http = await this.getHttp();
|
|
701
|
+
|
|
702
|
+
// Validate that queries parameter is provided and valid
|
|
703
|
+
if (!params.queries || !Array.isArray(params.queries) || params.queries.length === 0) {
|
|
704
|
+
return {
|
|
705
|
+
content: [{
|
|
706
|
+
type: 'text',
|
|
707
|
+
text: 'Error: queries parameter must be provided as a non-empty array of search terms. Example: ["button", "btn", "click"]'
|
|
708
|
+
}]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const searchQueries = params.queries;
|
|
712
|
+
|
|
713
|
+
// Determine the owners to use for the search
|
|
714
|
+
let ownersToUse = params.owners && params.owners.length > 0 ? params.owners : undefined;
|
|
715
|
+
|
|
716
|
+
// If owners not explicitly provided and skipAutoOwner is not true, try to extract from workspace.jsonc
|
|
717
|
+
if (!ownersToUse && !params.skipAutoOwner) {
|
|
718
|
+
try {
|
|
719
|
+
// Use provided cwd parameter or fall back to current working directory
|
|
720
|
+
const workspaceDir = params.cwd || process.cwd();
|
|
721
|
+
this.logger.debug(`[MCP-DEBUG] Attempting to auto-extract owner from workspace.jsonc in: ${workspaceDir}`);
|
|
722
|
+
const workspaceConfig = await this.readWorkspaceJsonc(workspaceDir);
|
|
723
|
+
if (workspaceConfig) {
|
|
724
|
+
const extractedOwner = this.extractOwnerFromWorkspace(workspaceConfig);
|
|
725
|
+
if (extractedOwner) {
|
|
726
|
+
ownersToUse = [extractedOwner];
|
|
727
|
+
this.logger.debug(`[MCP-DEBUG] Auto-extracted owner from workspace.jsonc: ${extractedOwner}`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
this.logger.debug(`[MCP-DEBUG] Failed to auto-extract owner: ${error}`);
|
|
732
|
+
// Continue without auto-extracted owner
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
this.logger.debug(`[MCP-DEBUG] Using provided owners for search: ${ownersToUse ? ownersToUse.join(', ') : 'none'}`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Execute searches in parallel
|
|
739
|
+
this.logger.debug(`[MCP-DEBUG] Executing ${searchQueries.length} search(es) in parallel: ${searchQueries.join(', ')}`);
|
|
740
|
+
try {
|
|
741
|
+
const searchPromises = searchQueries.map(async query => {
|
|
742
|
+
try {
|
|
743
|
+
const result = await http.search(query, ownersToUse);
|
|
744
|
+
return {
|
|
745
|
+
query,
|
|
746
|
+
success: true,
|
|
747
|
+
components: result?.components || [],
|
|
748
|
+
totalCount: result?.components?.length || 0
|
|
749
|
+
};
|
|
750
|
+
} catch (error) {
|
|
751
|
+
this.logger.warn(`[MCP-DEBUG] Search failed for query "${query}": ${error.message}`);
|
|
752
|
+
return {
|
|
753
|
+
query,
|
|
754
|
+
success: false,
|
|
755
|
+
error: error.message,
|
|
756
|
+
components: [],
|
|
757
|
+
totalCount: 0
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
const searchResults = await Promise.all(searchPromises);
|
|
762
|
+
|
|
763
|
+
// Process and consolidate results
|
|
764
|
+
const allComponents = new Set();
|
|
765
|
+
const resultsByQuery = [];
|
|
766
|
+
let totalComponentsFound = 0;
|
|
767
|
+
searchResults.forEach(result => {
|
|
768
|
+
resultsByQuery.push({
|
|
769
|
+
query: result.query,
|
|
770
|
+
success: result.success,
|
|
771
|
+
count: result.totalCount,
|
|
772
|
+
error: result.error || undefined
|
|
773
|
+
});
|
|
774
|
+
if (result.success && result.components.length > 0) {
|
|
775
|
+
result.components.forEach(component => allComponents.add(component));
|
|
776
|
+
totalComponentsFound += result.totalCount;
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// Format the consolidated results
|
|
781
|
+
if (allComponents.size === 0) {
|
|
782
|
+
const failedQueries = searchResults.filter(r => !r.success);
|
|
783
|
+
let message = 'No results found';
|
|
784
|
+
if (failedQueries.length > 0) {
|
|
785
|
+
message += `\n\nSome searches failed:`;
|
|
786
|
+
failedQueries.forEach(r => {
|
|
787
|
+
message += `\n- "${r.query}": ${r.error}`;
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
content: [{
|
|
792
|
+
type: 'text',
|
|
793
|
+
text: message
|
|
794
|
+
}]
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Build the response text
|
|
799
|
+
let responseText = `Found ${allComponents.size} unique components from ${searchQueries.length} parallel search${searchQueries.length > 1 ? 'es' : ''}:\n\n`;
|
|
800
|
+
|
|
801
|
+
// Show per-query results summary
|
|
802
|
+
responseText += 'Search Summary:\n';
|
|
803
|
+
resultsByQuery.forEach(result => {
|
|
804
|
+
if (result.success) {
|
|
805
|
+
responseText += `- "${result.query}": ${result.count} components\n`;
|
|
806
|
+
} else {
|
|
807
|
+
responseText += `- "${result.query}": FAILED (${result.error})\n`;
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
responseText += '\nConsolidated Results:\n';
|
|
811
|
+
responseText += Array.from(allComponents).join('\n');
|
|
812
|
+
const formattedResults = {
|
|
813
|
+
type: 'text',
|
|
814
|
+
text: responseText
|
|
815
|
+
};
|
|
816
|
+
this.logger.debug(`[MCP-DEBUG] Consolidated search results: ${allComponents.size} unique components from ${totalComponentsFound} total results`);
|
|
817
|
+
return {
|
|
818
|
+
content: [formattedResults]
|
|
819
|
+
};
|
|
820
|
+
} catch (error) {
|
|
821
|
+
this.logger.error(`[MCP-DEBUG] Error during parallel search execution: ${error.message}`);
|
|
822
|
+
return {
|
|
823
|
+
content: [{
|
|
824
|
+
type: 'text',
|
|
825
|
+
text: `Error executing searches: ${error.message}`
|
|
826
|
+
}]
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
registerRemoteComponentDetailsTool(server) {
|
|
832
|
+
const toolName = 'bit_remote_component_details';
|
|
833
|
+
const description = 'Get detailed information about a remote component including basic info, docs, usage examples, and public API schema. Uses global scope to provide comprehensive component details.';
|
|
834
|
+
const schema = {
|
|
835
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
836
|
+
componentName: _zod().z.string().describe('Component name or component ID to get details for'),
|
|
837
|
+
includeSchema: _zod().z.boolean().optional().describe('Include component public API schema (default: false)')
|
|
838
|
+
};
|
|
839
|
+
server.tool(toolName, description, schema, async params => {
|
|
840
|
+
try {
|
|
841
|
+
const {
|
|
842
|
+
componentName,
|
|
843
|
+
includeSchema = false,
|
|
844
|
+
cwd
|
|
845
|
+
} = params;
|
|
846
|
+
|
|
847
|
+
// Get enhanced details using remote.show()
|
|
848
|
+
const result = await (0, _remoteComponentUtils().getRemoteComponentWithDetails)(componentName);
|
|
849
|
+
|
|
850
|
+
// Add schema if requested
|
|
851
|
+
if (includeSchema) {
|
|
852
|
+
try {
|
|
853
|
+
const schemaArgs = ['schema', componentName, '--remote'];
|
|
854
|
+
const schemaResult = await this.runBit(schemaArgs, cwd);
|
|
855
|
+
const content = schemaResult.content[0];
|
|
856
|
+
if (content.type !== 'text') {
|
|
857
|
+
throw new Error(`Expected text content but got ${content.type}`);
|
|
858
|
+
}
|
|
859
|
+
result.publicAPI = content.text;
|
|
860
|
+
} catch (schemaError) {
|
|
861
|
+
this.logger.warn(`[MCP-DEBUG] Failed to get schema for ${componentName}: ${schemaError.message}`);
|
|
862
|
+
result.publicAPI = `Error fetching schema: ${schemaError.message}`;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return this.formatAsCallToolResult(result);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_remote_component_details tool: ${error.message}`);
|
|
868
|
+
return this.formatErrorAsCallToolResult(error, 'getting remote component details');
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
registerWorkspaceInfoTool(server) {
|
|
873
|
+
const toolName = 'bit_workspace_info';
|
|
874
|
+
const description = 'Get comprehensive workspace information including status, components list, apps, templates, dependency graph, and workspace dependencies';
|
|
875
|
+
const schema = {
|
|
876
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
877
|
+
includeTemplates: _zod().z.boolean().optional().describe('Include templates list (default: false)'),
|
|
878
|
+
includeApps: _zod().z.boolean().optional().describe('Include apps list (default: false)'),
|
|
879
|
+
includeGraph: _zod().z.boolean().optional().describe('Include dependency graph (default: false)'),
|
|
880
|
+
json: _zod().z.boolean().optional().describe('Return output in JSON format - WARNING: This produces verbose output and should be used when structured data is specifically needed (default: false)')
|
|
881
|
+
};
|
|
882
|
+
server.tool(toolName, description, schema, async params => {
|
|
883
|
+
try {
|
|
884
|
+
const includeTemplates = params.includeTemplates === true; // default: false
|
|
885
|
+
const includeApps = params.includeApps === true;
|
|
886
|
+
const includeGraph = params.includeGraph === true;
|
|
887
|
+
const useJson = params.json === true;
|
|
888
|
+
const workspaceInfo = {};
|
|
889
|
+
const flags = useJson ? {
|
|
890
|
+
json: true
|
|
891
|
+
} : {};
|
|
892
|
+
const statusExecution = await this.safeBitCommandExecution('status', [], flags, params.cwd, 'get workspace status', true);
|
|
893
|
+
workspaceInfo.status = statusExecution.result;
|
|
894
|
+
const listExecution = await this.safeBitCommandExecution('list', [], flags, params.cwd, 'get components list', true);
|
|
895
|
+
workspaceInfo.list = listExecution.result;
|
|
896
|
+
|
|
897
|
+
// Get apps list if requested
|
|
898
|
+
if (includeApps) {
|
|
899
|
+
const appsExecution = await this.safeBitCommandExecution('app', ['list'], flags, params.cwd, 'get apps list', true);
|
|
900
|
+
workspaceInfo.apps = appsExecution.result;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Get templates list if requested
|
|
904
|
+
if (includeTemplates) {
|
|
905
|
+
const templatesExecution = await this.safeBitCommandExecution('templates', [], flags, params.cwd, 'get templates list', true);
|
|
906
|
+
workspaceInfo.templates = templatesExecution.result;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Get dependency graph if requested
|
|
910
|
+
if (includeGraph) {
|
|
911
|
+
const graphExecution = await this.safeBitCommandExecution('graph', [], {
|
|
912
|
+
json: true
|
|
913
|
+
}, params.cwd, 'get dependency graph', true);
|
|
914
|
+
workspaceInfo.graph = graphExecution.result;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Get workspace dependencies if requested
|
|
918
|
+
try {
|
|
919
|
+
const workspaceDependencies = await this.callBitServerIDEAPI('getWorkspaceDependencies', [], params.cwd);
|
|
920
|
+
workspaceInfo.workspaceDependencies = workspaceDependencies;
|
|
921
|
+
} catch (error) {
|
|
922
|
+
this.logger.error(`[MCP-DEBUG] Error getting workspace dependencies: ${error.message}`);
|
|
923
|
+
workspaceInfo.workspaceDependencies = {
|
|
924
|
+
error: `Failed to get workspace dependencies: ${error.message}`
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Get current lane name with scope
|
|
929
|
+
try {
|
|
930
|
+
const laneId = await this.callBitServerIDEAPI('getCurrentLaneName', [true], params.cwd);
|
|
931
|
+
workspaceInfo.laneId = laneId;
|
|
932
|
+
} catch (error) {
|
|
933
|
+
this.logger.error(`[MCP-DEBUG] Error getting current lane name: ${error.message}`);
|
|
934
|
+
workspaceInfo.laneId = {
|
|
935
|
+
error: `Failed to get current lane name: ${error.message}`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
return this.formatAsCallToolResult(workspaceInfo);
|
|
939
|
+
} catch (error) {
|
|
940
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_workspace_info tool: ${error.message}`);
|
|
941
|
+
return this.formatErrorAsCallToolResult(error, 'getting workspace info');
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
registerComponentDetailsTool(server) {
|
|
946
|
+
const toolName = 'bit_component_details';
|
|
947
|
+
const description = 'Get detailed information about multiple components in parallel. Supports batch requests for efficient component discovery. Limited to 5 components max to prevent context overload.';
|
|
948
|
+
const schema = {
|
|
949
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
950
|
+
componentIds: _zod().z.array(_zod().z.string()).describe('Array of component IDs to get details for. Limited to 5 components max. Examples: ["acme.design/ui/button", "acme.design/forms/input", "teambit.base-ui/navigation/link"]'),
|
|
951
|
+
includeSchema: _zod().z.boolean().optional().describe('Include component public API schema (default: false)')
|
|
952
|
+
};
|
|
953
|
+
server.tool(toolName, description, schema, async params => {
|
|
954
|
+
try {
|
|
955
|
+
// Schema validation is handled automatically by the SDK
|
|
956
|
+
const includeSchema = params.includeSchema === true;
|
|
957
|
+
const componentIds = params.componentIds;
|
|
958
|
+
|
|
959
|
+
// Check limit of 5 components to prevent context overload
|
|
960
|
+
const maxComponents = 5;
|
|
961
|
+
if (componentIds.length > maxComponents) {
|
|
962
|
+
return {
|
|
963
|
+
content: [{
|
|
964
|
+
type: 'text',
|
|
965
|
+
text: `Error: Too many components requested. Maximum allowed is ${maxComponents} components, but ${componentIds.length} were requested. Please reduce the number of components in your request.`
|
|
966
|
+
}]
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Execute component detail requests in parallel
|
|
971
|
+
this.logger.debug(`[MCP-DEBUG] Fetching details for ${componentIds.length} component(s) in parallel: ${componentIds.join(', ')}`);
|
|
972
|
+
const detailPromises = componentIds.map(async componentId => {
|
|
973
|
+
try {
|
|
974
|
+
const result = await this.callBitServerIDEAPI('getCompDetails', [componentId, includeSchema], params.cwd);
|
|
975
|
+
return {
|
|
976
|
+
componentId,
|
|
977
|
+
success: true,
|
|
978
|
+
details: result
|
|
979
|
+
};
|
|
980
|
+
} catch (error) {
|
|
981
|
+
this.logger.warn(`[MCP-DEBUG] Failed to get details for component "${componentId}": ${error.message}`);
|
|
982
|
+
return {
|
|
983
|
+
componentId,
|
|
984
|
+
success: false,
|
|
985
|
+
error: error.message
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
const detailResults = await Promise.all(detailPromises);
|
|
990
|
+
|
|
991
|
+
// Process and consolidate results
|
|
992
|
+
const successfulResults = detailResults.filter(r => r.success);
|
|
993
|
+
const failedResults = detailResults.filter(r => !r.success);
|
|
994
|
+
if (successfulResults.length === 0) {
|
|
995
|
+
let message = 'Failed to get details for any components';
|
|
996
|
+
if (failedResults.length > 0) {
|
|
997
|
+
message += '\n\nErrors:';
|
|
998
|
+
failedResults.forEach(r => {
|
|
999
|
+
message += `\n- "${r.componentId}": ${r.error}`;
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
content: [{
|
|
1004
|
+
type: 'text',
|
|
1005
|
+
text: message
|
|
1006
|
+
}]
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Build the response
|
|
1011
|
+
const response = {
|
|
1012
|
+
summary: {
|
|
1013
|
+
requested: componentIds.length,
|
|
1014
|
+
successful: successfulResults.length,
|
|
1015
|
+
failed: failedResults.length,
|
|
1016
|
+
includeSchema
|
|
1017
|
+
},
|
|
1018
|
+
components: {}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// Add successful component details
|
|
1022
|
+
successfulResults.forEach(result => {
|
|
1023
|
+
response.components[result.componentId] = result.details;
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// Add failed components info if any
|
|
1027
|
+
if (failedResults.length > 0) {
|
|
1028
|
+
response.failures = {};
|
|
1029
|
+
failedResults.forEach(result => {
|
|
1030
|
+
response.failures[result.componentId] = result.error;
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
this.logger.debug(`[MCP-DEBUG] Component details fetched: ${successfulResults.length} successful, ${failedResults.length} failed`);
|
|
1034
|
+
return this.formatAsCallToolResult(response);
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_component_details tool: ${error.message}`);
|
|
1037
|
+
return this.formatErrorAsCallToolResult(error, 'getting component details');
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
registerCreateTool(server) {
|
|
1042
|
+
const toolName = 'bit_create';
|
|
1043
|
+
const description = 'Create a new component (source files and config) using a template.';
|
|
1044
|
+
const schema = {
|
|
1045
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
1046
|
+
templateName: _zod().z.string().describe('The template for generating the component (run "bit templates" for a list of available templates)'),
|
|
1047
|
+
componentNames: _zod().z.array(_zod().z.string()).describe('A list of component names to generate'),
|
|
1048
|
+
namespace: _zod().z.string().optional().describe("Sets the component's namespace and nested dirs inside the scope"),
|
|
1049
|
+
scope: _zod().z.string().optional().describe("Sets the component's scope-name. If not entered, the default-scope will be used"),
|
|
1050
|
+
aspect: _zod().z.string().optional().describe('Aspect-id of the template. Helpful when multiple aspects use the same template name'),
|
|
1051
|
+
template: _zod().z.string().optional().describe('Env-id of the template. Alias for --aspect'),
|
|
1052
|
+
path: _zod().z.string().optional().describe('Relative path in the workspace. By default the path is <scope>/<namespace>/<name>'),
|
|
1053
|
+
env: _zod().z.string().optional().describe("Set the component's environment. (overrides the env from variants and the template)"),
|
|
1054
|
+
force: _zod().z.boolean().optional().describe('Replace existing files at the target location')
|
|
1055
|
+
};
|
|
1056
|
+
server.tool(toolName, description, schema, async params => {
|
|
1057
|
+
try {
|
|
1058
|
+
const args = params.componentNames;
|
|
1059
|
+
const flags = {};
|
|
1060
|
+
|
|
1061
|
+
// Add optional flags
|
|
1062
|
+
if (params.namespace) flags.namespace = params.namespace;
|
|
1063
|
+
if (params.scope) flags.scope = params.scope;
|
|
1064
|
+
if (params.aspect) flags.aspect = params.aspect;
|
|
1065
|
+
if (params.template) flags.template = params.template;
|
|
1066
|
+
if (params.path) flags.path = params.path;
|
|
1067
|
+
if (params.env) flags.env = params.env;
|
|
1068
|
+
if (params.force) flags.force = true;
|
|
1069
|
+
|
|
1070
|
+
// Add template name as first argument
|
|
1071
|
+
const allArgs = [params.templateName, ...args];
|
|
1072
|
+
const execution = await this.safeBitCommandExecution('create', allArgs, flags, params.cwd, 'create component');
|
|
1073
|
+
return this.formatAsCallToolResult(execution.result);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_create tool: ${error.message}`);
|
|
1076
|
+
return this.formatErrorAsCallToolResult(error, 'creating component');
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
registerCommandsListTool(server) {
|
|
1081
|
+
const toolName = 'bit_commands_list';
|
|
1082
|
+
const description = 'Get all available Bit commands with descriptions and groups. Use this to discover what commands are available.';
|
|
1083
|
+
const schema = {
|
|
1084
|
+
extendedDescription: _zod().z.boolean().optional().describe('Include extended descriptions for commands (default: false)'),
|
|
1085
|
+
internal: _zod().z.boolean().optional().describe('Include internal/debug commands (default: false)')
|
|
1086
|
+
};
|
|
1087
|
+
server.tool(toolName, description, schema, async params => {
|
|
1088
|
+
try {
|
|
1089
|
+
const {
|
|
1090
|
+
extendedDescription = false,
|
|
1091
|
+
internal = false
|
|
1092
|
+
} = params;
|
|
1093
|
+
const commandsInfo = [];
|
|
1094
|
+
const shouldSkipCommand = cmd => {
|
|
1095
|
+
return Boolean(cmd.private && !internal || cmd.description.startsWith('DEPRECATED'));
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// Build list of all commands
|
|
1099
|
+
this.cli.commands.forEach(cmd => {
|
|
1100
|
+
if (shouldSkipCommand(cmd)) return;
|
|
1101
|
+
const mainCmdName = (0, _cli().getCommandName)(cmd);
|
|
1102
|
+
const groupKey = cmd.group;
|
|
1103
|
+
const commandInfo = {
|
|
1104
|
+
name: mainCmdName,
|
|
1105
|
+
description: cmd.description || ''
|
|
1106
|
+
};
|
|
1107
|
+
if (extendedDescription && cmd.extendedDescription) {
|
|
1108
|
+
commandInfo.extendedDescription = cmd.extendedDescription;
|
|
1109
|
+
}
|
|
1110
|
+
if (groupKey) commandInfo.group = this.cli.groups[groupKey] || groupKey;
|
|
1111
|
+
|
|
1112
|
+
// Add subcommands summary
|
|
1113
|
+
if (cmd.commands && cmd.commands.length > 0) {
|
|
1114
|
+
commandInfo.subcommands = cmd.commands.filter(subCmd => !shouldSkipCommand(subCmd)).map(subCmd => ({
|
|
1115
|
+
name: (0, _cli().getCommandName)(subCmd),
|
|
1116
|
+
description: subCmd.description || ''
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
commandsInfo.push(commandInfo);
|
|
1120
|
+
});
|
|
1121
|
+
commandsInfo.sort((a, b) => a.name.localeCompare(b.name));
|
|
1122
|
+
const result = JSON.stringify({
|
|
1123
|
+
total: commandsInfo.length,
|
|
1124
|
+
commands: commandsInfo
|
|
1125
|
+
}, null, 2);
|
|
1126
|
+
this.logger.debug(`[MCP-DEBUG] Successfully retrieved commands list. Total: ${commandsInfo.length}`);
|
|
1127
|
+
return this.formatAsCallToolResult(result);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_commands_list tool: ${error.message}`);
|
|
1130
|
+
return this.formatErrorAsCallToolResult(error, 'getting commands list');
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
registerCommandHelpTool(server) {
|
|
1135
|
+
const toolName = 'bit_command_help';
|
|
1136
|
+
const description = 'Get detailed help for a specific Bit command including syntax, arguments, flags, and usage examples. Use this to understand exactly how to use a command.';
|
|
1137
|
+
const schema = {
|
|
1138
|
+
command: _zod().z.string().describe('The command name to get help for (e.g., "status", "install", "create")'),
|
|
1139
|
+
subcommand: _zod().z.string().optional().describe('Optional subcommand name (e.g., for "lane show", use command="lane" and subcommand="show")')
|
|
1140
|
+
};
|
|
1141
|
+
server.tool(toolName, description, schema, async params => {
|
|
1142
|
+
try {
|
|
1143
|
+
const {
|
|
1144
|
+
command: requestedCommand,
|
|
1145
|
+
subcommand: requestedSubcommand
|
|
1146
|
+
} = params;
|
|
1147
|
+
let commandInfo = null;
|
|
1148
|
+
const buildDetailedCommandInfo = (cmd, parentName) => {
|
|
1149
|
+
const cmdName = parentName ? `${parentName} ${(0, _cli().getCommandName)(cmd)}` : (0, _cli().getCommandName)(cmd);
|
|
1150
|
+
const info = {
|
|
1151
|
+
name: cmdName,
|
|
1152
|
+
description: cmd.description || '',
|
|
1153
|
+
extendedDescription: cmd.extendedDescription || '',
|
|
1154
|
+
group: cmd.group ? this.cli.groups[cmd.group] || cmd.group : ''
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
// Add arguments information
|
|
1158
|
+
const argsData = (0, _cli().getArgsData)(cmd);
|
|
1159
|
+
if (argsData.length > 0) {
|
|
1160
|
+
info.arguments = argsData.map(arg => ({
|
|
1161
|
+
name: arg.nameRaw,
|
|
1162
|
+
description: arg.description || '',
|
|
1163
|
+
required: arg.required,
|
|
1164
|
+
isArray: arg.isArray
|
|
1165
|
+
}));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Add options/flags information
|
|
1169
|
+
info.options = (0, _cli().getFlagsData)(cmd);
|
|
1170
|
+
|
|
1171
|
+
// Add examples if available
|
|
1172
|
+
if (cmd.examples) {
|
|
1173
|
+
info.examples = cmd.examples;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Add subcommands if available (including private ones for help purposes)
|
|
1177
|
+
if (cmd.commands && cmd.commands.length > 0) {
|
|
1178
|
+
info.subcommands = cmd.commands.map(subCmd => ({
|
|
1179
|
+
name: (0, _cli().getCommandName)(subCmd),
|
|
1180
|
+
description: subCmd.description || ''
|
|
1181
|
+
}));
|
|
1182
|
+
}
|
|
1183
|
+
return info;
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// Search for the requested command
|
|
1187
|
+
this.cli.commands.forEach(cmd => {
|
|
1188
|
+
const mainCmdName = (0, _cli().getCommandName)(cmd);
|
|
1189
|
+
if (requestedSubcommand) {
|
|
1190
|
+
// Looking for a subcommand
|
|
1191
|
+
if (mainCmdName === requestedCommand && cmd.commands) {
|
|
1192
|
+
const subCmd = cmd.commands.find(sub => (0, _cli().getCommandName)(sub) === requestedSubcommand);
|
|
1193
|
+
if (subCmd) {
|
|
1194
|
+
commandInfo = buildDetailedCommandInfo(subCmd, requestedCommand);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
} else {
|
|
1198
|
+
// Looking for a main command
|
|
1199
|
+
if (mainCmdName === requestedCommand) {
|
|
1200
|
+
commandInfo = buildDetailedCommandInfo(cmd);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
if (!commandInfo) {
|
|
1205
|
+
const commandFullName = requestedSubcommand ? `${requestedCommand} ${requestedSubcommand}` : requestedCommand;
|
|
1206
|
+
return this.formatAsCallToolResult(`Command not found: ${commandFullName}`);
|
|
1207
|
+
}
|
|
1208
|
+
const result = JSON.stringify(commandInfo, null, 2);
|
|
1209
|
+
this.logger.debug(`[MCP-DEBUG] Successfully retrieved command help for: ${commandInfo.name}`);
|
|
1210
|
+
return this.formatAsCallToolResult(result);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_command_help tool: ${error.message}`);
|
|
1213
|
+
return this.formatErrorAsCallToolResult(error, 'getting command help');
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
registerQueryTool(server) {
|
|
1218
|
+
const toolName = 'bit_query';
|
|
1219
|
+
const description = 'Execute read-only Bit commands that safely inspect workspace and component state without making modifications. Only whitelisted query commands are allowed for safety.';
|
|
1220
|
+
const schema = {
|
|
1221
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
1222
|
+
command: _zod().z.string().describe('The Bit command to execute (e.g., "status", "show", "list")'),
|
|
1223
|
+
args: _zod().z.array(_zod().z.string()).optional().describe('Arguments to pass to the command'),
|
|
1224
|
+
flags: _zod().z.record(_zod().z.union([_zod().z.string(), _zod().z.boolean()])).optional().describe('Flags to pass to the command as key-value pairs')
|
|
1225
|
+
};
|
|
1226
|
+
server.tool(toolName, description, schema, async params => {
|
|
1227
|
+
try {
|
|
1228
|
+
const {
|
|
1229
|
+
command,
|
|
1230
|
+
args = [],
|
|
1231
|
+
flags = {},
|
|
1232
|
+
cwd
|
|
1233
|
+
} = params;
|
|
1234
|
+
|
|
1235
|
+
// Check if command is in the read-only whitelist
|
|
1236
|
+
// Support both single commands and subcommands (e.g., "lane show")
|
|
1237
|
+
const fullCommand = args.length > 0 ? `${command} ${args[0]}` : command;
|
|
1238
|
+
const isAllowed = this.readOnlyCommands.has(command) || this.readOnlyCommands.has(fullCommand);
|
|
1239
|
+
if (!isAllowed) {
|
|
1240
|
+
const allowedCommands = Array.from(this.readOnlyCommands).sort().join(', ');
|
|
1241
|
+
return this.formatAsCallToolResult(`Error: Command "${command}" is not allowed in query mode. Allowed read-only commands: ${allowedCommands}`);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Build command arguments
|
|
1245
|
+
const commandArgs = [command, ...args];
|
|
1246
|
+
|
|
1247
|
+
// Add flags to arguments
|
|
1248
|
+
Object.entries(flags).forEach(([key, value]) => {
|
|
1249
|
+
if (typeof value === 'boolean' && value) {
|
|
1250
|
+
commandArgs.push(`--${key}`);
|
|
1251
|
+
} else if (typeof value === 'string' && value) {
|
|
1252
|
+
commandArgs.push(`--${key}`);
|
|
1253
|
+
commandArgs.push(value);
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
this.logger.debug(`[MCP-DEBUG] Executing query command: ${command} with args: ${JSON.stringify(commandArgs)}`);
|
|
1257
|
+
const execution = await this.safeBitCommandExecution(command, args, flags, cwd, `execute query command ${command}`);
|
|
1258
|
+
return this.formatAsCallToolResult(execution.result);
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_query tool: ${error.message}`);
|
|
1261
|
+
return this.formatErrorAsCallToolResult(error, 'executing query command');
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
registerExecuteTool(server) {
|
|
1266
|
+
const toolName = 'bit_execute';
|
|
1267
|
+
const description = 'Execute Bit commands that make changes to workspace or components (not read-only).';
|
|
1268
|
+
const schema = {
|
|
1269
|
+
cwd: _zod().z.string().describe('Path to workspace directory'),
|
|
1270
|
+
command: _zod().z.string().describe('The Bit command to execute (e.g., "import", "tag", "export", "remove")'),
|
|
1271
|
+
args: _zod().z.array(_zod().z.string()).optional().describe('Arguments to pass to the command'),
|
|
1272
|
+
flags: _zod().z.record(_zod().z.union([_zod().z.string(), _zod().z.boolean()])).optional().describe('Flags to pass to the command as key-value pairs')
|
|
1273
|
+
};
|
|
1274
|
+
server.tool(toolName, description, schema, async params => {
|
|
1275
|
+
try {
|
|
1276
|
+
let {
|
|
1277
|
+
command,
|
|
1278
|
+
args = []
|
|
1279
|
+
} = params;
|
|
1280
|
+
const {
|
|
1281
|
+
flags = {},
|
|
1282
|
+
cwd
|
|
1283
|
+
} = params;
|
|
1284
|
+
|
|
1285
|
+
// Handle sub-commands: if command has multiple words, move the second word to args
|
|
1286
|
+
const commandParts = command.trim().split(/\s+/);
|
|
1287
|
+
if (commandParts.length > 1) {
|
|
1288
|
+
command = commandParts[0];
|
|
1289
|
+
args = [commandParts[1], ...args];
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Check for lane commands that modify workspace state and require direct user interaction
|
|
1293
|
+
if (command === 'lane') {
|
|
1294
|
+
const subcommand = args[0];
|
|
1295
|
+
if (subcommand === 'switch' || subcommand === 'merge') {
|
|
1296
|
+
return this.formatAsCallToolResult(`Error: The "lane ${subcommand}" command is not available through the MCP server. ` + `This workspace-modifying operation must be run directly in the terminal where the user has full visibility and control.`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Also check for the shorthand 'switch' command (which is an alias for 'lane switch')
|
|
1301
|
+
if (command === 'switch') {
|
|
1302
|
+
return this.formatAsCallToolResult(`Error: The "switch" command (alias for "lane switch") is not available through the MCP server. ` + `This workspace-modifying operation must be run directly in the terminal where the user has full visibility and control.`);
|
|
1303
|
+
}
|
|
1304
|
+
this.logger.debug(`[MCP-DEBUG] Executing command: ${command} with args: ${JSON.stringify(args)} and flags: ${JSON.stringify(flags)}`);
|
|
1305
|
+
const execution = await this.safeBitCommandExecution(command, args, flags, cwd, `execute command ${command}`);
|
|
1306
|
+
return this.formatAsCallToolResult(execution.result);
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
this.logger.error(`[MCP-DEBUG] Error in bit_execute tool: ${error.message}`);
|
|
1309
|
+
return this.formatErrorAsCallToolResult(error, 'executing command');
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
processSubCommands(server, parentCmd, options) {
|
|
1314
|
+
const parentCmdName = (0, _cli().getCommandName)(parentCmd);
|
|
1315
|
+
parentCmd.commands?.forEach(subCmd => {
|
|
1316
|
+
const subCmdName = (0, _cli().getCommandName)(subCmd);
|
|
1317
|
+
const fullCmdName = `${parentCmdName} ${subCmdName}`;
|
|
1318
|
+
if (this.shouldIncludeCommand(fullCmdName, options)) {
|
|
1319
|
+
this.registerToolForCommand(server, subCmd, parentCmd);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
async runBit(args, cwd) {
|
|
1324
|
+
this.logger.debug(`[MCP-DEBUG] Running: ${this.bitBin} ${args.join(' ')} in ${cwd}`);
|
|
1325
|
+
try {
|
|
1326
|
+
const result = _child_process().default.spawnSync(this.bitBin, args, {
|
|
1327
|
+
cwd,
|
|
1328
|
+
env: _objectSpread(_objectSpread({}, process.env), {}, {
|
|
1329
|
+
BIT_DISABLE_SPINNER: '1'
|
|
1330
|
+
}),
|
|
1331
|
+
encoding: 'utf8'
|
|
1332
|
+
});
|
|
1333
|
+
if (result.error) {
|
|
1334
|
+
throw result.error;
|
|
1335
|
+
}
|
|
1336
|
+
if (result.status !== 0) {
|
|
1337
|
+
const errorMessage = result.stderr || `Command failed with status ${result.status}`;
|
|
1338
|
+
throw new Error(errorMessage);
|
|
1339
|
+
}
|
|
1340
|
+
this.logger.debug(`[MCP-DEBUG] result. stdout: ${result.stdout}`);
|
|
1341
|
+
return {
|
|
1342
|
+
content: [{
|
|
1343
|
+
type: 'text',
|
|
1344
|
+
text: result.stdout
|
|
1345
|
+
}]
|
|
1346
|
+
};
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
this.logger.error(`[MCP-DEBUG] Error executing ${this.bitBin} ${args.join(' ')}`, error);
|
|
1349
|
+
return {
|
|
1350
|
+
content: [{
|
|
1351
|
+
type: 'text',
|
|
1352
|
+
text: error.message
|
|
1353
|
+
}]
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Helper method to execute a bit-server API call with standardized error handling
|
|
1360
|
+
*/
|
|
1361
|
+
async executeBitServerCommand(command, args = [], flags = {}, cwd, operationName) {
|
|
1362
|
+
try {
|
|
1363
|
+
const result = await this.callBitServerAPI(command, args, flags, cwd);
|
|
1364
|
+
this.logger.debug(`[MCP-DEBUG] Successfully executed ${operationName} via bit-server`);
|
|
1365
|
+
return result;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
this.logger.warn(`[MCP-DEBUG] Failed to execute ${operationName} via bit-server: ${error.message}`);
|
|
1368
|
+
throw error;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Helper method to format any result as CallToolResult
|
|
1374
|
+
*/
|
|
1375
|
+
formatAsCallToolResult(result) {
|
|
1376
|
+
if (typeof result === 'string') {
|
|
1377
|
+
return {
|
|
1378
|
+
content: [{
|
|
1379
|
+
type: 'text',
|
|
1380
|
+
text: result
|
|
1381
|
+
}]
|
|
1382
|
+
};
|
|
1383
|
+
} else if (typeof result === 'object') {
|
|
1384
|
+
return {
|
|
1385
|
+
content: [{
|
|
1386
|
+
type: 'text',
|
|
1387
|
+
text: JSON.stringify(result, null, 2)
|
|
1388
|
+
}]
|
|
1389
|
+
};
|
|
1390
|
+
} else {
|
|
1391
|
+
return {
|
|
1392
|
+
content: [{
|
|
1393
|
+
type: 'text',
|
|
1394
|
+
text: String(result)
|
|
1395
|
+
}]
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Helper method to format error as CallToolResult
|
|
1402
|
+
*/
|
|
1403
|
+
formatErrorAsCallToolResult(error, operation) {
|
|
1404
|
+
return {
|
|
1405
|
+
content: [{
|
|
1406
|
+
type: 'text',
|
|
1407
|
+
text: `Error ${operation}: ${error.message}`
|
|
1408
|
+
}]
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Helper method to safely execute a bit command with error handling
|
|
1414
|
+
*/
|
|
1415
|
+
async safeBitCommandExecution(command, args = [], flags = {}, cwd, operationName, includeErrorInResult = false) {
|
|
1416
|
+
try {
|
|
1417
|
+
let result = await this.executeBitServerCommand(command, args, flags, cwd, operationName);
|
|
1418
|
+
|
|
1419
|
+
// Unwrap the result from data wrapper if it exists and we have a successful result
|
|
1420
|
+
if (result && typeof result === 'object' && 'data' in result && result.data !== undefined) {
|
|
1421
|
+
result = result.data;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Clean up output by removing ANSI color codes when not using JSON format
|
|
1425
|
+
const useJson = flags && flags.json === true;
|
|
1426
|
+
if (!useJson && typeof result === 'string') {
|
|
1427
|
+
result = (0, _stripAnsi().default)(result);
|
|
1428
|
+
}
|
|
1429
|
+
return {
|
|
1430
|
+
success: true,
|
|
1431
|
+
result
|
|
1432
|
+
};
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
if (includeErrorInResult) {
|
|
1435
|
+
return {
|
|
1436
|
+
success: false,
|
|
1437
|
+
result: {
|
|
1438
|
+
error: `Failed to ${operationName}: ${error.message}`
|
|
1439
|
+
},
|
|
1440
|
+
error: error.message
|
|
1441
|
+
};
|
|
1442
|
+
} else {
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
validateBitBin(bitBin) {
|
|
1448
|
+
const trimmed = bitBin?.trim();
|
|
1449
|
+
// Check for shell metacharacters and spaces. Protect against command injection.
|
|
1450
|
+
if (!trimmed || /[;&|`$(){}[\]<>'"\\]/.test(trimmed) || /\s/.test(trimmed)) throw new Error('Invalid bitBin');
|
|
1451
|
+
return trimmed;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// Setup command business logic methods
|
|
1455
|
+
getEditorDisplayName(editor) {
|
|
1456
|
+
return _mcp2().McpConfigWriter.getEditorDisplayName(editor);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Get the path to the editor config file based on editor type and scope
|
|
1461
|
+
*/
|
|
1462
|
+
getEditorConfigPath(editor, isGlobal, workspaceDir) {
|
|
1463
|
+
return _mcp2().McpConfigWriter.getEditorConfigPath(editor, isGlobal, workspaceDir);
|
|
1464
|
+
}
|
|
1465
|
+
async setupEditor(editor, options, workspaceDir) {
|
|
1466
|
+
// Add workspaceDir to options if provided
|
|
1467
|
+
const setupOptions = _objectSpread({}, options);
|
|
1468
|
+
if (workspaceDir) {
|
|
1469
|
+
setupOptions.workspaceDir = workspaceDir;
|
|
1470
|
+
}
|
|
1471
|
+
await _mcp2().McpConfigWriter.setupEditor(editor, setupOptions);
|
|
1472
|
+
}
|
|
1473
|
+
async writeRulesFile(editor, options, workspaceDir) {
|
|
1474
|
+
// Add workspaceDir to options if provided
|
|
1475
|
+
const rulesOptions = _objectSpread({}, options);
|
|
1476
|
+
if (workspaceDir) {
|
|
1477
|
+
rulesOptions.workspaceDir = workspaceDir;
|
|
1478
|
+
}
|
|
1479
|
+
await _mcp2().McpConfigWriter.writeRulesFile(editor, rulesOptions);
|
|
1480
|
+
}
|
|
1481
|
+
async getRulesContent(consumerProject = false, forceStandard = false) {
|
|
1482
|
+
return _mcp2().McpConfigWriter.getDefaultRulesContent(consumerProject, process.cwd(), forceStandard);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Get the path to the rules file based on editor type and scope
|
|
1487
|
+
*/
|
|
1488
|
+
getRulesFilePath(editor, isGlobal, workspaceDir) {
|
|
1489
|
+
const editorLower = editor.toLowerCase();
|
|
1490
|
+
if (editorLower === 'vscode') {
|
|
1491
|
+
return _mcp2().McpConfigWriter.getVSCodePromptsPath(isGlobal, workspaceDir);
|
|
1492
|
+
} else if (editorLower === 'cursor') {
|
|
1493
|
+
return _mcp2().McpConfigWriter.getCursorPromptsPath(isGlobal, workspaceDir);
|
|
1494
|
+
} else if (editorLower === 'roo') {
|
|
1495
|
+
return _mcp2().McpConfigWriter.getRooCodePromptsPath(isGlobal, workspaceDir);
|
|
1496
|
+
} else if (editorLower === 'cline') {
|
|
1497
|
+
return _mcp2().McpConfigWriter.getClinePromptsPath(isGlobal, workspaceDir);
|
|
1498
|
+
} else if (editorLower === 'claude-code') {
|
|
1499
|
+
return _mcp2().McpConfigWriter.getClaudeCodePromptsPath(isGlobal, workspaceDir);
|
|
1500
|
+
}
|
|
1501
|
+
throw new Error(`Editor "${editor}" is not supported yet for rules files.`);
|
|
1502
|
+
}
|
|
1503
|
+
static async provider([cli, loggerMain]) {
|
|
1504
|
+
const logger = loggerMain.createLogger(_cliMcpServer().CliMcpServerAspect.id);
|
|
1505
|
+
const mcpServer = new CliMcpServerMain(cli, logger);
|
|
1506
|
+
const mcpServerCmd = new (_mcpServer().McpServerCmd)(mcpServer);
|
|
1507
|
+
mcpServerCmd.commands = [new (_mcpServer().McpStartCmd)(mcpServer), new (_setupCmd().McpSetupCmd)(mcpServer), new (_rulesCmd().McpRulesCmd)(mcpServer)];
|
|
1508
|
+
cli.register(mcpServerCmd);
|
|
1509
|
+
return mcpServer;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
exports.CliMcpServerMain = CliMcpServerMain;
|
|
1513
|
+
_defineProperty(CliMcpServerMain, "slots", []);
|
|
1514
|
+
_defineProperty(CliMcpServerMain, "dependencies", [_cli().CLIAspect, _logger().LoggerAspect]);
|
|
1515
|
+
_defineProperty(CliMcpServerMain, "runtime", _cli().MainRuntime);
|
|
1516
|
+
_cliMcpServer().CliMcpServerAspect.addRuntime(CliMcpServerMain);
|
|
1517
|
+
var _default = exports.default = CliMcpServerMain;
|
|
1518
|
+
|
|
1519
|
+
//# sourceMappingURL=cli-mcp-server.main.runtime.js.map
|