defense-mcp-server 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/build/index.js +98 -76
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -104,10 +104,6 @@ process.on("unhandledRejection", (reason) => {
104
104
  });
105
105
  // ── Main entry point ─────────────────────────────────────────────────────────
106
106
  async function main() {
107
- const server = new McpServer({
108
- name: "defense-mcp-server",
109
- version: VERSION,
110
- });
111
107
  // ── Phase 1: Dependency Validation & Auto-Install ────────────────────────
112
108
  //
113
109
  // Before registering tools, validate that all required system binaries
@@ -216,66 +212,79 @@ async function main() {
216
212
  catch {
217
213
  // Non-fatal — third-party check failure must not block startup
218
214
  }
219
- // Wrap server with pre-flight middleware
220
- const wrappedServer = createPreflightServer(server);
221
- // ── Phase 2: Register all defensive tool modules (with error isolation) ──
222
- let registered = 0;
223
- let failed = 0;
224
- const failedModules = [];
225
- function safeRegister(name, fn) {
226
- try {
227
- fn(wrappedServer);
228
- registered++;
229
- }
230
- catch (err) {
231
- failed++;
232
- failedModules.push(name);
233
- console.error(`[startup] Failed to register ${name} tools: ${err instanceof Error ? err.message : err}`);
215
+ // ── Phase 2: Tool registration ────────────────────────────────────────────
216
+ //
217
+ // registerAllTools() creates a fresh McpServer, wraps it with pre-flight
218
+ // middleware, and registers every tool module. For stdio we call it once;
219
+ // for HTTP we call it per session so each client gets its own instance
220
+ // (the MCP SDK only allows one transport per McpServer).
221
+ function registerAllTools() {
222
+ const srv = new McpServer({
223
+ name: "defense-mcp-server",
224
+ version: VERSION,
225
+ });
226
+ const wrapped = createPreflightServer(srv);
227
+ let registered = 0;
228
+ let failed = 0;
229
+ const failedModules = [];
230
+ function safeRegister(name, fn) {
231
+ try {
232
+ fn(wrapped);
233
+ registered++;
234
+ }
235
+ catch (err) {
236
+ failed++;
237
+ failedModules.push(name);
238
+ console.error(`[startup] ⚠ Failed to register ${name} tools: ${err instanceof Error ? err.message : err}`);
239
+ }
234
240
  }
241
+ // Sudo privilege management (must be registered first — prerequisite for other tools)
242
+ safeRegister("sudo-management", registerSudoManagementTools);
243
+ // Original tool modules
244
+ safeRegister("firewall", registerFirewallTools);
245
+ safeRegister("hardening", registerHardeningTools);
246
+ safeRegister("integrity", registerIntegrityTools);
247
+ safeRegister("logging", registerLoggingTools);
248
+ safeRegister("network-defense", registerNetworkDefenseTools);
249
+ safeRegister("compliance", registerComplianceTools);
250
+ safeRegister("malware", registerMalwareTools);
251
+ safeRegister("backup", registerBackupTools);
252
+ safeRegister("access-control", registerAccessControlTools);
253
+ safeRegister("encryption", registerEncryptionTools);
254
+ safeRegister("container-security", registerContainerSecurityTools);
255
+ safeRegister("meta", registerMetaTools);
256
+ safeRegister("patch-management", registerPatchManagementTools);
257
+ safeRegister("secrets", registerSecretsTools);
258
+ safeRegister("incident-response", registerIncidentResponseTools);
259
+ // New tool modules
260
+ safeRegister("supply-chain-security", registerSupplyChainSecurityTools);
261
+ safeRegister("zero-trust-network", registerZeroTrustNetworkTools);
262
+ safeRegister("ebpf-security", registerEbpfSecurityTools);
263
+ safeRegister("app-hardening", registerAppHardeningTools);
264
+ // v0.6.0 tool modules
265
+ safeRegister("api-security", registerApiSecurityTools);
266
+ safeRegister("cloud-security", registerCloudSecurityTools);
267
+ safeRegister("deception", registerDeceptionTools);
268
+ safeRegister("dns-security", registerDnsSecurityTools);
269
+ safeRegister("process-security", registerProcessSecurityTools);
270
+ safeRegister("threat-intel", registerThreatIntelTools);
271
+ safeRegister("vulnerability-management", registerVulnerabilityManagementTools);
272
+ safeRegister("waf", registerWafTools);
273
+ safeRegister("wireless-security", registerWirelessSecurityTools);
274
+ return { server: srv, registered, failed, failedModules };
235
275
  }
236
- // Sudo privilege management (must be registered first — prerequisite for other tools)
237
- safeRegister("sudo-management", registerSudoManagementTools);
238
- // Original tool modules
239
- safeRegister("firewall", registerFirewallTools);
240
- safeRegister("hardening", registerHardeningTools);
241
- safeRegister("integrity", registerIntegrityTools);
242
- safeRegister("logging", registerLoggingTools);
243
- safeRegister("network-defense", registerNetworkDefenseTools);
244
- safeRegister("compliance", registerComplianceTools);
245
- safeRegister("malware", registerMalwareTools);
246
- safeRegister("backup", registerBackupTools);
247
- safeRegister("access-control", registerAccessControlTools);
248
- safeRegister("encryption", registerEncryptionTools);
249
- safeRegister("container-security", registerContainerSecurityTools);
250
- safeRegister("meta", registerMetaTools);
251
- safeRegister("patch-management", registerPatchManagementTools);
252
- safeRegister("secrets", registerSecretsTools);
253
- safeRegister("incident-response", registerIncidentResponseTools);
254
- // New tool modules
255
- safeRegister("supply-chain-security", registerSupplyChainSecurityTools);
256
- safeRegister("zero-trust-network", registerZeroTrustNetworkTools);
257
- safeRegister("ebpf-security", registerEbpfSecurityTools);
258
- safeRegister("app-hardening", registerAppHardeningTools);
259
- // v0.6.0 tool modules
260
- safeRegister("api-security", registerApiSecurityTools);
261
- safeRegister("cloud-security", registerCloudSecurityTools);
262
- safeRegister("deception", registerDeceptionTools);
263
- safeRegister("dns-security", registerDnsSecurityTools);
264
- safeRegister("process-security", registerProcessSecurityTools);
265
- safeRegister("threat-intel", registerThreatIntelTools);
266
- safeRegister("vulnerability-management", registerVulnerabilityManagementTools);
267
- safeRegister("waf", registerWafTools);
268
- safeRegister("wireless-security", registerWirelessSecurityTools);
269
- // Fail hard if no modules loaded at all
270
- if (registered === 0) {
276
+ // Validate that tool registration works (fail-fast on startup)
277
+ const initial = registerAllTools();
278
+ if (initial.registered === 0) {
271
279
  throw new Error("No tool modules loaded — server cannot start");
272
280
  }
273
281
  // ── Phase 3: Connect transport ───────────────────────────────────────────
274
282
  const transportMode = process.env.MCP_TRANSPORT ?? "stdio";
275
- const registrationMsg = `Registered ${registered} tool modules with consolidated defensive security tools` +
276
- `${failed > 0 ? ` (${failed} failed: ${failedModules.join(", ")})` : ""}`;
283
+ const registrationMsg = `Registered ${initial.registered} tool modules with consolidated defensive security tools` +
284
+ `${initial.failed > 0 ? ` (${initial.failed} failed: ${initial.failedModules.join(", ")})` : ""}`;
277
285
  if (transportMode === "http") {
278
286
  const port = parseInt(process.env.MCP_PORT ?? "3100", 10);
287
+ const apiKey = process.env.MCP_API_KEY ?? "";
279
288
  // Server card for Smithery discovery
280
289
  const serverCard = {
281
290
  serverInfo: {
@@ -283,7 +292,7 @@ async function main() {
283
292
  version: VERSION,
284
293
  description: "31 defensive security tools (250+ actions) for Linux system hardening, compliance, and threat detection",
285
294
  },
286
- authentication: { required: false },
295
+ authentication: { required: !!apiKey },
287
296
  tools: [
288
297
  { name: "access_control", description: "Access control: SSH, PAM, sudo, user audit, password policy, shell restriction" },
289
298
  { name: "api_security", description: "API security: local API discovery, auth audit, rate limiting, TLS verify, CORS check" },
@@ -320,63 +329,74 @@ async function main() {
320
329
  resources: [],
321
330
  prompts: [],
322
331
  };
323
- // Track active transports by session ID
332
+ // Track active sessions: each gets its own McpServer + transport pair
324
333
  const sessions = new Map();
325
334
  const httpServer = createServer(async (req, res) => {
326
335
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
327
336
  const pathname = url.pathname;
328
- // CORS for Smithery Gateway
337
+ // CORS headers
329
338
  res.setHeader("Access-Control-Allow-Origin", "*");
330
339
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
331
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, smithery-*");
340
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, mcp-session-id, smithery-*");
332
341
  res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
333
342
  if (req.method === "OPTIONS") {
334
343
  res.writeHead(204).end();
335
344
  return;
336
345
  }
337
- // Health check
346
+ // Health check (unauthenticated)
338
347
  if (req.method === "GET" && pathname === "/health") {
339
348
  res.writeHead(200, { "Content-Type": "application/json" });
340
- res.end(JSON.stringify({ status: "ok", version: VERSION, tools: registered }));
349
+ res.end(JSON.stringify({ status: "ok", version: VERSION, tools: initial.registered }));
341
350
  return;
342
351
  }
343
- // Smithery server card
352
+ // Smithery server card (unauthenticated)
344
353
  if (req.method === "GET" && pathname === "/.well-known/mcp/server-card.json") {
345
354
  res.writeHead(200, { "Content-Type": "application/json" });
346
355
  res.end(JSON.stringify(serverCard));
347
356
  return;
348
357
  }
358
+ // API key authentication (when MCP_API_KEY is set)
359
+ if (apiKey) {
360
+ const authHeader = req.headers["authorization"] ?? "";
361
+ const provided = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
362
+ if (provided !== apiKey) {
363
+ res.writeHead(401, { "Content-Type": "application/json" });
364
+ res.end(JSON.stringify({ error: "Unauthorized", message: "Valid Bearer token required" }));
365
+ return;
366
+ }
367
+ }
349
368
  // MCP Streamable HTTP endpoint
350
369
  if (pathname === "/mcp" || pathname === "/") {
351
370
  const sessionId = req.headers["mcp-session-id"];
352
371
  if (req.method === "GET" || req.method === "POST") {
353
- // Reuse existing session or create new one
354
- let transport = sessionId ? sessions.get(sessionId) : undefined;
355
- if (!transport) {
356
- transport = new StreamableHTTPServerTransport({
372
+ // Reuse existing session or create a new McpServer + transport pair
373
+ let session = sessionId ? sessions.get(sessionId) : undefined;
374
+ if (!session) {
375
+ const { server: sessionServer } = registerAllTools();
376
+ const transport = new StreamableHTTPServerTransport({
357
377
  sessionIdGenerator: () => crypto.randomUUID(),
358
378
  });
359
- await server.connect(transport);
360
- // Store session after first request so we can retrieve it by ID
379
+ await sessionServer.connect(transport);
380
+ session = { server: sessionServer, transport };
361
381
  transport.onclose = () => {
362
- const sid = [...sessions.entries()].find(([, t]) => t === transport)?.[0];
382
+ const sid = [...sessions.entries()].find(([, s]) => s.transport === transport)?.[0];
363
383
  if (sid)
364
384
  sessions.delete(sid);
365
385
  };
366
386
  }
367
- await transport.handleRequest(req, res);
387
+ await session.transport.handleRequest(req, res);
368
388
  // Capture session ID from response headers if new session
369
389
  if (!sessionId) {
370
390
  const newSid = res.getHeader("mcp-session-id");
371
- if (newSid && transport)
372
- sessions.set(newSid, transport);
391
+ if (newSid && session)
392
+ sessions.set(newSid, session);
373
393
  }
374
394
  return;
375
395
  }
376
396
  if (req.method === "DELETE") {
377
397
  if (sessionId && sessions.has(sessionId)) {
378
- const transport = sessions.get(sessionId);
379
- await transport.handleRequest(req, res);
398
+ const session = sessions.get(sessionId);
399
+ await session.transport.handleRequest(req, res);
380
400
  sessions.delete(sessionId);
381
401
  }
382
402
  else {
@@ -390,14 +410,16 @@ async function main() {
390
410
  httpServer.listen(port, () => {
391
411
  console.error(`Defense MCP Server v${VERSION} running on HTTP port ${port}`);
392
412
  console.error(registrationMsg);
413
+ console.error(`[startup] Authentication: ${apiKey ? "ENABLED (MCP_API_KEY)" : "DISABLED — set MCP_API_KEY to require Bearer auth"}`);
393
414
  console.error("[startup] MCP endpoint: http://0.0.0.0:" + port + "/mcp");
394
415
  console.error("[startup] Health check: http://0.0.0.0:" + port + "/health");
395
416
  console.error("[startup] Server card: http://0.0.0.0:" + port + "/.well-known/mcp/server-card.json");
396
417
  });
397
418
  }
398
419
  else {
420
+ // stdio mode: single server instance, single transport
399
421
  const transport = new StdioServerTransport();
400
- await server.connect(transport);
422
+ await initial.server.connect(transport);
401
423
  console.error(`Defense MCP Server v${VERSION} running on stdio`);
402
424
  console.error(registrationMsg);
403
425
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "defense-mcp-server",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Defense MCP Server — 31 defensive security tools with 250+ actions for system hardening, compliance, and threat detection on Linux",
5
5
  "type": "module",
6
6
  "main": "build/index.js",