@tyvm/knowhow 0.0.117 → 0.0.118
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/package.json +1 -1
- package/src/cli.ts +2 -0
- package/src/commands/mcp.ts +742 -0
- package/src/services/Mcp.ts +3 -1
- package/src/types.ts +1 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/cli.js +2 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/commands/mcp.d.ts +2 -0
- package/ts_build/src/commands/mcp.js +664 -0
- package/ts_build/src/commands/mcp.js.map +1 -0
- package/ts_build/src/services/Mcp.js +2 -1
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/types.d.ts +1 -0
- package/ts_build/src/types.js.map +1 -1
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.addMcpCommands = addMcpCommands;
|
|
40
|
+
const config_1 = require("../config");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const KnowhowClient_1 = require("../services/KnowhowClient");
|
|
43
|
+
const Mcp_1 = require("../services/Mcp");
|
|
44
|
+
const http_1 = __importDefault(require("../utils/http"));
|
|
45
|
+
function inferTransport(opts) {
|
|
46
|
+
if (opts.transport)
|
|
47
|
+
return opts.transport;
|
|
48
|
+
if (opts.url) {
|
|
49
|
+
if (opts.url.endsWith("/sse") || opts.url.includes("/sse?"))
|
|
50
|
+
return "sse";
|
|
51
|
+
return "http";
|
|
52
|
+
}
|
|
53
|
+
return "stdio";
|
|
54
|
+
}
|
|
55
|
+
function formatLocalMcpList(mcps, statusMap = {}) {
|
|
56
|
+
if (!mcps || mcps.length === 0) {
|
|
57
|
+
console.log("No MCP servers configured locally.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
61
|
+
console.log(" Local MCP Servers");
|
|
62
|
+
console.log(`${"─".repeat(60)}`);
|
|
63
|
+
for (const mcp of mcps) {
|
|
64
|
+
const status = statusMap[mcp.name];
|
|
65
|
+
const transport = inferTransport(mcp);
|
|
66
|
+
const endpoint = mcp.url ? mcp.url : mcp.command ? `${mcp.command} ${(mcp.args || []).join(" ")}`.trim() : "(none)";
|
|
67
|
+
let statusIcon = "○";
|
|
68
|
+
let statusText = "not tested";
|
|
69
|
+
if (status) {
|
|
70
|
+
if (status.connected) {
|
|
71
|
+
statusIcon = "✓";
|
|
72
|
+
statusText = `connected (${status.toolCount} tools)`;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
statusIcon = "✗";
|
|
76
|
+
statusText = `failed: ${status.error || "unknown error"}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(`\n ${statusIcon} ${mcp.name}`);
|
|
80
|
+
console.log(` transport : ${transport}`);
|
|
81
|
+
console.log(` endpoint : ${endpoint}`);
|
|
82
|
+
if (mcp.authorization_token_file) {
|
|
83
|
+
console.log(` auth file : ${mcp.authorization_token_file}`);
|
|
84
|
+
}
|
|
85
|
+
if (mcp.autoConnect === false) {
|
|
86
|
+
console.log(` auto-connect: false`);
|
|
87
|
+
}
|
|
88
|
+
if (status) {
|
|
89
|
+
console.log(` status : ${statusText}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
console.log(`${"─".repeat(60)}\n`);
|
|
93
|
+
}
|
|
94
|
+
function formatRemoteMcpList(servers) {
|
|
95
|
+
if (!servers || servers.length === 0) {
|
|
96
|
+
console.log("No remote MCP servers configured for this organisation.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
100
|
+
console.log(" Remote MCP Servers (backend)");
|
|
101
|
+
console.log(`${"─".repeat(60)}`);
|
|
102
|
+
for (const s of servers) {
|
|
103
|
+
const transport = s.url ? (s.url.endsWith("/sse") ? "sse" : "http") : "stdio";
|
|
104
|
+
const endpoint = s.url ? s.url : s.command ? `${s.command} ${(s.args || []).join(" ")}`.trim() : "(none)";
|
|
105
|
+
const enabledIcon = s.enabled !== false ? "✓" : "○";
|
|
106
|
+
console.log(`\n ${enabledIcon} ${s.name} (${s.uniqueName})`);
|
|
107
|
+
console.log(` id : ${s.id}`);
|
|
108
|
+
console.log(` transport : ${transport}`);
|
|
109
|
+
console.log(` endpoint : ${endpoint}`);
|
|
110
|
+
if (s.url) {
|
|
111
|
+
const proxyUrl = `${KnowhowClient_1.KNOWHOW_API_URL}/api/mcp-proxy/${s.id}/mcp`;
|
|
112
|
+
console.log(` proxy url : ${proxyUrl}`);
|
|
113
|
+
}
|
|
114
|
+
if (s.enabled === false) {
|
|
115
|
+
console.log(` enabled : false`);
|
|
116
|
+
}
|
|
117
|
+
if (s.authConfig && typeof s.authConfig === "object") {
|
|
118
|
+
const ac = s.authConfig;
|
|
119
|
+
console.log(` auth type : ${ac.type || "unknown"}`);
|
|
120
|
+
if (ac.type === "basic") {
|
|
121
|
+
console.log(` username : secret:${ac.usernameSecretKey || "(not set)"}`);
|
|
122
|
+
console.log(` password : secret:${ac.passwordSecretKey || "(not set)"}`);
|
|
123
|
+
}
|
|
124
|
+
else if (ac.type === "api_key") {
|
|
125
|
+
console.log(` api key : secret:${ac.keySecretKey || "(not set)"} (${ac.location || "header"}:${ac.keyName || "?"})`);
|
|
126
|
+
}
|
|
127
|
+
else if (ac.type === "oauth2_static" || ac.type === "oauth2_dynamic") {
|
|
128
|
+
console.log(` oauth : ${ac.tokenUrl || ac.discoveryUrl || "(url not set)"}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
console.log(`${"─".repeat(60)}\n`);
|
|
133
|
+
}
|
|
134
|
+
async function testLocalConnections(mcps) {
|
|
135
|
+
const results = {};
|
|
136
|
+
const mcpService = new Mcp_1.McpService();
|
|
137
|
+
for (const mcp of mcps) {
|
|
138
|
+
try {
|
|
139
|
+
await mcpService.createStdioClients([mcp]);
|
|
140
|
+
await mcpService.connectAutoServers();
|
|
141
|
+
const tools = await mcpService.getTools();
|
|
142
|
+
results[mcp.name] = { connected: true, toolCount: tools.length };
|
|
143
|
+
await mcpService.closeTransports();
|
|
144
|
+
mcpService.clients = [];
|
|
145
|
+
mcpService.transports = [];
|
|
146
|
+
mcpService.config = [];
|
|
147
|
+
mcpService.connected = [];
|
|
148
|
+
mcpService.tools = [];
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
results[mcp.name] = { connected: false, toolCount: 0, error: err.message };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
156
|
+
function addMcpCommands(program) {
|
|
157
|
+
const mcp = program
|
|
158
|
+
.command("mcp")
|
|
159
|
+
.description("Manage MCP (Model Context Protocol) servers");
|
|
160
|
+
mcp
|
|
161
|
+
.command("add <name> [endpoint]")
|
|
162
|
+
.description("Add an MCP server.\n" +
|
|
163
|
+
" For HTTP/SSE servers: knowhow mcp add myserver https://example.com/mcp\n" +
|
|
164
|
+
" For stdio servers: knowhow mcp add myserver --command npx --args '-y,some-mcp-server'\n" +
|
|
165
|
+
" Remote (backend): knowhow mcp add myserver https://example.com/mcp --remote")
|
|
166
|
+
.option("--transport <transport>", "Transport type: http, sse, stdio (auto-detected if omitted)")
|
|
167
|
+
.option("--command <cmd>", "Command to run for stdio servers (e.g. npx)")
|
|
168
|
+
.option("--args <args>", "Comma-separated args for stdio command (e.g. '-y,some-mcp-server')")
|
|
169
|
+
.option("--env <KEY=VALUE...>", "Environment variables for stdio servers (repeatable)", collect, [])
|
|
170
|
+
.option("--header <Header: Value...>", "HTTP headers (stored as authorization_token_file hint)", collect, [])
|
|
171
|
+
.option("--auth-token-file <path>", "Path to file containing the bearer/basic auth token")
|
|
172
|
+
.option("--auth-scheme <scheme>", "Auth scheme: bearer or basic (default: bearer)", "bearer")
|
|
173
|
+
.option("--no-auto-connect", "Do not auto-connect to this server on startup")
|
|
174
|
+
.option("--remote", "Add to the knowhow backend instead of the local config")
|
|
175
|
+
.option("--unique-name <uniqueName>", "Unique name for remote MCP server (defaults to <name>)")
|
|
176
|
+
.option("--secret-mapping <json>", "Secret mapping JSON for remote MCP (e.g. '{\"MY_ENV\": \"secret.name.field\"}')")
|
|
177
|
+
.option("--auth-config <json>", "Auth config JSON for remote MCP (e.g. '{\"type\":\"basic\",\"usernameSecretKey\":\"grafana.username\",\"passwordSecretKey\":\"grafana.password\"}')")
|
|
178
|
+
.action(async (name, endpoint, opts) => {
|
|
179
|
+
const stdioArgs = [];
|
|
180
|
+
if (opts.command) {
|
|
181
|
+
stdioArgs.push(opts.command);
|
|
182
|
+
if (opts.args) {
|
|
183
|
+
const parsedArgs = opts.args.includes(",")
|
|
184
|
+
? opts.args.split(",").map((a) => a.trim())
|
|
185
|
+
: opts.args.split(" ").map((a) => a.trim()).filter(Boolean);
|
|
186
|
+
stdioArgs.push(...parsedArgs);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const transport = inferTransport({ url: endpoint, command: stdioArgs[0], transport: opts.transport });
|
|
190
|
+
if (opts.remote) {
|
|
191
|
+
await addRemoteMcp(name, endpoint, transport, opts, stdioArgs);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await addLocalMcp(name, endpoint, transport, opts, stdioArgs);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
mcp
|
|
198
|
+
.command("list")
|
|
199
|
+
.description("List configured MCP servers")
|
|
200
|
+
.option("--remote", "List MCP servers from the knowhow backend")
|
|
201
|
+
.option("--test", "Test connectivity and show tool counts (local only)")
|
|
202
|
+
.action(async (opts) => {
|
|
203
|
+
if (opts.remote) {
|
|
204
|
+
await listRemoteMcps();
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
await listLocalMcps(opts.test);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
mcp
|
|
211
|
+
.command("remove <name>")
|
|
212
|
+
.description("Remove an MCP server from the local config")
|
|
213
|
+
.option("--remote", "Remove from the knowhow backend instead")
|
|
214
|
+
.action(async (name, opts) => {
|
|
215
|
+
if (opts.remote) {
|
|
216
|
+
await removeRemoteMcp(name);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
await removeLocalMcp(name);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
mcp
|
|
223
|
+
.command("get <name>")
|
|
224
|
+
.description("Show details for a specific MCP server")
|
|
225
|
+
.option("--remote", "Look up from the knowhow backend")
|
|
226
|
+
.action(async (name, opts) => {
|
|
227
|
+
if (opts.remote) {
|
|
228
|
+
await getRemoteMcp(name);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
await getLocalMcp(name);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
mcp
|
|
235
|
+
.command("update <name>")
|
|
236
|
+
.description("Update a remote MCP server configuration")
|
|
237
|
+
.option("--remote", "Update in the knowhow backend (required)")
|
|
238
|
+
.option("--url <url>", "New URL for the server")
|
|
239
|
+
.option("--auth-config <json>", "New auth config JSON")
|
|
240
|
+
.option("--secret-mapping <json>", "New secret mapping JSON")
|
|
241
|
+
.option("--env <KEY=VALUE...>", "Environment variables (repeatable)", collect, [])
|
|
242
|
+
.option("--enabled <bool>", "Enable or disable (true/false)")
|
|
243
|
+
.action(async (name, opts) => {
|
|
244
|
+
if (!opts.remote) {
|
|
245
|
+
console.error("✗ mcp update currently only supports --remote. Use mcp remove + mcp add for local updates.");
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
await updateRemoteMcp(name, opts);
|
|
249
|
+
});
|
|
250
|
+
mcp
|
|
251
|
+
.command("secrets")
|
|
252
|
+
.description("Manage remote secrets for MCP servers (requires --remote)")
|
|
253
|
+
.option("--list", "List all org secrets")
|
|
254
|
+
.option("--create <name>", "Create a secret with the given name")
|
|
255
|
+
.option("--value <value>", "Value for the secret (used with --create)")
|
|
256
|
+
.option("--value-file <path>", "Read secret value from file (used with --create)")
|
|
257
|
+
.option("--delete <nameOrId>", "Delete a secret by name or id")
|
|
258
|
+
.action(async (opts) => {
|
|
259
|
+
if (opts.list) {
|
|
260
|
+
await listRemoteSecrets();
|
|
261
|
+
}
|
|
262
|
+
else if (opts.create) {
|
|
263
|
+
let value = opts.value;
|
|
264
|
+
if (!value && opts.valueFile) {
|
|
265
|
+
value = fs.readFileSync(opts.valueFile, "utf-8").trim();
|
|
266
|
+
}
|
|
267
|
+
if (!value) {
|
|
268
|
+
console.error("✗ --value or --value-file is required with --create");
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
await createRemoteSecret(opts.create, value);
|
|
272
|
+
}
|
|
273
|
+
else if (opts.delete) {
|
|
274
|
+
await deleteRemoteSecret(opts.delete);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
await listRemoteSecrets();
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async function addLocalMcp(name, endpoint, transport, opts, stdioArgs) {
|
|
282
|
+
const config = await (0, config_1.getConfig)();
|
|
283
|
+
const mcps = config.mcps || [];
|
|
284
|
+
if (mcps.find((m) => m.name === name)) {
|
|
285
|
+
console.error(`✗ MCP server '${name}' already exists. Use 'mcp remove ${name}' first.`);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const entry = { name };
|
|
289
|
+
if (transport === "stdio") {
|
|
290
|
+
if (!stdioArgs.length) {
|
|
291
|
+
console.error("✗ Stdio transport requires a command. Use: knowhow mcp add <name> -- <command> [args...]");
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
entry.command = stdioArgs[0];
|
|
295
|
+
entry.args = stdioArgs.slice(1);
|
|
296
|
+
if (opts.env && opts.env.length) {
|
|
297
|
+
entry.env = parseEnvList(opts.env);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
if (!endpoint) {
|
|
302
|
+
console.error("✗ HTTP/SSE transport requires a URL.");
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
entry.url = endpoint;
|
|
306
|
+
if (opts.authTokenFile) {
|
|
307
|
+
entry.authorization_token_file = opts.authTokenFile;
|
|
308
|
+
if (opts.authScheme && opts.authScheme !== "bearer") {
|
|
309
|
+
entry.authorization_scheme = opts.authScheme;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (opts.header && opts.header.length) {
|
|
313
|
+
const authHeader = opts.header.find((h) => h.toLowerCase().startsWith("authorization:"));
|
|
314
|
+
if (authHeader) {
|
|
315
|
+
const tokenMatch = authHeader.match(/:\s*(?:Bearer|Basic)\s+(.+)/i);
|
|
316
|
+
if (tokenMatch) {
|
|
317
|
+
entry.authorization_token = tokenMatch[1].trim();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (opts.autoConnect === false) {
|
|
323
|
+
entry.autoConnect = false;
|
|
324
|
+
}
|
|
325
|
+
mcps.push(entry);
|
|
326
|
+
config.mcps = mcps;
|
|
327
|
+
await (0, config_1.updateConfig)(config);
|
|
328
|
+
console.log(`✓ Added MCP server '${name}' (transport: ${transport}) to .knowhow/knowhow.json`);
|
|
329
|
+
if (entry.url)
|
|
330
|
+
console.log(` URL: ${entry.url}`);
|
|
331
|
+
if (entry.command)
|
|
332
|
+
console.log(` Command: ${entry.command} ${(entry.args || []).join(" ")}`);
|
|
333
|
+
}
|
|
334
|
+
async function listLocalMcps(test = false) {
|
|
335
|
+
const config = await (0, config_1.getConfig)();
|
|
336
|
+
const mcps = config.mcps || [];
|
|
337
|
+
let statusMap = {};
|
|
338
|
+
if (test && mcps.length > 0) {
|
|
339
|
+
console.log("Testing connections…");
|
|
340
|
+
statusMap = await testLocalConnections(mcps);
|
|
341
|
+
}
|
|
342
|
+
formatLocalMcpList(mcps, statusMap);
|
|
343
|
+
}
|
|
344
|
+
async function removeLocalMcp(name) {
|
|
345
|
+
const config = await (0, config_1.getConfig)();
|
|
346
|
+
const mcps = config.mcps || [];
|
|
347
|
+
const idx = mcps.findIndex((m) => m.name === name);
|
|
348
|
+
if (idx < 0) {
|
|
349
|
+
console.error(`✗ MCP server '${name}' not found in local config.`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
mcps.splice(idx, 1);
|
|
353
|
+
config.mcps = mcps;
|
|
354
|
+
await (0, config_1.updateConfig)(config);
|
|
355
|
+
console.log(`✓ Removed MCP server '${name}' from .knowhow/knowhow.json`);
|
|
356
|
+
}
|
|
357
|
+
async function getLocalMcp(name) {
|
|
358
|
+
const config = await (0, config_1.getConfig)();
|
|
359
|
+
const mcps = config.mcps || [];
|
|
360
|
+
const mcp = mcps.find((m) => m.name === name);
|
|
361
|
+
if (!mcp) {
|
|
362
|
+
console.error(`✗ MCP server '${name}' not found in local config.`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
console.log(JSON.stringify(mcp, null, 2));
|
|
366
|
+
}
|
|
367
|
+
async function getRemoteClient() {
|
|
368
|
+
const client = new KnowhowClient_1.KnowhowSimpleClient();
|
|
369
|
+
await client.checkJwt();
|
|
370
|
+
return client;
|
|
371
|
+
}
|
|
372
|
+
async function addRemoteMcp(name, endpoint, transport, opts, stdioArgs) {
|
|
373
|
+
const client = await getRemoteClient();
|
|
374
|
+
const uniqueName = opts.uniqueName || name;
|
|
375
|
+
const body = {
|
|
376
|
+
name,
|
|
377
|
+
uniqueName,
|
|
378
|
+
command: "url",
|
|
379
|
+
args: [],
|
|
380
|
+
};
|
|
381
|
+
if (transport === "stdio") {
|
|
382
|
+
if (!stdioArgs.length) {
|
|
383
|
+
console.error("✗ Stdio transport requires a command. Use: knowhow mcp add --remote <name> -- <command> [args...]");
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
body.command = stdioArgs[0];
|
|
387
|
+
body.args = stdioArgs.slice(1);
|
|
388
|
+
if (opts.env && opts.env.length) {
|
|
389
|
+
body.env = parseEnvList(opts.env);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
if (!endpoint) {
|
|
394
|
+
console.error("✗ HTTP/SSE transport requires a URL.");
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
body.url = endpoint;
|
|
398
|
+
body.command = "url";
|
|
399
|
+
body.args = [];
|
|
400
|
+
if (opts.env && opts.env.length) {
|
|
401
|
+
body.env = parseEnvList(opts.env);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (opts.secretMapping) {
|
|
405
|
+
try {
|
|
406
|
+
body.secretMapping = typeof opts.secretMapping === "string" ? JSON.parse(opts.secretMapping) : opts.secretMapping;
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
console.error("✗ --secret-mapping must be valid JSON.");
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (opts.authConfig) {
|
|
414
|
+
try {
|
|
415
|
+
body.authConfig = typeof opts.authConfig === "string" ? JSON.parse(opts.authConfig) : opts.authConfig;
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
console.error("✗ --auth-config must be valid JSON.");
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const response = await http_1.default.post(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers`, body, { headers: client.headers });
|
|
424
|
+
const server = response.data || response;
|
|
425
|
+
console.log(`✓ Created remote MCP server '${name}' (id: ${server.id})`);
|
|
426
|
+
const proxyUrl = `${KnowhowClient_1.KNOWHOW_API_URL}/api/mcp-proxy/${server.id}/mcp`;
|
|
427
|
+
console.log(` Proxy URL: ${proxyUrl}`);
|
|
428
|
+
console.log(`\nTo use this MCP locally via the backend proxy, add to your config:`);
|
|
429
|
+
console.log(` knowhow mcp add ${name}-remote ${proxyUrl} --auth-token-file .knowhow/.jwt`);
|
|
430
|
+
if (body.authConfig) {
|
|
431
|
+
const ac = body.authConfig;
|
|
432
|
+
const secretsNeeded = [];
|
|
433
|
+
if (ac.type === "basic") {
|
|
434
|
+
if (ac.usernameSecretKey)
|
|
435
|
+
secretsNeeded.push(ac.usernameSecretKey);
|
|
436
|
+
if (ac.passwordSecretKey)
|
|
437
|
+
secretsNeeded.push(ac.passwordSecretKey);
|
|
438
|
+
}
|
|
439
|
+
else if (ac.type === "api_key" && ac.keySecretKey) {
|
|
440
|
+
secretsNeeded.push(ac.keySecretKey);
|
|
441
|
+
}
|
|
442
|
+
else if (ac.type === "oauth2_static") {
|
|
443
|
+
if (ac.clientIdSecretKey)
|
|
444
|
+
secretsNeeded.push(ac.clientIdSecretKey);
|
|
445
|
+
if (ac.clientSecretSecretKey)
|
|
446
|
+
secretsNeeded.push(ac.clientSecretSecretKey);
|
|
447
|
+
}
|
|
448
|
+
if (secretsNeeded.length > 0) {
|
|
449
|
+
console.log(`\n⚠ This server uses auth secrets. Ensure the following org secrets exist:`);
|
|
450
|
+
for (const key of secretsNeeded) {
|
|
451
|
+
console.log(` knowhow mcp secrets --create ${key} --value <YOUR_VALUE>`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
const msg = err?.response?.data?.message || err.message;
|
|
458
|
+
console.error(`✗ Failed to create remote MCP server: ${msg}`);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function listRemoteMcps() {
|
|
463
|
+
const client = await getRemoteClient();
|
|
464
|
+
try {
|
|
465
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers`, { headers: client.headers });
|
|
466
|
+
const servers = response.data || response;
|
|
467
|
+
formatRemoteMcpList(Array.isArray(servers) ? servers : []);
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
const msg = err?.response?.data?.message || err.message;
|
|
471
|
+
console.error(`✗ Failed to list remote MCP servers: ${msg}`);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function removeRemoteMcp(nameOrId) {
|
|
476
|
+
const client = await getRemoteClient();
|
|
477
|
+
let servers = [];
|
|
478
|
+
try {
|
|
479
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers`, { headers: client.headers });
|
|
480
|
+
servers = response.data || response;
|
|
481
|
+
if (!Array.isArray(servers))
|
|
482
|
+
servers = [];
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
console.error(`✗ Failed to fetch remote MCP servers: ${err.message}`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
const server = servers.find((s) => s.id === nameOrId || s.name === nameOrId || s.uniqueName === nameOrId);
|
|
489
|
+
if (!server) {
|
|
490
|
+
console.error(`✗ Remote MCP server '${nameOrId}' not found.`);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
await http_1.default.delete(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers/${server.id}`, { headers: client.headers });
|
|
495
|
+
console.log(`✓ Removed remote MCP server '${server.name}' (id: ${server.id})`);
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
const msg = err?.response?.data?.message || err.message;
|
|
499
|
+
console.error(`✗ Failed to remove remote MCP server: ${msg}`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async function getRemoteMcp(nameOrId) {
|
|
504
|
+
const client = await getRemoteClient();
|
|
505
|
+
let servers = [];
|
|
506
|
+
try {
|
|
507
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers`, { headers: client.headers });
|
|
508
|
+
servers = response.data || response;
|
|
509
|
+
if (!Array.isArray(servers))
|
|
510
|
+
servers = [];
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
console.error(`✗ Failed to fetch remote MCP servers: ${err.message}`);
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
const server = servers.find((s) => s.id === nameOrId || s.name === nameOrId || s.uniqueName === nameOrId);
|
|
517
|
+
if (!server) {
|
|
518
|
+
console.error(`✗ Remote MCP server '${nameOrId}' not found.`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
console.log(JSON.stringify(server, null, 2));
|
|
522
|
+
const proxyUrl = `${KnowhowClient_1.KNOWHOW_API_URL}/api/mcp-proxy/${server.id}/mcp`;
|
|
523
|
+
console.log(`\n Proxy URL: ${proxyUrl}`);
|
|
524
|
+
}
|
|
525
|
+
async function updateRemoteMcp(nameOrId, opts) {
|
|
526
|
+
const client = await getRemoteClient();
|
|
527
|
+
let servers = [];
|
|
528
|
+
try {
|
|
529
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers`, { headers: client.headers });
|
|
530
|
+
servers = response.data || response;
|
|
531
|
+
if (!Array.isArray(servers))
|
|
532
|
+
servers = [];
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
console.error(`✗ Failed to fetch remote MCP servers: ${err.message}`);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
const server = servers.find((s) => s.id === nameOrId || s.name === nameOrId || s.uniqueName === nameOrId);
|
|
539
|
+
if (!server) {
|
|
540
|
+
console.error(`✗ Remote MCP server '${nameOrId}' not found.`);
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
const body = {};
|
|
544
|
+
if (opts.url)
|
|
545
|
+
body.url = opts.url;
|
|
546
|
+
if (opts.enabled !== undefined)
|
|
547
|
+
body.enabled = opts.enabled === "true";
|
|
548
|
+
if (opts.env && opts.env.length)
|
|
549
|
+
body.env = parseEnvList(opts.env);
|
|
550
|
+
if (opts.secretMapping) {
|
|
551
|
+
try {
|
|
552
|
+
body.secretMapping = JSON.parse(opts.secretMapping);
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
console.error("✗ --secret-mapping must be valid JSON.");
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (opts.authConfig) {
|
|
560
|
+
try {
|
|
561
|
+
body.authConfig = JSON.parse(opts.authConfig);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
console.error("✗ --auth-config must be valid JSON.");
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
const response = await http_1.default.put(`${KnowhowClient_1.KNOWHOW_API_URL}/api/org-mcp-servers/${server.id}`, body, { headers: client.headers });
|
|
570
|
+
const updated = response.data || response;
|
|
571
|
+
console.log(`✓ Updated remote MCP server '${updated.name}' (id: ${updated.id})`);
|
|
572
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
const msg = err?.response?.data?.message || err.message;
|
|
576
|
+
console.error(`✗ Failed to update remote MCP server: ${msg}`);
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function collect(value, previous) {
|
|
581
|
+
return previous.concat([value]);
|
|
582
|
+
}
|
|
583
|
+
function parseEnvList(envList) {
|
|
584
|
+
const env = {};
|
|
585
|
+
for (const item of envList) {
|
|
586
|
+
const eqIdx = item.indexOf("=");
|
|
587
|
+
if (eqIdx > 0) {
|
|
588
|
+
env[item.slice(0, eqIdx)] = item.slice(eqIdx + 1);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return env;
|
|
592
|
+
}
|
|
593
|
+
async function listRemoteSecrets() {
|
|
594
|
+
const client = await getRemoteClient();
|
|
595
|
+
try {
|
|
596
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/secrets/org`, { headers: client.headers });
|
|
597
|
+
const secrets = response.data || response;
|
|
598
|
+
const list = Array.isArray(secrets) ? secrets : [];
|
|
599
|
+
if (list.length === 0) {
|
|
600
|
+
console.log("No org secrets found.");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
604
|
+
console.log(" Remote Org Secrets");
|
|
605
|
+
console.log(`${"─".repeat(60)}`);
|
|
606
|
+
for (const s of list) {
|
|
607
|
+
console.log(`\n • ${s.name}`);
|
|
608
|
+
console.log(` id : ${s.id}`);
|
|
609
|
+
console.log(` created : ${s.createdAt}`);
|
|
610
|
+
console.log(` secret path: secret.${s.name}`);
|
|
611
|
+
}
|
|
612
|
+
console.log(`${"─".repeat(60)}\n`);
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
const msg = err?.response?.data?.message || err.message;
|
|
616
|
+
console.error(`✗ Failed to list remote secrets: ${msg}`);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function createRemoteSecret(name, value) {
|
|
621
|
+
const client = await getRemoteClient();
|
|
622
|
+
try {
|
|
623
|
+
const response = await http_1.default.post(`${KnowhowClient_1.KNOWHOW_API_URL}/api/secrets/org`, { name, value }, { headers: client.headers });
|
|
624
|
+
const secret = response.data || response;
|
|
625
|
+
console.log(`✓ Created org secret '${name}' (id: ${secret.id})`);
|
|
626
|
+
console.log(` Secret path: secret.${name}`);
|
|
627
|
+
console.log(` Use in secretMapping: { "MY_ENV_VAR": "secret.${name}" }`);
|
|
628
|
+
console.log(` Use in authConfig usernameSecretKey: "${name}"`);
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
const msg = err?.response?.data?.message || err.message;
|
|
632
|
+
console.error(`✗ Failed to create remote secret: ${msg}`);
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function deleteRemoteSecret(nameOrId) {
|
|
637
|
+
const client = await getRemoteClient();
|
|
638
|
+
let secrets = [];
|
|
639
|
+
try {
|
|
640
|
+
const response = await http_1.default.get(`${KnowhowClient_1.KNOWHOW_API_URL}/api/secrets/org`, { headers: client.headers });
|
|
641
|
+
secrets = response.data || response;
|
|
642
|
+
if (!Array.isArray(secrets))
|
|
643
|
+
secrets = [];
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
console.error(`✗ Failed to fetch remote secrets: ${err.message}`);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
const secret = secrets.find((s) => s.id === nameOrId || s.name === nameOrId);
|
|
650
|
+
if (!secret) {
|
|
651
|
+
console.error(`✗ Remote secret '${nameOrId}' not found.`);
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
await http_1.default.delete(`${KnowhowClient_1.KNOWHOW_API_URL}/api/secrets/org/${secret.id}`, { headers: client.headers });
|
|
656
|
+
console.log(`✓ Deleted org secret '${secret.name}' (id: ${secret.id})`);
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
659
|
+
const msg = err?.response?.data?.message || err.message;
|
|
660
|
+
console.error(`✗ Failed to delete remote secret: ${msg}`);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
//# sourceMappingURL=mcp.js.map
|