create-multicast 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +67 -24
- package/package.json +1 -1
- package/templates/default/src/index.ts +50 -12
package/dist/cli.js
CHANGED
|
@@ -256,45 +256,88 @@ async function main() {
|
|
|
256
256
|
process.exit(0);
|
|
257
257
|
}
|
|
258
258
|
const selectedNames = selections;
|
|
259
|
-
//
|
|
259
|
+
// Auto-detect auth for each selected server
|
|
260
|
+
// Strategy:
|
|
261
|
+
// 1. If auth found in config → use it silently
|
|
262
|
+
// 2. If no auth → probe the server to check if it needs auth
|
|
263
|
+
// 3. Only ask the user if we can't determine automatically
|
|
264
|
+
const authSpinner = p.spinner();
|
|
265
|
+
authSpinner.start("Detecting authentication requirements...");
|
|
260
266
|
for (const name of selectedNames) {
|
|
261
267
|
const server = httpServers.find((s) => s.name === name);
|
|
262
268
|
let auth = server.auth;
|
|
269
|
+
let authStatus;
|
|
263
270
|
if (auth) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
initialValue: true,
|
|
267
|
-
});
|
|
268
|
-
if (p.isCancel(useExisting)) {
|
|
269
|
-
p.cancel("Setup cancelled.");
|
|
270
|
-
process.exit(0);
|
|
271
|
-
}
|
|
272
|
-
if (!useExisting)
|
|
273
|
-
auth = undefined;
|
|
271
|
+
// Auth found in existing config — use it
|
|
272
|
+
authStatus = "credentials found in config";
|
|
274
273
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
274
|
+
else {
|
|
275
|
+
// No auth in config — probe the server
|
|
276
|
+
try {
|
|
277
|
+
const probe = await fetch(server.url, {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: { "Content-Type": "application/json" },
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
jsonrpc: "2.0",
|
|
282
|
+
method: "initialize",
|
|
283
|
+
params: {
|
|
284
|
+
protocolVersion: "2024-11-05",
|
|
285
|
+
capabilities: {},
|
|
286
|
+
clientInfo: { name: "multicast-probe", version: "0.1.0" },
|
|
287
|
+
},
|
|
288
|
+
id: "probe",
|
|
289
|
+
}),
|
|
290
|
+
signal: AbortSignal.timeout(5000),
|
|
284
291
|
});
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
292
|
+
if (probe.status === 401 || probe.status === 403) {
|
|
293
|
+
// Server requires auth but we don't have it — need to ask
|
|
294
|
+
authStatus = "needs-auth";
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// Server responded without auth — no auth needed
|
|
298
|
+
authStatus = "no auth required";
|
|
288
299
|
}
|
|
289
|
-
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Can't reach server — assume no auth (will fail later with clear error)
|
|
303
|
+
authStatus = "unreachable (skipping auth)";
|
|
290
304
|
}
|
|
291
305
|
}
|
|
306
|
+
if (authStatus === "needs-auth") {
|
|
307
|
+
// Only ask when we KNOW the server needs auth but we don't have it
|
|
308
|
+
authSpinner.stop(`${pc.yellow("!")} ${name} requires authentication.`);
|
|
309
|
+
const authResult = await p.text({
|
|
310
|
+
message: `Enter auth header for ${pc.bold(name)}:`,
|
|
311
|
+
placeholder: "Bearer your-token-here",
|
|
312
|
+
validate: (v) => {
|
|
313
|
+
if (!v.trim())
|
|
314
|
+
return "Auth header is required — server returned 401";
|
|
315
|
+
return undefined;
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
if (p.isCancel(authResult)) {
|
|
319
|
+
p.cancel("Setup cancelled.");
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
auth = authResult;
|
|
323
|
+
authSpinner.start("Detecting authentication requirements...");
|
|
324
|
+
}
|
|
292
325
|
selectedServers.push({
|
|
293
326
|
name,
|
|
294
327
|
url: server.url,
|
|
295
328
|
auth: auth || undefined,
|
|
296
329
|
});
|
|
297
330
|
}
|
|
331
|
+
authSpinner.stop("Authentication configured.");
|
|
332
|
+
// Show auth summary
|
|
333
|
+
for (const s of selectedServers) {
|
|
334
|
+
if (s.auth) {
|
|
335
|
+
p.log.message(` ${pc.green("✓")} ${pc.bold(s.name)} — auth configured`);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
p.log.message(` ${pc.green("✓")} ${pc.bold(s.name)} — no auth needed`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
298
341
|
}
|
|
299
342
|
// Option to add servers manually
|
|
300
343
|
let addMore = true;
|
package/package.json
CHANGED
|
@@ -67,16 +67,53 @@ async function callMcpServer(
|
|
|
67
67
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
const
|
|
70
|
+
const baseHeaders: Record<string, string> = {
|
|
71
71
|
"Content-Type": "application/json",
|
|
72
|
+
"Accept": "application/json, text/event-stream",
|
|
72
73
|
};
|
|
73
74
|
if (server.auth) {
|
|
74
|
-
|
|
75
|
+
baseHeaders["Authorization"] = server.auth;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Step 1: Initialize MCP session
|
|
79
|
+
const initResponse = await fetch(server.url, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: baseHeaders,
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
jsonrpc: "2.0",
|
|
84
|
+
method: "initialize",
|
|
85
|
+
params: {
|
|
86
|
+
protocolVersion: "2024-11-05",
|
|
87
|
+
capabilities: {},
|
|
88
|
+
clientInfo: { name: "multicast", version: "0.1.0" },
|
|
89
|
+
},
|
|
90
|
+
id: "init-" + crypto.randomUUID(),
|
|
91
|
+
}),
|
|
92
|
+
signal: controller.signal,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const sessionId = initResponse.headers.get("mcp-session-id");
|
|
96
|
+
|
|
97
|
+
// Step 2: Send initialized notification (required by spec)
|
|
98
|
+
const callHeaders = { ...baseHeaders };
|
|
99
|
+
if (sessionId) {
|
|
100
|
+
callHeaders["mcp-session-id"] = sessionId;
|
|
75
101
|
}
|
|
76
102
|
|
|
103
|
+
await fetch(server.url, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: callHeaders,
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
method: "notifications/initialized",
|
|
109
|
+
}),
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Step 3: Call the tool with the session
|
|
77
114
|
const response = await fetch(server.url, {
|
|
78
115
|
method: "POST",
|
|
79
|
-
headers,
|
|
116
|
+
headers: callHeaders,
|
|
80
117
|
body: JSON.stringify({
|
|
81
118
|
jsonrpc: "2.0",
|
|
82
119
|
method: "tools/call",
|
|
@@ -148,6 +185,7 @@ async function discoverServerTools(
|
|
|
148
185
|
try {
|
|
149
186
|
const headers: Record<string, string> = {
|
|
150
187
|
"Content-Type": "application/json",
|
|
188
|
+
"Accept": "application/json, text/event-stream",
|
|
151
189
|
};
|
|
152
190
|
if (server.auth) {
|
|
153
191
|
headers["Authorization"] = server.auth;
|
|
@@ -222,6 +260,15 @@ async function discoverServerTools(
|
|
|
222
260
|
|
|
223
261
|
const tools = data.result?.tools || [];
|
|
224
262
|
|
|
263
|
+
// Upsert server row FIRST (tools table has FK reference to servers)
|
|
264
|
+
await db
|
|
265
|
+
.prepare(
|
|
266
|
+
`INSERT OR REPLACE INTO servers (name, url, description, tool_count, status, last_error, last_discovered_at, updated_at)
|
|
267
|
+
VALUES (?, ?, '', ?, 'active', NULL, datetime('now'), datetime('now'))`
|
|
268
|
+
)
|
|
269
|
+
.bind(server.name, server.url, tools.length)
|
|
270
|
+
.run();
|
|
271
|
+
|
|
225
272
|
// Clear old tools for this server
|
|
226
273
|
await db
|
|
227
274
|
.prepare("DELETE FROM tools WHERE server_name = ?")
|
|
@@ -244,15 +291,6 @@ async function discoverServerTools(
|
|
|
244
291
|
.run();
|
|
245
292
|
}
|
|
246
293
|
|
|
247
|
-
// Update server metadata
|
|
248
|
-
await db
|
|
249
|
-
.prepare(
|
|
250
|
-
`INSERT OR REPLACE INTO servers (name, url, description, tool_count, status, last_error, last_discovered_at, updated_at)
|
|
251
|
-
VALUES (?, ?, '', ?, 'active', NULL, datetime('now'), datetime('now'))`
|
|
252
|
-
)
|
|
253
|
-
.bind(server.name, server.url, tools.length)
|
|
254
|
-
.run();
|
|
255
|
-
|
|
256
294
|
return { tool_count: tools.length };
|
|
257
295
|
} catch (err: unknown) {
|
|
258
296
|
const message = err instanceof Error ? err.message : "discovery failed";
|