@decionis/mcp 0.1.1 → 0.1.2

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.
@@ -549,7 +549,7 @@ var MCP_PROTOCOL_VERSIONS = [
549
549
  "2025-03-26",
550
550
  "2024-11-05"
551
551
  ];
552
- var MCP_DEFAULT_PROTOCOL_VERSION = "2025-06-18";
552
+ var MCP_DEFAULT_PROTOCOL_VERSION = "2025-11-25";
553
553
  var DEFAULT_PUBLIC_BASE_URL = "https://protocol.decionis.com";
554
554
  var MCP_HTTP_PATHS = ["/mcp", "/v1/protocol/mcp"];
555
555
 
@@ -753,7 +753,7 @@ var McpAuthGuard = class {
753
753
  return auth?.kind === "org_api_key";
754
754
  }
755
755
  static findTargetOrgIdForTool(name, args) {
756
- if (name === "decionis.evaluate_decision" || name === "evaluate_decision" || name === "decionis.agentic_checkout_authorize" || name === "agentic_checkout_authorize" || name === "decionis.evaluate_webex_agent_intent" || name === "evaluate_webex_agent_intent" || name === "decionis.get_decision_dossier" || name === "get_decision_dossier" || name === "decionis.list_enforcement_rollouts" || name === "list_enforcement_rollouts" || name === "decionis.upsert_enforcement_rollout" || name === "upsert_enforcement_rollout" || name === "decionis.resolve_vertical_pack" || name === "resolve_vertical_pack" || name === "decionis.run_shadow_decisioning" || name === "run_shadow_decisioning") {
756
+ if (name === "decionis.evaluate_decision" || name === "evaluate_decision" || name === "decionis.agentic_checkout_authorize" || name === "agentic_checkout_authorize" || name === "decionis.evaluate_webex_agent_intent" || name === "evaluate_webex_agent_intent" || name === "decionis.get_decision_dossier" || name === "get_decision_dossier" || name === "decionis.get_decision_chain" || name === "get_decision_chain" || name === "decionis.list_enforcement_rollouts" || name === "list_enforcement_rollouts" || name === "decionis.upsert_enforcement_rollout" || name === "upsert_enforcement_rollout" || name === "decionis.resolve_vertical_pack" || name === "resolve_vertical_pack" || name === "decionis.run_shadow_decisioning" || name === "run_shadow_decisioning") {
757
757
  return asString(args.org_id);
758
758
  }
759
759
  return null;
@@ -762,7 +762,7 @@ var McpAuthGuard = class {
762
762
  if (name === "decionis.evaluate_decision" || name === "evaluate_decision" || name === "decionis.agentic_checkout_authorize" || name === "agentic_checkout_authorize" || name === "decionis.evaluate_webex_agent_intent" || name === "evaluate_webex_agent_intent" || name === "decionis.run_shadow_decisioning" || name === "run_shadow_decisioning") {
763
763
  return ["decision:run", "decision:*", "org:*"];
764
764
  }
765
- if (name === "decionis.get_decision_dossier" || name === "get_decision_dossier" || name === "decionis.list_enforcement_rollouts" || name === "list_enforcement_rollouts" || name === "decionis.resolve_vertical_pack" || name === "resolve_vertical_pack") {
765
+ if (name === "decionis.get_decision_dossier" || name === "get_decision_dossier" || name === "decionis.get_decision_chain" || name === "get_decision_chain" || name === "decionis.list_enforcement_rollouts" || name === "list_enforcement_rollouts" || name === "decionis.resolve_vertical_pack" || name === "resolve_vertical_pack") {
766
766
  return ["decision:read", "org:read", "decision:*", "org:*"];
767
767
  }
768
768
  if (name === "decionis.upsert_enforcement_rollout" || name === "upsert_enforcement_rollout") {
@@ -1984,7 +1984,7 @@ var McpTools = class {
1984
1984
  return cart && typeof cart.amount === "number" ? cart.amount : void 0;
1985
1985
  }
1986
1986
  static buildTools(app) {
1987
- return [
1987
+ const tools = [
1988
1988
  {
1989
1989
  name: "decionis.evaluate_decision",
1990
1990
  aliases: ["evaluate_decision"],
@@ -2489,6 +2489,29 @@ var McpTools = class {
2489
2489
  }
2490
2490
  }
2491
2491
  ];
2492
+ const readOnlyTools = /* @__PURE__ */ new Set([
2493
+ "decionis.get_decision_dossier",
2494
+ "decionis.get_decision_chain",
2495
+ "decionis.list_enforcement_rollouts",
2496
+ "decionis.list_marketplace_targets",
2497
+ "decionis.get_marketplace_target",
2498
+ "decionis.list_marketplace_readiness",
2499
+ "decionis.list_vertical_packs",
2500
+ "decionis.resolve_vertical_pack"
2501
+ ]);
2502
+ const idempotentWriteTools = /* @__PURE__ */ new Set(["decionis.upsert_enforcement_rollout"]);
2503
+ return tools.map((tool) => {
2504
+ const readOnly = readOnlyTools.has(tool.name);
2505
+ return {
2506
+ ...tool,
2507
+ annotations: {
2508
+ readOnlyHint: readOnly,
2509
+ destructiveHint: false,
2510
+ idempotentHint: readOnly || idempotentWriteTools.has(tool.name),
2511
+ openWorldHint: false
2512
+ }
2513
+ };
2514
+ });
2492
2515
  }
2493
2516
  static buildTextToolResult(value, options) {
2494
2517
  const text = toPrettyText(value);
@@ -2542,6 +2565,37 @@ function findTool(tools, name) {
2542
2565
  }
2543
2566
 
2544
2567
  // ../protocol/src/mcp/McpRoutes.ts
2568
+ async function recordTelemetry(app, sink, event) {
2569
+ if (!sink) return;
2570
+ try {
2571
+ await sink.record(event);
2572
+ } catch (err) {
2573
+ app.log.warn({ err, event: event.event }, "MCP telemetry write failed");
2574
+ }
2575
+ }
2576
+ function canonicalAuthorizationOutcome(value) {
2577
+ const record = asRecord(value);
2578
+ const raw = asString(record?.outcome) ?? asString(record?.decision) ?? asString(record?.verdict);
2579
+ if (!raw) return null;
2580
+ switch (raw.trim().toUpperCase()) {
2581
+ case "ALLOW":
2582
+ case "APPROVE":
2583
+ case "AUTO_APPROVE":
2584
+ return "ALLOW";
2585
+ case "BLOCK":
2586
+ case "REJECT":
2587
+ case "AUTO_REJECT":
2588
+ return "BLOCK";
2589
+ case "ESCALATE":
2590
+ return "ESCALATE";
2591
+ case "REVIEW":
2592
+ case "RESTRAIN":
2593
+ case "REQUIRE_REVIEW":
2594
+ return "REVIEW";
2595
+ default:
2596
+ return null;
2597
+ }
2598
+ }
2545
2599
  function buildInitializeResult(serviceName, serviceVersion) {
2546
2600
  return {
2547
2601
  protocolVersion: MCP_DEFAULT_PROTOCOL_VERSION,
@@ -2555,7 +2609,7 @@ function buildInitializeResult(serviceName, serviceVersion) {
2555
2609
  title: "Decionis Protocol MCP",
2556
2610
  version: serviceVersion
2557
2611
  },
2558
- instructions: "Use Decionis Protocol as the deterministic execution-governance MCP server. Encode policy with evaluate_decision, stage enforcement with upsert_enforcement_rollout, and verify evidence through Decision Dossiers and audit-linked resources."
2612
+ instructions: "Use Decionis Protocol to evaluate a proposed action before execution and inspect its signed governance evidence. Evaluation tools record Decionis decisions but never perform the downstream mutation. Use read-only dossier, chain, marketplace, and vertical tools for inspection."
2559
2613
  };
2560
2614
  }
2561
2615
  async function handleJsonRpcRequest(app, requestBody, options) {
@@ -2593,6 +2647,16 @@ async function handleJsonRpcRequest(app, requestBody, options) {
2593
2647
  }
2594
2648
  const requestedVersion = asString(params.protocolVersion);
2595
2649
  const negotiatedVersion = requestedVersion && MCP_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : MCP_DEFAULT_PROTOCOL_VERSION;
2650
+ const clientInfo = asRecord(params.clientInfo);
2651
+ await recordTelemetry(app, options.telemetry, {
2652
+ event: "connection_succeeded",
2653
+ connectionId: options.connectionId,
2654
+ clientName: asString(clientInfo?.name),
2655
+ clientVersion: asString(clientInfo?.version),
2656
+ protocolVersion: negotiatedVersion,
2657
+ authMode: options.auth?.kind ?? "none",
2658
+ statusCode: 200
2659
+ });
2596
2660
  return {
2597
2661
  statusCode: 200,
2598
2662
  payload: jsonRpcSuccess(id, {
@@ -2608,6 +2672,12 @@ async function handleJsonRpcRequest(app, requestBody, options) {
2608
2672
  case "ping":
2609
2673
  return { statusCode: 200, payload: jsonRpcSuccess(id, {}) };
2610
2674
  case "tools/list":
2675
+ await recordTelemetry(app, options.telemetry, {
2676
+ event: "tools_listed",
2677
+ connectionId: options.connectionId,
2678
+ authMode: options.auth?.kind ?? "none",
2679
+ statusCode: 200
2680
+ });
2611
2681
  return {
2612
2682
  statusCode: 200,
2613
2683
  payload: jsonRpcSuccess(id, {
@@ -2616,7 +2686,8 @@ async function handleJsonRpcRequest(app, requestBody, options) {
2616
2686
  title: tool.title,
2617
2687
  description: tool.description,
2618
2688
  inputSchema: tool.inputSchema,
2619
- ...tool.outputSchema ? { outputSchema: tool.outputSchema } : {}
2689
+ ...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
2690
+ annotations: tool.annotations
2620
2691
  }))
2621
2692
  })
2622
2693
  };
@@ -2635,15 +2706,61 @@ async function handleJsonRpcRequest(app, requestBody, options) {
2635
2706
  payload: jsonRpcError(id, -32601, `Unknown tool '${name}'`)
2636
2707
  };
2637
2708
  }
2709
+ const toolStartedAt = process.hrtime.bigint();
2710
+ await recordTelemetry(app, options.telemetry, {
2711
+ event: "tool_called",
2712
+ connectionId: options.connectionId,
2713
+ toolName: tool.name,
2714
+ authMode: options.auth?.kind ?? "none"
2715
+ });
2638
2716
  const args = asRecord(params.arguments) ?? {};
2639
2717
  const authError = authorizeToolInvocation(options.auth, name, args);
2640
2718
  if (authError) {
2719
+ await recordTelemetry(app, options.telemetry, {
2720
+ event: "authorization_outcome",
2721
+ connectionId: options.connectionId,
2722
+ toolName: tool.name,
2723
+ outcome: "DENIED",
2724
+ authMode: options.auth?.kind ?? "none",
2725
+ statusCode: 403,
2726
+ durationMs: durationMs(toolStartedAt)
2727
+ });
2728
+ await recordTelemetry(app, options.telemetry, {
2729
+ event: "tool_failed",
2730
+ connectionId: options.connectionId,
2731
+ toolName: tool.name,
2732
+ outcome: "DENIED",
2733
+ authMode: options.auth?.kind ?? "none",
2734
+ statusCode: 403,
2735
+ durationMs: durationMs(toolStartedAt)
2736
+ });
2641
2737
  return {
2642
2738
  statusCode: 403,
2643
2739
  payload: jsonRpcError(id, authError.code, authError.message)
2644
2740
  };
2645
2741
  }
2646
2742
  const result = await handleToolCall(tool, args);
2743
+ const outcome = canonicalAuthorizationOutcome(result.structuredContent);
2744
+ await recordTelemetry(app, options.telemetry, {
2745
+ event: result.isError ? "tool_failed" : "tool_succeeded",
2746
+ connectionId: options.connectionId,
2747
+ toolName: tool.name,
2748
+ outcome,
2749
+ authMode: options.auth?.kind ?? "none",
2750
+ statusCode: 200,
2751
+ durationMs: durationMs(toolStartedAt)
2752
+ });
2753
+ if (outcome) {
2754
+ await recordTelemetry(app, options.telemetry, {
2755
+ event: "authorization_outcome",
2756
+ connectionId: options.connectionId,
2757
+ toolName: tool.name,
2758
+ outcome,
2759
+ authMode: options.auth?.kind ?? "none",
2760
+ statusCode: 200,
2761
+ durationMs: durationMs(toolStartedAt)
2762
+ });
2763
+ }
2647
2764
  return {
2648
2765
  statusCode: 200,
2649
2766
  payload: jsonRpcSuccess(id, result)
@@ -2827,6 +2944,11 @@ async function handleMcpHttpRequest(app, req, reply, options) {
2827
2944
  const auth = await authenticateMcpRequest(req, authLookup);
2828
2945
  const authDurationMs = durationMs(authStartedAt);
2829
2946
  if (!auth && isAuthenticationRequired(authLookup)) {
2947
+ await recordTelemetry(app, options.telemetry ?? null, {
2948
+ event: "authentication_failed",
2949
+ statusCode: 401,
2950
+ durationMs: authDurationMs
2951
+ });
2830
2952
  const maybeId = asRecord(req.body)?.id;
2831
2953
  const id = typeof maybeId === "string" || typeof maybeId === "number" ? maybeId : null;
2832
2954
  reply.header(
@@ -2836,6 +2958,10 @@ async function handleMcpHttpRequest(app, req, reply, options) {
2836
2958
  stages: { auth_ms: authDurationMs }
2837
2959
  })
2838
2960
  );
2961
+ reply.header(
2962
+ "www-authenticate",
2963
+ `Bearer resource_metadata="${options.publicBaseUrl}/.well-known/oauth-protected-resource", error="invalid_token"`
2964
+ );
2839
2965
  return reply.code(401).send(jsonRpcError(id, -32001, "Unauthorized MCP request"));
2840
2966
  }
2841
2967
  if (Array.isArray(req.body)) {
@@ -2850,7 +2976,35 @@ async function handleMcpHttpRequest(app, req, reply, options) {
2850
2976
  }
2851
2977
  if (isLegacyToolCall(req.body)) {
2852
2978
  const rpcStartedAt2 = process.hrtime.bigint();
2979
+ const connectionId2 = asString(req.headers["mcp-session-id"]);
2980
+ await recordTelemetry(app, options.telemetry ?? null, {
2981
+ event: "tool_called",
2982
+ connectionId: connectionId2,
2983
+ toolName: req.body.tool_name,
2984
+ authMode: auth?.kind ?? "none"
2985
+ });
2853
2986
  const payload = await handleLegacyToolCall(app, auth, req.body);
2987
+ const outcome = canonicalAuthorizationOutcome(payload.output);
2988
+ await recordTelemetry(app, options.telemetry ?? null, {
2989
+ event: payload.is_error || payload.error ? "tool_failed" : "tool_succeeded",
2990
+ connectionId: connectionId2,
2991
+ toolName: req.body.tool_name,
2992
+ outcome,
2993
+ authMode: auth?.kind ?? "none",
2994
+ statusCode: 200,
2995
+ durationMs: durationMs(rpcStartedAt2)
2996
+ });
2997
+ if (outcome) {
2998
+ await recordTelemetry(app, options.telemetry ?? null, {
2999
+ event: "authorization_outcome",
3000
+ connectionId: connectionId2,
3001
+ toolName: req.body.tool_name,
3002
+ outcome,
3003
+ authMode: auth?.kind ?? "none",
3004
+ statusCode: 200,
3005
+ durationMs: durationMs(rpcStartedAt2)
3006
+ });
3007
+ }
2854
3008
  reply.header(
2855
3009
  "server-timing",
2856
3010
  buildServerTimingHeader({
@@ -2875,13 +3029,21 @@ async function handleMcpHttpRequest(app, req, reply, options) {
2875
3029
  return reply.code(400).send(jsonRpcError(null, -32600, "JSON-RPC request body required"));
2876
3030
  }
2877
3031
  const rpcStartedAt = process.hrtime.bigint();
3032
+ const requestedMethod = asString(requestBody.method);
3033
+ const incomingSession = asString(req.headers["mcp-session-id"]);
3034
+ const connectionId = requestedMethod === "initialize" ? randomUUID2() : incomingSession;
2878
3035
  const result = await handleJsonRpcRequest(app, requestBody, {
2879
3036
  publicBaseUrl: options.publicBaseUrl,
2880
3037
  serviceName: options.serviceName,
2881
3038
  serviceVersion: options.serviceVersion,
2882
3039
  auth,
2883
- authLookup
3040
+ authLookup,
3041
+ telemetry: options.telemetry ?? null,
3042
+ connectionId
2884
3043
  });
3044
+ if (requestedMethod === "initialize" && result.statusCode === 200) {
3045
+ reply.header("mcp-session-id", connectionId);
3046
+ }
2885
3047
  reply.header(
2886
3048
  "server-timing",
2887
3049
  buildServerTimingHeader({
@@ -2911,8 +3073,21 @@ function registerProtocolMcpRoutes(app, options) {
2911
3073
  endpoint: `${publicBaseUrl}/v1/protocol/mcp`,
2912
3074
  endpoint_aliases: MCP_HTTP_PATHS,
2913
3075
  supported_protocol_versions: [...MCP_PROTOCOL_VERSIONS],
2914
- auth
3076
+ auth,
3077
+ oauth: {
3078
+ protected_resource_metadata: `${publicBaseUrl}/.well-known/oauth-protected-resource`,
3079
+ authorization_server_metadata: "https://api.decionis.com/.well-known/oauth-authorization-server"
3080
+ }
2915
3081
  };
3082
+ const oauthProtectedResource = {
3083
+ resource: `${publicBaseUrl}/mcp`,
3084
+ authorization_servers: ["https://api.decionis.com"],
3085
+ bearer_methods_supported: ["header"],
3086
+ scopes_supported: ["decision:read", "decision:run", "decision:write"],
3087
+ resource_documentation: "https://decionis.com/docs/protocol-mcp"
3088
+ };
3089
+ app.get("/.well-known/oauth-protected-resource", async () => oauthProtectedResource);
3090
+ app.get("/.well-known/oauth-protected-resource/mcp", async () => oauthProtectedResource);
2916
3091
  for (const path of MCP_HTTP_PATHS) {
2917
3092
  app.get(path, async () => metadata);
2918
3093
  app.delete(path, async (_req, reply) => reply.code(204).send());
@@ -2922,7 +3097,8 @@ function registerProtocolMcpRoutes(app, options) {
2922
3097
  publicBaseUrl,
2923
3098
  serviceName: options.serviceName,
2924
3099
  serviceVersion: options.serviceVersion,
2925
- authLookup
3100
+ authLookup,
3101
+ telemetry: options.telemetry ?? null
2926
3102
  })
2927
3103
  );
2928
3104
  }
@@ -17197,7 +17373,8 @@ function buildProtocolApp(options) {
17197
17373
  registerProtocolMcpRoutes(app, {
17198
17374
  serviceName: PROTOCOL_SERVICE,
17199
17375
  serviceVersion: PROTOCOL_VERSION,
17200
- authLookup: options?.mcpAuthLookup ?? null
17376
+ authLookup: options?.mcpAuthLookup ?? null,
17377
+ telemetry: options?.mcpTelemetry ?? null
17201
17378
  });
17202
17379
  const executionAuthority = createExecutionGatingService(options?.executionGating);
17203
17380
  registerExecutionGatingRoutes(app, {
@@ -17573,4 +17750,4 @@ export {
17573
17750
  normalizeVerdict,
17574
17751
  PolicyFileRepository
17575
17752
  };
17576
- //# sourceMappingURL=chunk-56VFPJNL.js.map
17753
+ //# sourceMappingURL=chunk-KY5AVRL6.js.map