@true-and-useful/janee 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +82 -0
  2. package/dist/cli/cli-utils.d.ts +7 -0
  3. package/dist/cli/cli-utils.d.ts.map +1 -0
  4. package/dist/cli/cli-utils.js +55 -0
  5. package/dist/cli/cli-utils.js.map +1 -0
  6. package/dist/cli/commands/add.d.ts +4 -0
  7. package/dist/cli/commands/add.d.ts.map +1 -1
  8. package/dist/cli/commands/add.js +65 -147
  9. package/dist/cli/commands/add.js.map +1 -1
  10. package/dist/cli/commands/capability.d.ts +2 -3
  11. package/dist/cli/commands/capability.d.ts.map +1 -1
  12. package/dist/cli/commands/capability.js +30 -155
  13. package/dist/cli/commands/capability.js.map +1 -1
  14. package/dist/cli/commands/config.d.ts.map +1 -1
  15. package/dist/cli/commands/config.js +10 -34
  16. package/dist/cli/commands/config.js.map +1 -1
  17. package/dist/cli/commands/diagnose.d.ts.map +1 -1
  18. package/dist/cli/commands/diagnose.js +9 -21
  19. package/dist/cli/commands/diagnose.js.map +1 -1
  20. package/dist/cli/commands/list.d.ts.map +1 -1
  21. package/dist/cli/commands/list.js +3 -26
  22. package/dist/cli/commands/list.js.map +1 -1
  23. package/dist/cli/commands/logs.d.ts.map +1 -1
  24. package/dist/cli/commands/logs.js +2 -17
  25. package/dist/cli/commands/logs.js.map +1 -1
  26. package/dist/cli/commands/overview.d.ts +4 -0
  27. package/dist/cli/commands/overview.d.ts.map +1 -0
  28. package/dist/cli/commands/overview.js +115 -0
  29. package/dist/cli/commands/overview.js.map +1 -0
  30. package/dist/cli/commands/remove.d.ts.map +1 -1
  31. package/dist/cli/commands/remove.js +4 -35
  32. package/dist/cli/commands/remove.js.map +1 -1
  33. package/dist/cli/commands/revoke.d.ts.map +1 -1
  34. package/dist/cli/commands/revoke.js +3 -8
  35. package/dist/cli/commands/revoke.js.map +1 -1
  36. package/dist/cli/commands/serve-mcp.d.ts.map +1 -1
  37. package/dist/cli/commands/serve-mcp.js +24 -34
  38. package/dist/cli/commands/serve-mcp.js.map +1 -1
  39. package/dist/cli/commands/service-edit.d.ts +2 -0
  40. package/dist/cli/commands/service-edit.d.ts.map +1 -1
  41. package/dist/cli/commands/service-edit.js +36 -48
  42. package/dist/cli/commands/service-edit.js.map +1 -1
  43. package/dist/cli/commands/sessions.d.ts.map +1 -1
  44. package/dist/cli/commands/sessions.js +3 -18
  45. package/dist/cli/commands/sessions.js.map +1 -1
  46. package/dist/cli/commands/status.d.ts.map +1 -1
  47. package/dist/cli/commands/status.js +3 -18
  48. package/dist/cli/commands/status.js.map +1 -1
  49. package/dist/cli/commands/test.d.ts.map +1 -1
  50. package/dist/cli/commands/test.js +5 -41
  51. package/dist/cli/commands/test.js.map +1 -1
  52. package/dist/cli/commands/whoami.d.ts.map +1 -1
  53. package/dist/cli/commands/whoami.js +3 -17
  54. package/dist/cli/commands/whoami.js.map +1 -1
  55. package/dist/cli/config-yaml.d.ts +7 -1
  56. package/dist/cli/config-yaml.d.ts.map +1 -1
  57. package/dist/cli/config-yaml.js +19 -0
  58. package/dist/cli/config-yaml.js.map +1 -1
  59. package/dist/cli/index.js +16 -1
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/core/audit.d.ts +2 -12
  62. package/dist/core/audit.d.ts.map +1 -1
  63. package/dist/core/audit.js +1 -1
  64. package/dist/core/audit.js.map +1 -1
  65. package/dist/core/auth.d.ts.map +1 -1
  66. package/dist/core/auth.js +14 -0
  67. package/dist/core/auth.js.map +1 -1
  68. package/dist/core/authority.d.ts +6 -0
  69. package/dist/core/authority.d.ts.map +1 -1
  70. package/dist/core/authority.js +19 -13
  71. package/dist/core/authority.js.map +1 -1
  72. package/dist/core/directory.d.ts +1 -1
  73. package/dist/core/directory.d.ts.map +1 -1
  74. package/dist/core/directory.js +19 -0
  75. package/dist/core/directory.js.map +1 -1
  76. package/dist/core/exec.d.ts.map +1 -1
  77. package/dist/core/exec.js +12 -11
  78. package/dist/core/exec.js.map +1 -1
  79. package/dist/core/mcp-server.d.ts +10 -25
  80. package/dist/core/mcp-server.d.ts.map +1 -1
  81. package/dist/core/mcp-server.js +47 -578
  82. package/dist/core/mcp-server.js.map +1 -1
  83. package/dist/core/runner-proxy.d.ts.map +1 -1
  84. package/dist/core/runner-proxy.js +2 -1
  85. package/dist/core/runner-proxy.js.map +1 -1
  86. package/dist/core/sessions.d.ts +10 -0
  87. package/dist/core/sessions.d.ts.map +1 -1
  88. package/dist/core/sessions.js.map +1 -1
  89. package/dist/core/signing.d.ts +24 -0
  90. package/dist/core/signing.d.ts.map +1 -1
  91. package/dist/core/signing.js +85 -0
  92. package/dist/core/signing.js.map +1 -1
  93. package/dist/core/tool-handlers.d.ts +39 -0
  94. package/dist/core/tool-handlers.d.ts.map +1 -0
  95. package/dist/core/tool-handlers.js +378 -0
  96. package/dist/core/tool-handlers.js.map +1 -0
  97. package/dist/core/types.d.ts +28 -0
  98. package/dist/core/types.d.ts.map +1 -0
  99. package/dist/core/types.js +16 -0
  100. package/dist/core/types.js.map +1 -0
  101. package/package.json +1 -1
@@ -57,21 +57,11 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
57
57
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
58
58
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
59
59
  const agent_scope_js_1 = require("./agent-scope.js");
60
- const exec_js_1 = require("./exec.js");
61
- const health_js_1 = require("./health.js");
62
- const rules_js_1 = require("./rules.js");
60
+ const tool_handlers_js_1 = require("./tool-handlers.js");
61
+ const types_js_2 = require("./types.js");
63
62
  // Read version from package.json
64
63
  const packageJsonPath = (0, path_1.join)(__dirname, "../../package.json");
65
64
  const pkgVersion = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, "utf8")).version || "0.0.0";
66
- class DenialError extends Error {
67
- denial;
68
- constructor(message, denial) {
69
- super(message);
70
- this.name = 'DenialError';
71
- this.denial = denial;
72
- }
73
- }
74
- exports.DenialError = DenialError;
75
65
  /**
76
66
  * Check whether an agent can access a capability.
77
67
  * Checks capability-level allowedAgents first, then falls back to
@@ -85,7 +75,8 @@ function canAccessCapability(agentId, cap, service, defaultAccessPolicy) {
85
75
  if (cap.allowedAgents && cap.allowedAgents.length > 0) {
86
76
  return cap.allowedAgents.includes(agentId);
87
77
  }
88
- if (defaultAccessPolicy === "restricted") {
78
+ const effectiveAccess = cap.access ?? defaultAccessPolicy;
79
+ if (effectiveAccess === "restricted") {
89
80
  return false;
90
81
  }
91
82
  return (0, agent_scope_js_1.canAgentAccess)(agentId, service?.ownership);
@@ -105,10 +96,13 @@ function explainAccessDenial(agentId, cap, service, defaultAccessPolicy) {
105
96
  }
106
97
  return null;
107
98
  }
108
- if (defaultAccessPolicy === 'restricted') {
99
+ const effectiveAccess = cap.access ?? defaultAccessPolicy;
100
+ if (effectiveAccess === 'restricted') {
109
101
  return {
110
102
  reason: 'DEFAULT_ACCESS_RESTRICTED',
111
- detail: `defaultAccess is "restricted" and capability has no allowedAgents list`
103
+ detail: cap.access
104
+ ? `Capability access is "restricted" and has no allowedAgents list`
105
+ : `defaultAccess is "restricted" and capability has no allowedAgents list`
112
106
  };
113
107
  }
114
108
  if (!(0, agent_scope_js_1.canAgentAccess)(agentId, service?.ownership)) {
@@ -119,23 +113,8 @@ function explainAccessDenial(agentId, cap, service, defaultAccessPolicy) {
119
113
  }
120
114
  return null;
121
115
  }
122
- /**
123
- * Parse TTL string to seconds
124
- */
125
- function parseTTL(ttl) {
126
- const match = ttl.match(/^(\d+)([smhd])$/);
127
- if (!match)
128
- throw new Error(`Invalid TTL format: ${ttl}`);
129
- const value = parseInt(match[1]);
130
- const unit = match[2];
131
- const multipliers = {
132
- s: 1,
133
- m: 60,
134
- h: 3600,
135
- d: 86400,
136
- };
137
- return value * multipliers[unit];
138
- }
116
+ var types_js_3 = require("./types.js");
117
+ Object.defineProperty(exports, "DenialError", { enumerable: true, get: function () { return types_js_3.DenialError; } });
139
118
  /**
140
119
  * Create and start MCP server
141
120
  */
@@ -380,6 +359,21 @@ function createMCPServer(options) {
380
359
  const result = await onForwardToolCall(name, (args || {}), forwardAgentId);
381
360
  return result;
382
361
  }
362
+ const ctx = {
363
+ getCapabilities: () => capabilities,
364
+ getServices: () => services,
365
+ defaultAccess,
366
+ sessionManager,
367
+ auditLogger,
368
+ onExecute,
369
+ onExecCommand,
370
+ onForwardToolCall,
371
+ onPersistOwnership,
372
+ resolveAgent: resolveAgentFromRequest,
373
+ clientSessions,
374
+ explainAccessDenial,
375
+ canAccessCapability,
376
+ };
383
377
  switch (name) {
384
378
  case "list_services": {
385
379
  const listAgentId = resolveAgentFromRequest(extra, args);
@@ -416,482 +410,32 @@ function createMCPServer(options) {
416
410
  capabilities = result.capabilities;
417
411
  services = result.services;
418
412
  return {
419
- content: [
420
- {
413
+ content: [{
421
414
  type: "text",
422
415
  text: JSON.stringify({
423
- success: true,
424
- message: "Configuration reloaded successfully",
425
- services: services.size,
426
- capabilities: capabilities.length,
427
- changes: {
428
- services: services.size - prevServiceCount,
429
- capabilities: capabilities.length - prevCapCount,
430
- },
416
+ success: true, message: "Configuration reloaded successfully",
417
+ services: services.size, capabilities: capabilities.length,
418
+ changes: { services: services.size - prevServiceCount, capabilities: capabilities.length - prevCapCount },
431
419
  }, null, 2),
432
- },
433
- ],
420
+ }],
434
421
  };
435
422
  }
436
423
  catch (error) {
437
424
  throw new Error(`Failed to reload config: ${error instanceof Error ? error.message : "Unknown error"}`);
438
425
  }
439
426
  }
440
- case "execute": {
441
- const { capability, method, path, body, headers, reason } = args;
442
- // Validate required arguments
443
- if (!capability) {
444
- throw new Error("Missing required argument: capability");
445
- }
446
- if (!method) {
447
- throw new Error("Missing required argument: method (GET, POST, PUT, DELETE, etc.)");
448
- }
449
- if (!path) {
450
- throw new Error("Missing required argument: path");
451
- }
452
- // Find capability
453
- const cap = capabilities.find((c) => c.name === capability);
454
- if (!cap) {
455
- throw new DenialError(`Unknown capability: ${capability}`, {
456
- reasonCode: 'CAPABILITY_NOT_FOUND',
457
- capability,
458
- nextStep: `Run 'janee cap list' to see available capabilities, or add one with 'janee cap add'.`
459
- });
460
- }
461
- // Reject exec-mode capabilities — they should use janee_exec instead
462
- if (cap.mode === "exec") {
463
- throw new DenialError(`Capability "${capability}" is an exec-mode capability. Use the 'janee_exec' tool instead.`, {
464
- reasonCode: "MODE_MISMATCH",
465
- capability,
466
- nextStep: `Use the 'janee_exec' tool for exec-mode capabilities.`,
467
- });
468
- }
469
- // Check if reason required
470
- if (cap.requiresReason && !reason) {
471
- throw new DenialError(`Capability "${capability}" requires a reason`, {
472
- reasonCode: 'REASON_REQUIRED',
473
- capability,
474
- nextStep: `Include a 'reason' argument explaining why you need this access.`
475
- });
476
- }
477
- // Check rules (path-based policies)
478
- const ruleCheck = (0, rules_js_1.checkRules)(cap.rules, method, path);
479
- if (!ruleCheck.allowed) {
480
- auditLogger.logDenied(cap.service, method, path, ruleCheck.reason || "Request denied by policy", reason);
481
- throw new DenialError(ruleCheck.reason || "Request denied by policy", {
482
- reasonCode: "RULE_DENY",
483
- capability,
484
- agentId: resolveAgentFromRequest(extra, args),
485
- evaluatedPolicy: `rules for ${method} ${path}`,
486
- nextStep: `Check capability rules with 'janee cap list --json' — the path/method may be explicitly denied.`,
487
- });
488
- }
489
- // Check agent-scoped access (capability-level allowedAgents, then service-level ownership)
490
- const executeAgentId = resolveAgentFromRequest(extra, args);
491
- const executeSvc = services.get(cap.service);
492
- if (!canAccessCapability(executeAgentId, cap, executeSvc, defaultAccess)) {
493
- const denialDetail = explainAccessDenial(executeAgentId, cap, executeSvc, defaultAccess);
494
- auditLogger.logDenied(cap.service, method, path, "Agent does not have access to this capability", reason);
495
- throw new DenialError(`Access denied: capability "${capability}" is not accessible to this agent`, {
496
- reasonCode: denialDetail?.reason || "AGENT_NOT_ALLOWED",
497
- capability,
498
- agentId: executeAgentId,
499
- evaluatedPolicy: denialDetail?.detail,
500
- nextStep: denialDetail?.reason === "AGENT_NOT_ALLOWED"
501
- ? `Add this agent to allowedAgents: 'janee cap edit ${capability} --allowed-agents ${executeAgentId}'`
502
- : denialDetail?.reason === "DEFAULT_ACCESS_RESTRICTED"
503
- ? `Either add allowedAgents to the capability or change defaultAccess to 'open'.`
504
- : `Check service ownership settings for the backing service.`,
505
- });
506
- }
507
- // Get or create session
508
- const ttlSeconds = parseTTL(cap.ttl);
509
- const session = sessionManager.createSession(cap.name, cap.service, ttlSeconds, { agentId: executeAgentId, reason });
510
- // Build API request
511
- const apiReq = {
512
- service: cap.service,
513
- path,
514
- method,
515
- headers: headers || {},
516
- body,
517
- };
518
- // Execute
519
- const response = await onExecute(session, apiReq);
520
- return {
521
- content: [
522
- {
523
- type: "text",
524
- text: JSON.stringify({
525
- status: response.statusCode,
526
- body: response.body,
527
- }, null, 2),
528
- },
529
- ],
530
- };
531
- }
532
- case "janee_exec": {
533
- if (!onExecCommand) {
534
- throw new Error("CLI execution not supported in this configuration");
535
- }
536
- const { capability: execCapName, command: rawExecCommand, cwd: execCwd, stdin: execStdin, reason: execReason, } = args;
537
- if (!execCapName) {
538
- throw new Error("Missing required argument: capability");
539
- }
540
- if (!rawExecCommand ||
541
- (Array.isArray(rawExecCommand) && rawExecCommand.length === 0) ||
542
- (typeof rawExecCommand === "string" && rawExecCommand.trim() === "")) {
543
- throw new Error("Missing required argument: command");
544
- }
545
- const execCommand = Array.isArray(rawExecCommand)
546
- ? rawExecCommand
547
- : typeof rawExecCommand === "string"
548
- ? rawExecCommand.trim().split(/\s+/)
549
- : [];
550
- let execCap;
551
- let execSession;
552
- if (onForwardToolCall) {
553
- // Runner mode: Authority handles validation and credential injection.
554
- // Build a minimal capability stub so onExecCommand has the name.
555
- execCap = {
556
- name: execCapName,
557
- service: "",
558
- ttl: "1h",
559
- mode: "exec",
560
- workDir: execCwd,
561
- };
562
- execSession = { agentId: resolveAgentFromRequest(extra, args) };
563
- }
564
- else {
565
- // Standalone mode: validate locally
566
- const foundCap = capabilities.find((c) => c.name === execCapName);
567
- if (!foundCap) {
568
- throw new DenialError(`Unknown capability: ${execCapName}`, {
569
- reasonCode: 'CAPABILITY_NOT_FOUND',
570
- capability: execCapName,
571
- nextStep: `Run 'janee cap list' to see available capabilities, or add one with 'janee cap add'.`
572
- });
573
- }
574
- execCap = execCwd ? { ...foundCap, workDir: execCwd } : foundCap;
575
- if (execCap.mode !== "exec") {
576
- throw new DenialError(`Capability "${execCapName}" is not an exec-mode capability. Use the 'execute' tool for API proxy capabilities.`, {
577
- reasonCode: "MODE_MISMATCH",
578
- capability: execCapName,
579
- nextStep: `Use the 'execute' tool for proxy-mode capabilities.`,
580
- });
581
- }
582
- const execAgentId = resolveAgentFromRequest(extra, args);
583
- const execSvc = services.get(execCap.service);
584
- if (!canAccessCapability(execAgentId, execCap, execSvc, defaultAccess)) {
585
- const execDenialDetail = explainAccessDenial(execAgentId, execCap, execSvc, defaultAccess);
586
- auditLogger.logDenied(execCap.service, "EXEC", execCommand.join(" "), "Agent does not have access to this capability", execReason);
587
- throw new DenialError(`Access denied: capability "${execCapName}" is not accessible to this agent`, {
588
- reasonCode: execDenialDetail?.reason || "AGENT_NOT_ALLOWED",
589
- capability: execCapName,
590
- agentId: execAgentId,
591
- evaluatedPolicy: execDenialDetail?.detail,
592
- nextStep: execDenialDetail?.reason === "AGENT_NOT_ALLOWED"
593
- ? `Add this agent to allowedAgents: 'janee cap edit ${execCapName} --allowed-agents ${execAgentId}'`
594
- : execDenialDetail?.reason === "DEFAULT_ACCESS_RESTRICTED"
595
- ? `Either add allowedAgents to the capability or change defaultAccess to 'open'.`
596
- : `Check service ownership settings for the backing service.`,
597
- });
598
- }
599
- if (execCap.requiresReason && !execReason) {
600
- throw new DenialError(`Capability "${execCapName}" requires a reason`, {
601
- reasonCode: 'REASON_REQUIRED',
602
- capability: execCapName,
603
- nextStep: `Include a 'reason' argument explaining why you need this access.`
604
- });
605
- }
606
- const cmdValidation = (0, exec_js_1.validateCommand)(execCommand, execCap.allowCommands || []);
607
- if (!cmdValidation.allowed) {
608
- auditLogger.logDenied(execCap.service, "EXEC", execCommand.join(" "), cmdValidation.reason || "Command not allowed", execReason);
609
- throw new DenialError(cmdValidation.reason || "Command not allowed", {
610
- reasonCode: "COMMAND_NOT_ALLOWED",
611
- capability: execCapName,
612
- agentId: execAgentId,
613
- evaluatedPolicy: `allowCommands: [${(execCap.allowCommands || []).join(", ")}]`,
614
- nextStep: `Update allowed commands: 'janee cap edit ${execCapName} --allow-commands "new-pattern"'`,
615
- });
616
- }
617
- const execTtlSeconds = parseTTL(execCap.ttl);
618
- execSession = sessionManager.createSession(execCap.name, execCap.service, execTtlSeconds, { reason: execReason });
619
- }
620
- const execResult = await onExecCommand(execSession, execCap, execCommand, execStdin);
621
- // Log to audit
622
- auditLogger.log({
623
- service: execCap.service,
624
- path: execCommand.join(" "),
625
- method: "EXEC",
626
- headers: { "x-janee-reason": execReason || "" },
627
- }, {
628
- statusCode: execResult.exitCode === 0 ? 200 : 500,
629
- headers: {},
630
- body: execResult.stdout,
631
- }, execResult.executionTimeMs);
632
- return {
633
- content: [
634
- {
635
- type: "text",
636
- text: JSON.stringify({
637
- exitCode: execResult.exitCode,
638
- stdout: execResult.stdout,
639
- stderr: execResult.stderr,
640
- executionTimeMs: execResult.executionTimeMs,
641
- executionTarget: "runner",
642
- }, null, 2),
643
- },
644
- ],
645
- };
646
- }
647
- case "manage_credential": {
648
- const { action: credAction, service: credService, targetAgentId: credTarget, } = args;
649
- const credAgentId = resolveAgentFromRequest(extra, args);
650
- if (!credService) {
651
- throw new Error("Missing required argument: service");
652
- }
653
- const svc = services.get(credService);
654
- if (!svc) {
655
- throw new Error(`Unknown service: ${credService}`);
656
- }
657
- if (credAction === "view") {
658
- return {
659
- content: [
660
- {
661
- type: "text",
662
- text: JSON.stringify({
663
- service: credService,
664
- ownership: svc.ownership || {
665
- accessPolicy: "all-agents",
666
- note: "No ownership metadata (legacy credential)",
667
- },
668
- yourAccess: (0, agent_scope_js_1.canAgentAccess)(credAgentId, svc.ownership),
669
- }, null, 2),
670
- },
671
- ],
672
- };
673
- }
674
- // Grant/revoke require ownership verification
675
- if (!credAgentId) {
676
- throw new Error("agentId is required for grant/revoke actions");
677
- }
678
- if (!svc.ownership) {
679
- throw new Error("Cannot manage access for legacy credentials without ownership metadata. Re-add the service to enable scoping.");
680
- }
681
- if (svc.ownership.createdBy !== credAgentId) {
682
- throw new Error("Only the credential owner can grant or revoke access");
683
- }
684
- if (credAction === "grant") {
685
- if (!credTarget) {
686
- throw new Error("targetAgentId is required for grant action");
687
- }
688
- const { grantAccess } = await Promise.resolve().then(() => __importStar(require("./agent-scope.js")));
689
- svc.ownership = grantAccess(svc.ownership, credTarget);
690
- // Persist ownership change to config storage
691
- if (onPersistOwnership) {
692
- onPersistOwnership(credService, svc.ownership);
693
- }
694
- return {
695
- content: [
696
- {
697
- type: "text",
698
- text: JSON.stringify({
699
- success: true,
700
- message: `Granted access to ${credTarget}`,
701
- ownership: svc.ownership,
702
- persisted: !!onPersistOwnership,
703
- }, null, 2),
704
- },
705
- ],
706
- };
707
- }
708
- if (credAction === "revoke") {
709
- if (!credTarget) {
710
- throw new Error("targetAgentId is required for revoke action");
711
- }
712
- const { revokeAccess } = await Promise.resolve().then(() => __importStar(require("./agent-scope.js")));
713
- svc.ownership = revokeAccess(svc.ownership, credTarget);
714
- // Persist ownership change to config storage
715
- if (onPersistOwnership) {
716
- onPersistOwnership(credService, svc.ownership);
717
- }
718
- return {
719
- content: [
720
- {
721
- type: "text",
722
- text: JSON.stringify({
723
- success: true,
724
- message: `Revoked access from ${credTarget}`,
725
- ownership: svc.ownership,
726
- persisted: !!onPersistOwnership,
727
- }, null, 2),
728
- },
729
- ],
730
- };
731
- }
732
- throw new Error(`Unknown action: ${credAction}. Use 'view', 'grant', or 'revoke'.`);
733
- }
734
- case "test_service": {
735
- const { service: testSvcName, timeout: testTimeout } = (args ||
736
- {});
737
- const testOpts = testTimeout ? { timeout: testTimeout } : {};
738
- let targets;
739
- if (testSvcName) {
740
- const svc = services.get(testSvcName);
741
- if (!svc) {
742
- throw new Error(`Unknown service: ${testSvcName}. Use list_services to see available services.`);
743
- }
744
- targets = [[testSvcName, svc]];
745
- }
746
- else {
747
- targets = Array.from(services.entries());
748
- }
749
- if (targets.length === 0) {
750
- throw new Error("No services configured");
751
- }
752
- const results = await Promise.all(targets.map(([name, config]) => (0, health_js_1.testServiceConnection)(name, config, testOpts)));
753
- return {
754
- content: [
755
- {
756
- type: "text",
757
- text: JSON.stringify(results.length === 1 ? results[0] : results, null, 2),
758
- },
759
- ],
760
- };
761
- }
762
- case 'explain_access': {
763
- const { agent: explainAgent, capability: explainCapName, method: explainMethod, path: explainPath } = args;
764
- const targetAgentId = explainAgent || resolveAgentFromRequest(extra, args);
765
- const trace = [];
766
- const explainCap = capabilities.find(c => c.name === explainCapName);
767
- if (!explainCap) {
768
- trace.push({ check: 'capability_exists', result: 'fail', detail: `Capability "${explainCapName}" not found` });
769
- return {
770
- content: [{
771
- type: 'text',
772
- text: JSON.stringify({
773
- agent: targetAgentId ?? null,
774
- capability: explainCapName,
775
- allowed: false,
776
- trace,
777
- nextStep: `Run 'janee cap list' to see available capabilities.`
778
- }, null, 2)
779
- }]
780
- };
781
- }
782
- trace.push({ check: 'capability_exists', result: 'pass', detail: `Capability "${explainCapName}" exists (service: ${explainCap.service})` });
783
- // Mode check
784
- if (explainMethod && explainCap.mode === 'exec') {
785
- trace.push({ check: 'mode', result: 'fail', detail: `Capability is exec-mode but method/path were provided (use janee_exec)` });
786
- }
787
- else if (!explainMethod && explainCap.mode !== 'exec') {
788
- trace.push({ check: 'mode', result: 'pass', detail: `Capability mode: ${explainCap.mode || 'proxy'}` });
789
- }
790
- else {
791
- trace.push({ check: 'mode', result: 'pass', detail: `Capability mode: ${explainCap.mode || 'proxy'}` });
792
- }
793
- // allowedAgents
794
- if (explainCap.allowedAgents && explainCap.allowedAgents.length > 0) {
795
- if (!targetAgentId) {
796
- trace.push({ check: 'allowed_agents', result: 'pass', detail: `No agent ID (admin/CLI) — bypasses allowedAgents` });
797
- }
798
- else if (explainCap.allowedAgents.includes(targetAgentId)) {
799
- trace.push({ check: 'allowed_agents', result: 'pass', detail: `Agent "${targetAgentId}" is in allowedAgents [${explainCap.allowedAgents.join(', ')}]` });
800
- }
801
- else {
802
- trace.push({ check: 'allowed_agents', result: 'fail', detail: `Agent "${targetAgentId}" is NOT in allowedAgents [${explainCap.allowedAgents.join(', ')}]` });
803
- }
804
- }
805
- else {
806
- trace.push({ check: 'allowed_agents', result: 'skip', detail: `No allowedAgents restriction on this capability` });
807
- }
808
- // defaultAccess
809
- if (targetAgentId && (!explainCap.allowedAgents || explainCap.allowedAgents.length === 0)) {
810
- if (defaultAccess === 'restricted') {
811
- trace.push({ check: 'default_access', result: 'fail', detail: `defaultAccess is "restricted" and no allowedAgents list — agent blocked` });
812
- }
813
- else {
814
- trace.push({ check: 'default_access', result: 'pass', detail: `defaultAccess is "${defaultAccess ?? 'open'}" — agent allowed` });
815
- }
816
- }
817
- else {
818
- trace.push({ check: 'default_access', result: 'skip', detail: targetAgentId ? `allowedAgents list takes precedence` : `No agent ID (admin/CLI)` });
819
- }
820
- // Ownership
821
- const explainSvc = services.get(explainCap.service);
822
- if (targetAgentId && explainSvc?.ownership) {
823
- if ((0, agent_scope_js_1.canAgentAccess)(targetAgentId, explainSvc.ownership)) {
824
- trace.push({ check: 'ownership', result: 'pass', detail: `Agent can access service (ownership: ${JSON.stringify(explainSvc.ownership)})` });
825
- }
826
- else {
827
- trace.push({ check: 'ownership', result: 'fail', detail: `Agent cannot access service (ownership: ${JSON.stringify(explainSvc.ownership)})` });
828
- }
829
- }
830
- else {
831
- trace.push({ check: 'ownership', result: 'skip', detail: explainSvc?.ownership ? `No agent ID (admin/CLI)` : `No ownership restrictions on service` });
832
- }
833
- // Rules check (only if method/path provided)
834
- if (explainMethod && explainPath && explainCap.mode !== 'exec') {
835
- const ruleResult = (0, rules_js_1.checkRules)(explainCap.rules, explainMethod, explainPath);
836
- if (ruleResult.allowed) {
837
- trace.push({ check: 'rules', result: 'pass', detail: `${explainMethod} ${explainPath} is allowed by rules` });
838
- }
839
- else {
840
- trace.push({ check: 'rules', result: 'fail', detail: ruleResult.reason || `${explainMethod} ${explainPath} is denied by rules` });
841
- }
842
- }
843
- else if (explainCap.mode === 'exec') {
844
- trace.push({ check: 'rules', result: 'skip', detail: `Exec-mode capabilities use allowCommands, not path rules` });
845
- }
846
- else {
847
- trace.push({ check: 'rules', result: 'skip', detail: `No method/path provided for rules evaluation` });
848
- }
849
- // Command validation for exec mode
850
- if (explainCap.mode === 'exec') {
851
- trace.push({ check: 'allow_commands', result: 'skip', detail: `allowCommands: [${(explainCap.allowCommands || []).join(', ')}] — provide a specific command to validate` });
852
- }
853
- const hasFail = trace.some(t => t.result === 'fail');
854
- const firstFail = trace.find(t => t.result === 'fail');
855
- return {
856
- content: [{
857
- type: 'text',
858
- text: JSON.stringify({
859
- agent: targetAgentId ?? null,
860
- capability: explainCapName,
861
- allowed: !hasFail,
862
- trace,
863
- ...(hasFail && firstFail ? { nextStep: firstFail.detail } : {})
864
- }, null, 2)
865
- }]
866
- };
867
- }
868
- case 'whoami': {
869
- const whoamiAgentId = resolveAgentFromRequest(extra, args);
870
- const accessibleCaps = capabilities
871
- .filter(cap => canAccessCapability(whoamiAgentId, cap, services.get(cap.service), defaultAccess))
872
- .map(cap => cap.name);
873
- const deniedCaps = capabilities
874
- .filter(cap => !canAccessCapability(whoamiAgentId, cap, services.get(cap.service), defaultAccess))
875
- .map(cap => cap.name);
876
- return {
877
- content: [{
878
- type: 'text',
879
- text: JSON.stringify({
880
- agentId: whoamiAgentId ?? null,
881
- identitySource: whoamiAgentId
882
- ? ((extra?.sessionId && clientSessions.has(extra.sessionId)) || clientSessions.has('__default__')
883
- ? 'transport (clientInfo.name)'
884
- : 'client-asserted (untrusted)')
885
- : 'none',
886
- defaultAccessPolicy: defaultAccess ?? 'open',
887
- capabilities: {
888
- accessible: accessibleCaps,
889
- denied: deniedCaps,
890
- },
891
- }, null, 2)
892
- }]
893
- };
894
- }
427
+ case "execute":
428
+ return await (0, tool_handlers_js_1.handleExecute)(ctx, args, extra);
429
+ case "janee_exec":
430
+ return await (0, tool_handlers_js_1.handleExec)(ctx, args, extra);
431
+ case "manage_credential":
432
+ return await (0, tool_handlers_js_1.handleManageCredential)(ctx, args, extra);
433
+ case "test_service":
434
+ return await (0, tool_handlers_js_1.handleTestService)(ctx, args);
435
+ case "explain_access":
436
+ return (0, tool_handlers_js_1.handleExplainAccess)(ctx, args, extra);
437
+ case "whoami":
438
+ return (0, tool_handlers_js_1.handleWhoami)(ctx, args, extra);
895
439
  case "doctor": {
896
440
  if (!options.onDoctorRunner) {
897
441
  throw new Error("Doctor diagnostics only available in runner mode.");
@@ -899,12 +443,7 @@ function createMCPServer(options) {
899
443
  const doctorAgentId = resolveAgentFromRequest(extra, args);
900
444
  const doctorResult = await options.onDoctorRunner(doctorAgentId);
901
445
  return {
902
- content: [
903
- {
904
- type: "text",
905
- text: JSON.stringify(doctorResult, null, 2),
906
- },
907
- ],
446
+ content: [{ type: "text", text: JSON.stringify(doctorResult, null, 2) }],
908
447
  };
909
448
  }
910
449
  default:
@@ -915,7 +454,7 @@ function createMCPServer(options) {
915
454
  const payload = {
916
455
  error: error instanceof Error ? error.message : "Unknown error",
917
456
  };
918
- if (error instanceof DenialError) {
457
+ if (error instanceof types_js_2.DenialError) {
919
458
  payload.denial = error.denial;
920
459
  }
921
460
  return {
@@ -1020,79 +559,9 @@ async function startMCPServerHTTP(serverOptions, httpOptions) {
1020
559
  app.use(express_1.default.json());
1021
560
  const idleTimeoutMs = httpOptions.idleTimeoutMs ?? 30 * 60 * 1000; // default 30 min
1022
561
  const sessions = new Map();
1023
- // Authority REST endpoints -- active when runnerKey is provided
1024
562
  if (httpOptions.runnerKey && httpOptions.authorityHooks) {
1025
- const { timingSafeEqual } = await Promise.resolve().then(() => __importStar(require("crypto")));
1026
- const runnerKey = httpOptions.runnerKey;
1027
- const hooks = httpOptions.authorityHooks;
1028
- const authMiddleware = (req, res, next) => {
1029
- const provided = req.header("x-janee-runner-key");
1030
- if (!provided ||
1031
- provided.length !== runnerKey.length ||
1032
- !timingSafeEqual(Buffer.from(provided), Buffer.from(runnerKey))) {
1033
- res.status(401).json({ error: "Unauthorized runner request" });
1034
- return;
1035
- }
1036
- next();
1037
- };
1038
- app.get("/v1/health", (_req, res) => {
1039
- res.status(200).json({ ok: true, mode: "authority" });
1040
- });
1041
- app.post("/v1/exec/authorize", authMiddleware, async (req, res) => {
1042
- try {
1043
- const body = req.body;
1044
- if (!body?.runner?.runnerId ||
1045
- !Array.isArray(body?.command) ||
1046
- body.command.length === 0 ||
1047
- !body.capabilityId) {
1048
- res.status(400).json({ error: "Invalid authorize request" });
1049
- return;
1050
- }
1051
- const response = await hooks.authorizeExec(body);
1052
- res.status(200).json(response);
1053
- }
1054
- catch (error) {
1055
- res
1056
- .status(403)
1057
- .json({
1058
- error: error instanceof Error ? error.message : "Authorization failed",
1059
- });
1060
- }
1061
- });
1062
- app.post("/v1/exec/complete", authMiddleware, async (req, res) => {
1063
- try {
1064
- if (!req.body?.grantId) {
1065
- res.status(400).json({ error: "grantId is required" });
1066
- return;
1067
- }
1068
- await hooks.completeExec(req.body);
1069
- res.status(200).json({ ok: true });
1070
- }
1071
- catch (error) {
1072
- res
1073
- .status(500)
1074
- .json({
1075
- error: error instanceof Error ? error.message : "completion failed",
1076
- });
1077
- }
1078
- });
1079
- if (hooks.testService) {
1080
- app.post("/v1/test", authMiddleware, async (req, res) => {
1081
- try {
1082
- const result = await hooks.testService(req.body?.service, {
1083
- timeout: req.body?.timeout,
1084
- });
1085
- res.status(200).json(result);
1086
- }
1087
- catch (error) {
1088
- res
1089
- .status(500)
1090
- .json({
1091
- error: error instanceof Error ? error.message : "Test failed",
1092
- });
1093
- }
1094
- });
1095
- }
563
+ const { mountAuthorityRoutes } = await Promise.resolve().then(() => __importStar(require("./authority.js")));
564
+ mountAuthorityRoutes(app, httpOptions.runnerKey, httpOptions.authorityHooks);
1096
565
  }
1097
566
  // Sweep idle sessions every 60 seconds
1098
567
  const idleSweepInterval = idleTimeoutMs > 0