@tangle-network/agent-integrations 0.2.0 → 0.3.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { createHmac as createHmac5, randomUUID, timingSafeEqual as timingSafeEqual4 } from "crypto";
2
+ import { createHmac as createHmac3, randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "crypto";
3
3
 
4
4
  // src/connectors/types.ts
5
5
  var ResourceContention = class extends Error {
@@ -2525,43 +2525,6 @@ function signHeaders(creds, body, idempotencyKey) {
2525
2525
  }
2526
2526
 
2527
2527
  // src/connectors/adapters/stripe-webhook-receiver.ts
2528
- import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual2 } from "crypto";
2529
- var SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
2530
- function parseStripeHeader(header) {
2531
- const t = { sigs: [] };
2532
- for (const part of header.split(",")) {
2533
- const idx = part.indexOf("=");
2534
- if (idx < 0) continue;
2535
- const key = part.slice(0, idx).trim();
2536
- const val = part.slice(idx + 1).trim();
2537
- if (key === "t") {
2538
- const n = Number(val);
2539
- if (Number.isFinite(n)) t.ts = n;
2540
- } else if (key === "v1") {
2541
- t.sigs.push(val);
2542
- }
2543
- }
2544
- if (t.ts === void 0 || t.sigs.length === 0) return null;
2545
- return { t: t.ts, sigs: t.sigs };
2546
- }
2547
- function verifyStripeSignature2(rawBody, header, secret, now) {
2548
- const parsed = parseStripeHeader(header);
2549
- if (!parsed) return false;
2550
- if (Math.abs(now - parsed.t) > SIGNATURE_TOLERANCE_SECONDS) return false;
2551
- const expected = createHmac3("sha256", secret).update(`${parsed.t}.${rawBody}`).digest("hex");
2552
- const expectedBuf = Buffer.from(expected, "utf8");
2553
- for (const sig of parsed.sigs) {
2554
- const sigBuf = Buffer.from(sig, "utf8");
2555
- if (sigBuf.length !== expectedBuf.length) continue;
2556
- if (timingSafeEqual2(sigBuf, expectedBuf)) return true;
2557
- }
2558
- return false;
2559
- }
2560
- function firstHeader2(h, name) {
2561
- const v = h[name] ?? h[name.toLowerCase()];
2562
- if (Array.isArray(v)) return v[0];
2563
- return typeof v === "string" ? v : void 0;
2564
- }
2565
2528
  var stripeWebhookReceiverConnector = {
2566
2529
  manifest: {
2567
2530
  kind: "stripe",
@@ -2569,23 +2532,17 @@ var stripeWebhookReceiverConnector = {
2569
2532
  description: "Receive Stripe webhook events from your own Stripe account. Paste your endpoint signing secret (whsec_*) at connect time; we'll verify every push and feed events to your agent's runtime.",
2570
2533
  auth: { kind: "hmac" },
2571
2534
  category: "commerce",
2572
- // Inbound-only today; outbound read/mutation are scaffolded. Stripe
2573
- // events are advisory in this incarnation the agent reacts to them
2574
- // but doesn't compete for writes against the same resource.
2535
+ // Inbound-only. Stripe events are advisory in this incarnation — the
2536
+ // agent reacts to them but doesn't compete for writes against the same
2537
+ // resource.
2575
2538
  defaultConsistencyModel: "advisory",
2576
2539
  capabilities: []
2577
2540
  },
2578
- async executeRead(_inv) {
2579
- throw new Error("not_implemented: stripe outbound read is scaffolded \u2014 use stripe-customers connector when shipped");
2580
- },
2581
- async executeMutation(_inv) {
2582
- throw new Error("not_implemented: stripe outbound mutation is scaffolded \u2014 use stripe-customers connector when shipped");
2583
- },
2584
2541
  verifySignature({ rawBody, headers, source }) {
2585
2542
  if (source.credentials.kind !== "hmac") return { valid: false, reason: "missing_hmac_secret" };
2586
- const sig = firstHeader2(headers, "stripe-signature");
2543
+ const sig = firstHeader(headers, "stripe-signature");
2587
2544
  if (!sig) return { valid: false, reason: "missing_stripe_signature_header" };
2588
- const ok = verifyStripeSignature2(rawBody, sig, source.credentials.secret, Math.floor(Date.now() / 1e3));
2545
+ const ok = verifyStripeSignature(rawBody, sig, source.credentials.secret);
2589
2546
  return ok ? { valid: true } : { valid: false, reason: "invalid_signature" };
2590
2547
  },
2591
2548
  async handleInboundEvent({ rawBody }) {
@@ -2619,24 +2576,6 @@ var stripeWebhookReceiverConnector = {
2619
2576
  };
2620
2577
 
2621
2578
  // src/connectors/adapters/slack-events.ts
2622
- import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
2623
- var SIGNATURE_TOLERANCE_SECONDS2 = 5 * 60;
2624
- function firstHeader3(h, name) {
2625
- const v = h[name] ?? h[name.toLowerCase()];
2626
- if (Array.isArray(v)) return v[0];
2627
- return typeof v === "string" ? v : void 0;
2628
- }
2629
- function verifySlackSignature2(rawBody, signatureHeader, timestampHeader, secret, now) {
2630
- if (!signatureHeader.startsWith("v0=")) return false;
2631
- const ts = Number(timestampHeader);
2632
- if (!Number.isFinite(ts)) return false;
2633
- if (Math.abs(now - ts) > SIGNATURE_TOLERANCE_SECONDS2) return false;
2634
- const expected = "v0=" + createHmac4("sha256", secret).update(`v0:${ts}:${rawBody}`).digest("hex");
2635
- const expectedBuf = Buffer.from(expected, "utf8");
2636
- const sigBuf = Buffer.from(signatureHeader, "utf8");
2637
- if (sigBuf.length !== expectedBuf.length) return false;
2638
- return timingSafeEqual3(sigBuf, expectedBuf);
2639
- }
2640
2579
  var slackEventsConnector = {
2641
2580
  manifest: {
2642
2581
  // NOTE: `slack` is owned by the OAuth bot connector in slack.ts (post_message,
@@ -2649,23 +2588,17 @@ var slackEventsConnector = {
2649
2588
  description: "Receive workspace events (messages, reactions, app mentions, \u2026) from Slack's Events API. Outbound bot messaging will land in a follow-up.",
2650
2589
  auth: { kind: "hmac" },
2651
2590
  category: "comms",
2652
- // Inbound-only today; outbound read/mutation scaffolded. Events are
2653
- // advisory in this incarnation — agents observe and react, no CAS.
2591
+ // Inbound-only. Events are advisory in this incarnation — agents observe
2592
+ // and react, no CAS.
2654
2593
  defaultConsistencyModel: "advisory",
2655
2594
  capabilities: []
2656
2595
  },
2657
- async executeRead(_inv) {
2658
- throw new Error("not_implemented: slack outbound read is scaffolded");
2659
- },
2660
- async executeMutation(_inv) {
2661
- throw new Error("not_implemented: slack outbound mutation is scaffolded");
2662
- },
2663
2596
  verifySignature({ rawBody, headers, source }) {
2664
2597
  if (source.credentials.kind !== "hmac") return { valid: false, reason: "missing_hmac_secret" };
2665
- const sig = firstHeader3(headers, "x-slack-signature");
2666
- const ts = firstHeader3(headers, "x-slack-request-timestamp");
2598
+ const sig = firstHeader(headers, "x-slack-signature");
2599
+ const ts = firstHeader(headers, "x-slack-request-timestamp");
2667
2600
  if (!sig || !ts) return { valid: false, reason: "missing_slack_headers" };
2668
- const ok = verifySlackSignature2(rawBody, sig, ts, source.credentials.secret, Math.floor(Date.now() / 1e3));
2601
+ const ok = verifySlackSignature(rawBody, sig, ts, source.credentials.secret);
2669
2602
  return ok ? { valid: true } : { valid: false, reason: "invalid_signature" };
2670
2603
  },
2671
2604
  async handleInboundEvent({ rawBody }) {
@@ -2707,6 +2640,434 @@ var slackEventsConnector = {
2707
2640
  }
2708
2641
  };
2709
2642
 
2643
+ // src/catalog.ts
2644
+ var riskRank = {
2645
+ read: 0,
2646
+ write: 1,
2647
+ destructive: 2
2648
+ };
2649
+ function integrationToolName(providerId, connectorId, actionId) {
2650
+ return `int_${encodeToolPart(providerId)}_${encodeToolPart(connectorId)}_${encodeToolPart(actionId)}`;
2651
+ }
2652
+ function parseIntegrationToolName(name) {
2653
+ const parts = name.split("_");
2654
+ if (parts.length !== 4 || parts[0] !== "int") {
2655
+ throw new Error(`Invalid integration tool name: ${name}`);
2656
+ }
2657
+ return {
2658
+ providerId: decodeToolPart(parts[1]),
2659
+ connectorId: decodeToolPart(parts[2]),
2660
+ actionId: decodeToolPart(parts[3])
2661
+ };
2662
+ }
2663
+ function buildIntegrationToolCatalog(connectors) {
2664
+ const tools = [];
2665
+ for (const connector of connectors) {
2666
+ for (const action of connector.actions) {
2667
+ const tags = unique([
2668
+ connector.id,
2669
+ connector.providerId,
2670
+ connector.title,
2671
+ connector.category,
2672
+ action.id,
2673
+ action.title,
2674
+ action.risk,
2675
+ action.dataClass,
2676
+ ...connector.scopes ?? [],
2677
+ ...action.requiredScopes ?? []
2678
+ ].flatMap(tokenize));
2679
+ tools.push({
2680
+ name: integrationToolName(connector.providerId, connector.id, action.id),
2681
+ title: `${connector.title}: ${action.title}`,
2682
+ description: action.description ?? `${action.risk} action ${action.id} on ${connector.title}`,
2683
+ providerId: connector.providerId,
2684
+ connectorId: connector.id,
2685
+ connectorTitle: connector.title,
2686
+ category: connector.category,
2687
+ action,
2688
+ risk: action.risk,
2689
+ dataClass: action.dataClass,
2690
+ requiredScopes: action.requiredScopes,
2691
+ inputSchema: action.inputSchema,
2692
+ outputSchema: action.outputSchema,
2693
+ tags
2694
+ });
2695
+ }
2696
+ }
2697
+ return tools;
2698
+ }
2699
+ function searchIntegrationTools(catalog, query, filters = {}) {
2700
+ const terms = tokenize(query);
2701
+ const filtered = catalog.filter((tool) => {
2702
+ if (filters.providerId && tool.providerId !== filters.providerId) return false;
2703
+ if (filters.connectorId && tool.connectorId !== filters.connectorId) return false;
2704
+ if (filters.category && tool.category !== filters.category) return false;
2705
+ if (filters.dataClass && tool.dataClass !== filters.dataClass) return false;
2706
+ if (filters.maxRisk && riskRank[tool.risk] > riskRank[filters.maxRisk]) return false;
2707
+ return true;
2708
+ });
2709
+ const scored = filtered.map((tool) => scoreTool(tool, terms));
2710
+ return scored.filter((result) => terms.length === 0 || result.score > 0).sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name)).slice(0, filters.limit ?? 20);
2711
+ }
2712
+ function toMcpTools(tools) {
2713
+ return tools.map((tool) => ({
2714
+ name: tool.name,
2715
+ description: `${tool.title}. ${tool.description}`,
2716
+ inputSchema: tool.inputSchema ?? {
2717
+ type: "object",
2718
+ additionalProperties: true,
2719
+ properties: {}
2720
+ }
2721
+ }));
2722
+ }
2723
+ function scoreTool(tool, terms) {
2724
+ if (terms.length === 0) return { tool, score: 1, matched: [] };
2725
+ const haystack = new Set(tool.tags);
2726
+ const matched = [];
2727
+ let score = 0;
2728
+ for (const term of terms) {
2729
+ if (haystack.has(term)) {
2730
+ matched.push(term);
2731
+ score += 4;
2732
+ continue;
2733
+ }
2734
+ if (tool.tags.some((tag) => tag.includes(term))) {
2735
+ matched.push(term);
2736
+ score += 1;
2737
+ }
2738
+ }
2739
+ if (tool.risk === "read") score += 0.25;
2740
+ return { tool, score, matched: unique(matched) };
2741
+ }
2742
+ function tokenize(value) {
2743
+ return value.toLowerCase().split(/[^a-z0-9]+/g).map((part) => part.trim()).filter(Boolean);
2744
+ }
2745
+ function encodeToolPart(value) {
2746
+ return Buffer.from(value, "utf8").toString("base64url");
2747
+ }
2748
+ function decodeToolPart(value) {
2749
+ return Buffer.from(value, "base64url").toString("utf8");
2750
+ }
2751
+ function unique(values) {
2752
+ return [...new Set(values)];
2753
+ }
2754
+
2755
+ // src/policy.ts
2756
+ import { randomUUID } from "crypto";
2757
+ var StaticIntegrationPolicyEngine = class {
2758
+ rules;
2759
+ defaultReadEffect;
2760
+ defaultWriteEffect;
2761
+ defaultDestructiveEffect;
2762
+ now;
2763
+ constructor(options = {}) {
2764
+ this.rules = options.rules ?? [];
2765
+ this.defaultReadEffect = options.defaultReadEffect ?? "allow";
2766
+ this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
2767
+ this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
2768
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
2769
+ }
2770
+ decide(ctx) {
2771
+ const action = ctx.action;
2772
+ if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
2773
+ const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
2774
+ const effect = matched?.effect ?? this.defaultEffect(action.risk);
2775
+ const reason = matched?.reason ?? defaultReason(effect, action.risk);
2776
+ if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
2777
+ if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
2778
+ return {
2779
+ decision: "require_approval",
2780
+ reason,
2781
+ approval: buildApprovalRequest(ctx, reason, this.now()),
2782
+ metadata: matched ? { ruleId: matched.id } : void 0
2783
+ };
2784
+ }
2785
+ defaultEffect(risk) {
2786
+ if (risk === "read") return this.defaultReadEffect;
2787
+ if (risk === "write") return this.defaultWriteEffect;
2788
+ return this.defaultDestructiveEffect;
2789
+ }
2790
+ };
2791
+ function createDefaultIntegrationPolicyEngine(options = {}) {
2792
+ return new StaticIntegrationPolicyEngine(options);
2793
+ }
2794
+ function buildApprovalRequest(ctx, reason, requestedAt) {
2795
+ if (!ctx.action) {
2796
+ throw new Error("Cannot build approval request without an action descriptor.");
2797
+ }
2798
+ return {
2799
+ id: `approval_${randomUUID()}`,
2800
+ connectionId: ctx.connection.id,
2801
+ providerId: ctx.connection.providerId,
2802
+ connectorId: ctx.connection.connectorId,
2803
+ action: ctx.request.action,
2804
+ actor: { type: ctx.subject.type, id: ctx.subject.id },
2805
+ risk: ctx.action.risk,
2806
+ dataClass: ctx.action.dataClass,
2807
+ reason,
2808
+ requestedAt: requestedAt.toISOString(),
2809
+ inputPreview: previewInput(ctx.request.input)
2810
+ };
2811
+ }
2812
+ function redactApprovalRequest(request) {
2813
+ return {
2814
+ ...request,
2815
+ inputPreview: redactUnknown(request.inputPreview)
2816
+ };
2817
+ }
2818
+ function ruleMatches(rule, ctx) {
2819
+ if (!ctx.action) return false;
2820
+ if (rule.providerId && rule.providerId !== ctx.connection.providerId) return false;
2821
+ if (rule.connectorId && rule.connectorId !== ctx.connection.connectorId) return false;
2822
+ if (rule.action && rule.action !== ctx.request.action) return false;
2823
+ if (rule.risk && rule.risk !== ctx.action.risk) return false;
2824
+ if (rule.maxRisk && riskRank2(ctx.action.risk) > riskRank2(rule.maxRisk)) return false;
2825
+ if (rule.dataClass && rule.dataClass !== ctx.action.dataClass) return false;
2826
+ return true;
2827
+ }
2828
+ function riskRank2(risk) {
2829
+ if (risk === "read") return 0;
2830
+ if (risk === "write") return 1;
2831
+ return 2;
2832
+ }
2833
+ function defaultReason(effect, risk) {
2834
+ if (effect === "allow") return `${risk} integration action allowed by default policy.`;
2835
+ if (effect === "deny") return `${risk} integration action denied by default policy.`;
2836
+ return `${risk} integration action requires approval by default policy.`;
2837
+ }
2838
+ function previewInput(input) {
2839
+ return redactUnknown(input);
2840
+ }
2841
+ function redactUnknown(value) {
2842
+ if (Array.isArray(value)) return value.map(redactUnknown);
2843
+ if (!value || typeof value !== "object") return value;
2844
+ const out = {};
2845
+ for (const [key, child] of Object.entries(value)) {
2846
+ if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
2847
+ out[key] = "[REDACTED]";
2848
+ } else {
2849
+ out[key] = redactUnknown(child);
2850
+ }
2851
+ }
2852
+ return out;
2853
+ }
2854
+
2855
+ // src/sandbox.ts
2856
+ function buildIntegrationInvocationEnvelope(input) {
2857
+ const parsed = parseIntegrationToolName(input.toolName);
2858
+ return {
2859
+ kind: "integration.invocation",
2860
+ capabilityToken: input.capabilityToken,
2861
+ toolName: input.toolName,
2862
+ action: parsed.actionId,
2863
+ input: input.args,
2864
+ idempotencyKey: input.idempotencyKey,
2865
+ dryRun: input.dryRun,
2866
+ metadata: input.metadata
2867
+ };
2868
+ }
2869
+ function invocationRequestFromEnvelope(envelope) {
2870
+ return {
2871
+ action: envelope.action,
2872
+ input: envelope.input,
2873
+ idempotencyKey: envelope.idempotencyKey,
2874
+ dryRun: envelope.dryRun,
2875
+ metadata: envelope.metadata
2876
+ };
2877
+ }
2878
+ function redactInvocationEnvelope(envelope) {
2879
+ return {
2880
+ ...envelope,
2881
+ capabilityToken: "[REDACTED]",
2882
+ input: redactUnknown2(envelope.input)
2883
+ };
2884
+ }
2885
+ function redactCapability(capability) {
2886
+ return {
2887
+ ...capability,
2888
+ metadata: redactUnknown2(capability.metadata)
2889
+ };
2890
+ }
2891
+ function normalizeIntegrationResult(result) {
2892
+ const output = result.output;
2893
+ if (!result.ok && output?.approvalRequired === true && output.approval) {
2894
+ return {
2895
+ status: "approval_required",
2896
+ action: result.action,
2897
+ approval: output.approval,
2898
+ metadata: result.metadata
2899
+ };
2900
+ }
2901
+ if (!result.ok) {
2902
+ return {
2903
+ status: "failed",
2904
+ action: result.action,
2905
+ error: String(result.output ?? result.warnings?.[0] ?? "integration action failed"),
2906
+ metadata: result.metadata
2907
+ };
2908
+ }
2909
+ return {
2910
+ status: "ok",
2911
+ action: result.action,
2912
+ output: result.output,
2913
+ metadata: result.metadata
2914
+ };
2915
+ }
2916
+ function redactUnknown2(value) {
2917
+ if (Array.isArray(value)) return value.map(redactUnknown2);
2918
+ if (!value || typeof value !== "object") return value;
2919
+ const out = {};
2920
+ for (const [key, child] of Object.entries(value)) {
2921
+ if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
2922
+ out[key] = "[REDACTED]";
2923
+ } else {
2924
+ out[key] = redactUnknown2(child);
2925
+ }
2926
+ }
2927
+ return out;
2928
+ }
2929
+
2930
+ // src/adapter-provider.ts
2931
+ function createConnectorAdapterProvider(options) {
2932
+ const providerId = options.id ?? "first-party";
2933
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
2934
+ const adapters = /* @__PURE__ */ new Map();
2935
+ for (const adapter of options.adapters) {
2936
+ adapters.set(adapter.manifest.kind, adapter);
2937
+ }
2938
+ return {
2939
+ id: providerId,
2940
+ kind: options.kind ?? "first_party",
2941
+ listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
2942
+ async invokeAction(connection, request) {
2943
+ const adapter = adapters.get(connection.connectorId);
2944
+ if (!adapter) {
2945
+ throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
2946
+ }
2947
+ const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
2948
+ if (!capability) {
2949
+ throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
2950
+ }
2951
+ const source = await options.resolveDataSource(connection);
2952
+ const invocation = {
2953
+ source,
2954
+ capabilityName: request.action,
2955
+ args: toRecord(request.input),
2956
+ idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
2957
+ expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
2958
+ callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
2959
+ };
2960
+ if (capability.class === "read") {
2961
+ if (!adapter.executeRead) {
2962
+ throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
2963
+ }
2964
+ const result = await adapter.executeRead(invocation);
2965
+ return readResultToAction(request, result);
2966
+ }
2967
+ if (capability.class === "mutation") {
2968
+ if (!adapter.executeMutation) {
2969
+ throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
2970
+ }
2971
+ const result = await adapter.executeMutation(invocation);
2972
+ return mutationResultToAction(request, result);
2973
+ }
2974
+ throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
2975
+ }
2976
+ };
2977
+ }
2978
+ function manifestToConnector(providerId, adapter) {
2979
+ const manifest = adapter.manifest;
2980
+ return {
2981
+ id: manifest.kind,
2982
+ providerId,
2983
+ title: manifest.displayName,
2984
+ category: mapCategory(manifest.category),
2985
+ auth: mapAuth(manifest.auth.kind),
2986
+ scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
2987
+ actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
2988
+ id: capability.name,
2989
+ title: titleFromName(capability.name),
2990
+ risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
2991
+ requiredScopes: capability.requiredScopes ?? [],
2992
+ dataClass: inferDataClass(manifest.category),
2993
+ description: capability.description,
2994
+ approvalRequired: capability.class === "mutation",
2995
+ inputSchema: capability.parameters
2996
+ }))
2997
+ };
2998
+ }
2999
+ function readResultToAction(request, result) {
3000
+ return {
3001
+ ok: true,
3002
+ action: request.action,
3003
+ output: result.data,
3004
+ metadata: {
3005
+ etag: result.etag,
3006
+ fetchedAt: result.fetchedAt
3007
+ }
3008
+ };
3009
+ }
3010
+ function mutationResultToAction(request, result) {
3011
+ if (result.status === "committed") {
3012
+ return {
3013
+ ok: true,
3014
+ action: request.action,
3015
+ output: result.data,
3016
+ metadata: {
3017
+ etagAfter: result.etagAfter,
3018
+ committedAt: result.committedAt,
3019
+ idempotentReplay: result.idempotentReplay
3020
+ }
3021
+ };
3022
+ }
3023
+ if (result.status === "conflict") {
3024
+ return {
3025
+ ok: false,
3026
+ action: request.action,
3027
+ output: {
3028
+ conflict: true,
3029
+ message: result.message,
3030
+ alternatives: result.alternatives,
3031
+ currentState: result.currentState
3032
+ }
3033
+ };
3034
+ }
3035
+ return {
3036
+ ok: false,
3037
+ action: request.action,
3038
+ output: {
3039
+ rateLimited: true,
3040
+ retryAfterMs: result.retryAfterMs,
3041
+ message: result.message
3042
+ }
3043
+ };
3044
+ }
3045
+ function mapAuth(kind) {
3046
+ if (kind === "oauth2") return "oauth2";
3047
+ if (kind === "api-key") return "api_key";
3048
+ if (kind === "none") return "none";
3049
+ return "custom";
3050
+ }
3051
+ function mapCategory(category) {
3052
+ if (category === "comms") return "chat";
3053
+ if (category === "spreadsheet") return "database";
3054
+ if (category === "doc") return "docs";
3055
+ if (category === "commerce") return "workflow";
3056
+ return category === "other" ? "other" : category;
3057
+ }
3058
+ function inferDataClass(category) {
3059
+ if (category === "commerce") return "sensitive";
3060
+ if (category === "webhook") return "internal";
3061
+ return "private";
3062
+ }
3063
+ function titleFromName(name) {
3064
+ return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
3065
+ }
3066
+ function toRecord(input) {
3067
+ if (input && typeof input === "object" && !Array.isArray(input)) return input;
3068
+ return {};
3069
+ }
3070
+
2710
3071
  // src/index.ts
2711
3072
  var IntegrationError = class extends Error {
2712
3073
  constructor(message, code) {
@@ -2738,6 +3099,7 @@ var IntegrationHub = class {
2738
3099
  store;
2739
3100
  capabilitySecret;
2740
3101
  guard;
3102
+ policy;
2741
3103
  now;
2742
3104
  constructor(options) {
2743
3105
  if (!options.capabilitySecret) {
@@ -2747,6 +3109,7 @@ var IntegrationHub = class {
2747
3109
  this.store = options.store;
2748
3110
  this.capabilitySecret = options.capabilitySecret;
2749
3111
  this.guard = options.guard;
3112
+ this.policy = options.policy;
2750
3113
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
2751
3114
  }
2752
3115
  async listConnectors() {
@@ -2776,11 +3139,11 @@ var IntegrationHub = class {
2776
3139
  assertScopes(connection, request.scopes);
2777
3140
  const now = this.now();
2778
3141
  const capability = {
2779
- id: `cap_${randomUUID()}`,
3142
+ id: `cap_${randomUUID2()}`,
2780
3143
  subject: request.subject,
2781
3144
  connectionId: request.connectionId,
2782
- scopes: unique(request.scopes),
2783
- allowedActions: unique(request.allowedActions),
3145
+ scopes: unique2(request.scopes),
3146
+ allowedActions: unique2(request.allowedActions),
2784
3147
  issuedAt: now.toISOString(),
2785
3148
  expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
2786
3149
  metadata: request.metadata
@@ -2808,6 +3171,25 @@ var IntegrationHub = class {
2808
3171
  assertScopes(connection, action.requiredScopes);
2809
3172
  assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes);
2810
3173
  const fullRequest = { ...request, connectionId: connection.id };
3174
+ if (this.policy) {
3175
+ const decision = await this.policy.decide({
3176
+ connection,
3177
+ request: fullRequest,
3178
+ action,
3179
+ subject: capability.subject
3180
+ });
3181
+ if (decision.decision === "deny") {
3182
+ throw new IntegrationError(decision.reason, "policy_denied");
3183
+ }
3184
+ if (decision.decision === "require_approval") {
3185
+ return {
3186
+ ok: false,
3187
+ action: request.action,
3188
+ output: { approvalRequired: true, approval: decision.approval },
3189
+ metadata: { policyDecision: decision.decision, reason: decision.reason, ...decision.metadata }
3190
+ };
3191
+ }
3192
+ }
2811
3193
  const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest));
2812
3194
  if (this.guard) {
2813
3195
  return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed);
@@ -2994,12 +3376,12 @@ function assertScopes(connection, requiredScopes) {
2994
3376
  if (missing.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing.join(", ")}`, "scope_denied");
2995
3377
  }
2996
3378
  function hmac(payload, secret) {
2997
- return createHmac5("sha256", secret).update(payload).digest("base64url");
3379
+ return createHmac3("sha256", secret).update(payload).digest("base64url");
2998
3380
  }
2999
3381
  function constantTimeEqual(a, b) {
3000
3382
  const left = Buffer.from(a);
3001
3383
  const right = Buffer.from(b);
3002
- return left.length === right.length && timingSafeEqual4(left, right);
3384
+ return left.length === right.length && timingSafeEqual2(left, right);
3003
3385
  }
3004
3386
  function base64UrlEncode(value) {
3005
3387
  return Buffer.from(value, "utf8").toString("base64url");
@@ -3007,7 +3389,7 @@ function base64UrlEncode(value) {
3007
3389
  function base64UrlDecode(value) {
3008
3390
  return Buffer.from(value, "base64url").toString("utf8");
3009
3391
  }
3010
- function unique(values) {
3392
+ function unique2(values) {
3011
3393
  return [...new Set(values)];
3012
3394
  }
3013
3395
  export {
@@ -3018,9 +3400,15 @@ export {
3018
3400
  IntegrationError,
3019
3401
  IntegrationHub,
3020
3402
  ResourceContention,
3403
+ StaticIntegrationPolicyEngine,
3021
3404
  _resetPendingFlowsForTests,
3022
3405
  assertValidConnectorManifest,
3406
+ buildApprovalRequest,
3407
+ buildIntegrationInvocationEnvelope,
3408
+ buildIntegrationToolCatalog,
3023
3409
  consumePendingFlow,
3410
+ createConnectorAdapterProvider,
3411
+ createDefaultIntegrationPolicyEngine,
3024
3412
  createHttpIntegrationProvider,
3025
3413
  createMockIntegrationProvider,
3026
3414
  exchangeAuthorizationCode,
@@ -3028,17 +3416,27 @@ export {
3028
3416
  googleCalendar,
3029
3417
  googleSheets,
3030
3418
  hubspot,
3419
+ integrationToolName,
3420
+ invocationRequestFromEnvelope,
3421
+ manifestToConnector,
3031
3422
  microsoftCalendar,
3423
+ normalizeIntegrationResult,
3032
3424
  notionDatabase,
3425
+ parseIntegrationToolName,
3033
3426
  parseStripeSignatureHeader,
3427
+ redactApprovalRequest,
3428
+ redactCapability,
3429
+ redactInvocationEnvelope,
3034
3430
  refreshAccessToken,
3035
3431
  sanitizeConnection,
3432
+ searchIntegrationTools,
3036
3433
  signCapability,
3037
3434
  slack,
3038
3435
  slackEventsConnector,
3039
3436
  startOAuthFlow,
3040
3437
  stripePackConnector,
3041
3438
  stripeWebhookReceiverConnector,
3439
+ toMcpTools,
3042
3440
  twilioSmsConnector,
3043
3441
  validateConnectorManifest,
3044
3442
  verifyCapabilityToken,