@trigguard/mcp-server
Advanced tools
| import { toolError } from "./mcpResponse.js"; | ||
| export type FailureClass = "TRIGGUARD_APPLICATION_ERROR" | "EDGE_HTML_ERROR" | "UPSTREAM_PROXY_ERROR" | "NETWORK_ERROR" | "AUTHENTICATION_ERROR" | "ORGANIZATION_BINDING_ERROR"; | ||
| export type GatewayFailureCapture = { | ||
| status?: number; | ||
| bodyText?: string; | ||
| contentType?: string; | ||
| }; | ||
| export type McpCodedError = Error & { | ||
| readonly code?: string; | ||
| readonly status?: number; | ||
| readonly remediation?: string; | ||
| readonly bodyText?: string; | ||
| readonly contentType?: string; | ||
| }; | ||
| export declare function sanitizePublicMessage(message: string): string; | ||
| export declare function codedError(code: string, message: string, remediation?: string, status?: number): McpCodedError; | ||
| export declare function looksLikeHtml(text: string, contentType?: string): boolean; | ||
| export declare function parseJsonObject(text: string): Record<string, unknown> | null; | ||
| export declare function looksLikeTrigGuardJson(text: string): boolean; | ||
| export declare function enrichFailure(err: unknown, capture: GatewayFailureCapture | null): unknown; | ||
| export declare function wrapFetchWithCapture(fetchImpl?: typeof fetch): { | ||
| fetchImpl: typeof fetch; | ||
| capture: { | ||
| last: GatewayFailureCapture | null; | ||
| }; | ||
| }; | ||
| export declare function mapToolFailure(err: unknown): ReturnType<typeof toolError>; | ||
| //# sourceMappingURL=errors.d.ts.map |
| {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,MAAM,MAAM,YAAY,GACpB,6BAA6B,GAC7B,iBAAiB,GACjB,sBAAsB,GACtB,eAAe,GACf,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACd,aAAa,CAEf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CASzE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAU5E;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS5D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,GAAG,OAAO,CAS1F;AAED,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG;IAC9D,SAAS,EAAE,OAAO,KAAK,CAAC;IACxB,OAAO,EAAE;QAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAA;KAAE,CAAC;CACjD,CAmCA;AAWD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CA6JzE"} |
+164
| import { toolError } from "./mcpResponse.js"; | ||
| const SECRET_RE = /tg_live_[A-Za-z0-9_-]+|Bearer\s+\S+|sk-[A-Za-z0-9]+/gi; | ||
| export function sanitizePublicMessage(message) { | ||
| return message.replace(SECRET_RE, "[redacted]").replace(/\s+/g, " ").trim().slice(0, 280); | ||
| } | ||
| export function codedError(code, message, remediation, status) { | ||
| return Object.assign(new Error(message), { code, remediation, status }); | ||
| } | ||
| export function looksLikeHtml(text, contentType) { | ||
| const ct = (contentType ?? "").toLowerCase(); | ||
| if (ct.includes("text/html")) | ||
| return true; | ||
| const head = text.trim().slice(0, 400).toLowerCase(); | ||
| return (head.startsWith("<!doctype html") || | ||
| head.startsWith("<html") || | ||
| (head.includes("<html") && (head.includes("cloudflare") || head.includes("attention required")))); | ||
| } | ||
| export function parseJsonObject(text) { | ||
| try { | ||
| const value = JSON.parse(text); | ||
| if (value && typeof value === "object" && !Array.isArray(value)) { | ||
| return value; | ||
| } | ||
| return null; | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| export function looksLikeTrigGuardJson(text) { | ||
| const obj = parseJsonObject(text); | ||
| if (!obj) | ||
| return false; | ||
| if (typeof obj.protocol === "string" && obj.protocol.toLowerCase().includes("trigguard")) | ||
| return true; | ||
| if (typeof obj.authority === "string" && obj.authority.toLowerCase().includes("trigguard")) | ||
| return true; | ||
| if (typeof obj.error === "string" && String(obj.error).startsWith("TG_")) | ||
| return true; | ||
| if (typeof obj.code === "string" && String(obj.code).startsWith("TG_")) | ||
| return true; | ||
| const decision = typeof obj.decision === "string" ? obj.decision.toUpperCase() : ""; | ||
| return decision === "PERMIT" || decision === "DENY" || decision === "SILENCE" || decision === "ESCALATE"; | ||
| } | ||
| export function enrichFailure(err, capture) { | ||
| if (!capture) | ||
| return err; | ||
| const base = err instanceof Error ? err : new Error(String(err)); | ||
| const current = base; | ||
| return Object.assign(base, { | ||
| status: current.status ?? capture.status, | ||
| bodyText: current.bodyText ?? capture.bodyText, | ||
| contentType: current.contentType ?? capture.contentType, | ||
| }); | ||
| } | ||
| export function wrapFetchWithCapture(fetchImpl) { | ||
| const capture = { last: null }; | ||
| const impl = fetchImpl ?? fetch; | ||
| const wrapped = (async (input, init) => { | ||
| try { | ||
| const res = await impl(input, init); | ||
| const contentType = typeof res.headers?.get === "function" ? (res.headers.get("content-type") ?? "") : ""; | ||
| const bodyText = await res.text(); | ||
| if (!res.ok) { | ||
| capture.last = { status: res.status, bodyText, contentType }; | ||
| } | ||
| return { | ||
| ok: res.ok, | ||
| status: res.status, | ||
| headers: res.headers, | ||
| text: async () => bodyText, | ||
| json: async () => { | ||
| try { | ||
| return JSON.parse(bodyText); | ||
| } | ||
| catch { | ||
| return {}; | ||
| } | ||
| }, | ||
| }; | ||
| } | ||
| catch (e) { | ||
| capture.last = { | ||
| status: 0, | ||
| bodyText: e instanceof Error ? e.message : String(e), | ||
| contentType: "", | ||
| }; | ||
| throw e; | ||
| } | ||
| }); | ||
| return { fetchImpl: wrapped, capture }; | ||
| } | ||
| function classifiedToolError(code, message, failureClass, remediation) { | ||
| return toolError(code, message, remediation, failureClass); | ||
| } | ||
| export function mapToolFailure(err) { | ||
| const e = err; | ||
| const raw = e?.message ? String(e.message) : "authorization_failed"; | ||
| const lower = raw.toLowerCase(); | ||
| const codeFromErr = typeof e?.code === "string" ? e.code : undefined; | ||
| const bodyText = typeof e?.bodyText === "string" ? e.bodyText : ""; | ||
| const contentType = typeof e?.contentType === "string" ? e.contentType : ""; | ||
| const jsonBody = bodyText ? parseJsonObject(bodyText) : null; | ||
| const jsonCode = jsonBody && typeof (jsonBody.error ?? jsonBody.code) === "string" | ||
| ? String(jsonBody.error ?? jsonBody.code) | ||
| : ""; | ||
| if (codeFromErr === "missing_api_key" || (lower.includes("api key") && !jsonBody)) { | ||
| return classifiedToolError("missing_api_key", "TRIGGUARD_API_KEY is required for authorize_action", "AUTHENTICATION_ERROR", "Set TRIGGUARD_API_KEY in the MCP server env. Never pass keys in tool arguments."); | ||
| } | ||
| if (codeFromErr === "missing_organization" || | ||
| lower.includes("organizationid is required") || | ||
| lower.includes("organization id")) { | ||
| return classifiedToolError("missing_organization", "TRIGGUARD_ORG_ID is required for live API-key authorization", "ORGANIZATION_BINDING_ERROR", "Set TRIGGUARD_ORG_ID or TRIGGUARD_ORGANIZATION_ID. Do not invent a default org."); | ||
| } | ||
| if (codeFromErr === "org_binding_conflict") { | ||
| return classifiedToolError("org_binding_conflict", "TRIGGUARD_ORGANIZATION_ID and TRIGGUARD_ORG_ID disagree", "ORGANIZATION_BINDING_ERROR", "Set exactly one org id, or make both values identical."); | ||
| } | ||
| if (codeFromErr === "unknown_decision" || lower.includes("unknown_decision")) { | ||
| return classifiedToolError("unknown_decision", "Gateway returned a non-canonical decision; fail-closed (not PERMIT)", "TRIGGUARD_APPLICATION_ERROR", "Retry with health_check. Do not execute. Canonical states: PERMIT, DENY, SILENCE, ESCALATE."); | ||
| } | ||
| if (looksLikeHtml(bodyText || raw, contentType)) { | ||
| return classifiedToolError("edge_html_error", "Upstream returned HTML instead of a TrigGuard authorize payload", "EDGE_HTML_ERROR", "This is not a policy decision. Check the gateway URL and edge/WAF path. Do not execute."); | ||
| } | ||
| const httpStatus = typeof e?.status === "number" | ||
| ? e.status | ||
| : /HTTP (401|403|404|5\d\d)/.exec(raw)?.[1] | ||
| ? Number(/HTTP (401|403|404|5\d\d)/.exec(raw)?.[1]) | ||
| : undefined; | ||
| if (httpStatus === 401 || jsonCode === "TG_API_KEY_MISSING" || jsonCode === "TG_API_KEY_INVALID") { | ||
| return classifiedToolError("authentication_error", "Gateway rejected authentication (not a policy DENY)", "AUTHENTICATION_ERROR", "Check TRIGGUARD_API_KEY. Missing or invalid keys are not authorization decisions."); | ||
| } | ||
| if (looksLikeTrigGuardJson(bodyText) && httpStatus && httpStatus >= 400) { | ||
| const appCode = jsonCode && /^[A-Za-z0-9_]+$/.test(jsonCode) ? jsonCode.toLowerCase() : "trigguard_application_error"; | ||
| return classifiedToolError(appCode.startsWith("tg_") ? "trigguard_application_error" : appCode, "TrigGuard returned an application error (not a four-state decision)", "TRIGGUARD_APPLICATION_ERROR", "Inspect error.code. Do not treat this as PERMIT or as a policy DENY."); | ||
| } | ||
| if (httpStatus === 403) { | ||
| return classifiedToolError("upstream_proxy_error", "HTTP 403 without a TrigGuard JSON decision — not credentials, not policy", "UPSTREAM_PROXY_ERROR", "Do not claim the API key or plan failed. Confirm the gateway JSON path is reachable."); | ||
| } | ||
| if (httpStatus === 404 || lower.includes("unsupported surface") || lower.includes("unknown_surface")) { | ||
| return classifiedToolError("unsupported_surface", "Surface is not registered or not allowed", "TRIGGUARD_APPLICATION_ERROR", "Call list_surfaces and retry with a registered surface id."); | ||
| } | ||
| if (lower.includes("abort") || lower.includes("timeout") || e?.code === "timeout") { | ||
| return classifiedToolError("timeout", "Gateway request timed out", "NETWORK_ERROR", "Retry once. Duplicate authorize calls are not automatically retried by this server."); | ||
| } | ||
| if ((typeof e?.status === "number" && e.status >= 500) || | ||
| lower.includes("econnrefused") || | ||
| lower.includes("enotfound") || | ||
| lower.includes("fetch failed") || | ||
| lower.includes("unavailable") || | ||
| e?.code === "ECONNREFUSED" || | ||
| e?.code === "ENOTFOUND") { | ||
| return classifiedToolError("network_error", "TrigGuard gateway is unreachable or failed closed", "NETWORK_ERROR", "Check TRIGGUARD_GATEWAY_URL and health_check. Do not execute."); | ||
| } | ||
| if (lower.includes("payload") || codeFromErr === "payload_too_large") { | ||
| return classifiedToolError("malformed_request", "Tool input exceeds size or schema limits", "TRIGGUARD_APPLICATION_ERROR", "Reduce context size. Do not send secrets in context."); | ||
| } | ||
| if (lower.includes("protocol mismatch") || codeFromErr === "protocol_mismatch") { | ||
| return classifiedToolError("protocol_mismatch", "Response was not a TrigGuard authorize payload", "UPSTREAM_PROXY_ERROR", "Confirm TRIGGUARD_GATEWAY_URL points at the execution gateway."); | ||
| } | ||
| if (lower.includes("verif") || codeFromErr === "receipt_verification_failed") { | ||
| return classifiedToolError("receipt_verification_failed", "Receipt could not be verified", "TRIGGUARD_APPLICATION_ERROR", "Do not treat the decision as trusted evidence. Call verify_receipt or tg verify."); | ||
| } | ||
| return classifiedToolError(codeFromErr && /^[a-z_]+$/.test(codeFromErr) ? codeFromErr : "authorization_failed", sanitizePublicMessage(raw), "TRIGGUARD_APPLICATION_ERROR", e?.remediation ?? "Call health_check. Do not execute without PERMIT."); | ||
| } |
| /** Canonical live-wire decisions. Unknown values fail closed — never PERMIT. */ | ||
| export declare const CANONICAL_WIRE_DECISIONS: readonly ["PERMIT", "DENY", "SILENCE", "ESCALATE"]; | ||
| export type CanonicalWireDecision = (typeof CANONICAL_WIRE_DECISIONS)[number]; | ||
| export declare function isCanonicalWireDecision(value: string): value is CanonicalWireDecision; | ||
| /** | ||
| * Identity map only. SILENCE stays SILENCE. ESCALATE stays ESCALATE. | ||
| * BLOCK is not a wire state. Unknown is not PERMIT. | ||
| */ | ||
| export declare function assertCanonicalWireDecision(raw: string): CanonicalWireDecision; | ||
| //# sourceMappingURL=wireDecision.d.ts.map |
| {"version":3,"file":"wireDecision.d.ts","sourceRoot":"","sources":["../src/wireDecision.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,eAAO,MAAM,wBAAwB,oDAAqD,CAAC;AAE3F,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,qBAAqB,CAErF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAM9E"} |
| /** Canonical live-wire decisions. Unknown values fail closed — never PERMIT. */ | ||
| export const CANONICAL_WIRE_DECISIONS = ["PERMIT", "DENY", "SILENCE", "ESCALATE"]; | ||
| export function isCanonicalWireDecision(value) { | ||
| return CANONICAL_WIRE_DECISIONS.includes(value); | ||
| } | ||
| /** | ||
| * Identity map only. SILENCE stays SILENCE. ESCALATE stays ESCALATE. | ||
| * BLOCK is not a wire state. Unknown is not PERMIT. | ||
| */ | ||
| export function assertCanonicalWireDecision(raw) { | ||
| const value = raw.trim().toUpperCase(); | ||
| if (isCanonicalWireDecision(value)) | ||
| return value; | ||
| throw Object.assign(new Error(`unknown_decision: ${value || "(empty)"}`), { | ||
| code: "unknown_decision", | ||
| }); | ||
| } |
| # @trigguard/mcp-server 1.2.0 — Release summary | ||
| Publish-only bump so the npm tarball includes `mcpName` and aligned `server.json`. | ||
| ## Why 1.2.0 | ||
| Published `@trigguard/mcp-server@1.1.0` on npm does not contain: | ||
| ``` | ||
| mcpName=io.github.TrigGuard-AI/trigguard | ||
| ``` | ||
| This release ships that field in `package.json` and matching MCP registry metadata in `server.json`. | ||
| ## Unchanged | ||
| - Authorization protocol (PERMIT, DENY, SILENCE, ESCALATE) | ||
| - Gateway, policy bundles, receipts, SDK, and CLI behavior | ||
| - Production policy hash is not modified by this package | ||
| ## Four-state copy | ||
| TrigGuard supports four authorization states: | ||
| PERMIT | ||
| DENY | ||
| SILENCE | ||
| ESCALATE | ||
| PERMIT, DENY, and SILENCE are currently present in the active production policy bundle. | ||
| ESCALATE is protocol-supported and available for policy configurations that use approval workflows. |
+56
| { | ||
| "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", | ||
| "name": "io.github.TrigGuard-AI/trigguard", | ||
| "title": "TrigGuard", | ||
| "description": "Execution authorization for AI agents and automated systems.", | ||
| "websiteUrl": "https://trigguardai.com", | ||
| "repository": { | ||
| "url": "https://github.com/TrigGuard-AI/TrigGuard", | ||
| "source": "github", | ||
| "subfolder": "packages/trigguard-mcp-server" | ||
| }, | ||
| "version": "1.2.0", | ||
| "packages": [ | ||
| { | ||
| "registryType": "npm", | ||
| "registryBaseUrl": "https://registry.npmjs.org", | ||
| "identifier": "@trigguard/mcp-server", | ||
| "version": "1.2.0", | ||
| "runtimeHint": "npx", | ||
| "transport": { | ||
| "type": "stdio" | ||
| }, | ||
| "environmentVariables": [ | ||
| { | ||
| "name": "TRIGGUARD_API_KEY", | ||
| "description": "TrigGuard gateway API key (tg_live_…)", | ||
| "isRequired": true, | ||
| "isSecret": true, | ||
| "format": "string" | ||
| }, | ||
| { | ||
| "name": "TRIGGUARD_ORG_ID", | ||
| "description": "Organisation id sent as X-Consumer on POST /v1/authorize", | ||
| "isRequired": true, | ||
| "isSecret": false, | ||
| "format": "string" | ||
| }, | ||
| { | ||
| "name": "TRIGGUARD_GATEWAY_URL", | ||
| "description": "Execution gateway origin (default https://api.trigguardai.com)", | ||
| "isRequired": false, | ||
| "isSecret": false, | ||
| "format": "string" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "_meta": { | ||
| "io.modelcontextprotocol.registry/publisher-provided": { | ||
| "npmPackage": "@trigguard/mcp-server", | ||
| "doNotPublish": "@trigguard/mcp", | ||
| "authorityPath": "MCP → @trigguard/agent-sdk → POST /v1/authorize", | ||
| "decisions": ["PERMIT", "DENY", "SILENCE", "ESCALATE"] | ||
| } | ||
| } | ||
| } |
+3
-1
| #!/usr/bin/env node | ||
| export {}; | ||
| export declare function maybeHandleCli(argv?: readonly string[]): boolean; | ||
| /** True when this file is the process entrypoint, including macOS /tmp → /private/tmp and bin symlinks. */ | ||
| export declare function isCliEntrypoint(argv1?: string | undefined, moduleUrl?: string): boolean; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAqCA,wBAAgB,cAAc,CAAC,IAAI,GAAE,SAAS,MAAM,EAA0B,GAAG,OAAO,CAUvF;AAED,2GAA2G;AAC3G,wBAAgB,eAAe,CAC7B,KAAK,GAAE,MAAM,GAAG,SAA2B,EAC3C,SAAS,GAAE,MAAwB,GAClC,OAAO,CAST"} |
+66
-4
| #!/usr/bin/env node | ||
| import { readFileSync, realpathSync } from "node:fs"; | ||
| import { dirname, join } from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import { startStdioServer } from "./server.js"; | ||
| startStdioServer().catch((err) => { | ||
| console.error("trigguard-mcp-server failed:", err); | ||
| process.exit(1); | ||
| }); | ||
| const HELP = `trigguard-mcp-server — execution authorization for AI agents and automated systems | ||
| Usage: | ||
| npx @trigguard/mcp-server | ||
| trigguard-mcp-server [--help] [--version] | ||
| This process speaks MCP over stdio. Do not send logs to stdout. | ||
| Required for authorize_action: | ||
| TRIGGUARD_API_KEY | ||
| TRIGGUARD_ORG_ID (or TRIGGUARD_ORGANIZATION_ID) | ||
| Optional: | ||
| TRIGGUARD_GATEWAY_URL (default https://api.trigguardai.com) | ||
| Decisions: PERMIT | DENY | SILENCE | ESCALATE | ||
| TrigGuard authorizes execution. It does not execute actions. | ||
| Docs: https://github.com/TrigGuard-AI/TrigGuard/blob/main/docs/adoption/MCP_NPM_QUICKSTART.md | ||
| `; | ||
| function packageVersion() { | ||
| try { | ||
| const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"); | ||
| const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); | ||
| return pkg.version ?? "1.2.0"; | ||
| } | ||
| catch { | ||
| return "1.2.0"; | ||
| } | ||
| } | ||
| export function maybeHandleCli(argv = process.argv.slice(2)) { | ||
| if (argv.includes("--help") || argv.includes("-h")) { | ||
| process.stdout.write(HELP); | ||
| return true; | ||
| } | ||
| if (argv.includes("--version") || argv.includes("-v")) { | ||
| process.stdout.write(`${packageVersion()}\n`); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| /** True when this file is the process entrypoint, including macOS /tmp → /private/tmp and bin symlinks. */ | ||
| export function isCliEntrypoint(argv1 = process.argv[1], moduleUrl = import.meta.url) { | ||
| if (!argv1) | ||
| return false; | ||
| try { | ||
| const argvReal = realpathSync(argv1); | ||
| const moduleReal = realpathSync(fileURLToPath(moduleUrl)); | ||
| return argvReal === moduleReal; | ||
| } | ||
| catch { | ||
| return false; | ||
| } | ||
| } | ||
| if (isCliEntrypoint()) { | ||
| if (maybeHandleCli()) { | ||
| process.exit(0); | ||
| } | ||
| startStdioServer().catch((err) => { | ||
| console.error("trigguard-mcp-server failed:", err instanceof Error ? err.message : "unknown"); | ||
| process.exit(1); | ||
| }); | ||
| } |
@@ -7,6 +7,6 @@ /** Structured MCP tool response helpers (stdio JSON content). */ | ||
| readonly message: string; | ||
| readonly class?: string; | ||
| readonly remediation?: string; | ||
| }; | ||
| }; | ||
| export type ToolFailure = McpToolErrorBody; | ||
| export declare function jsonContent(data: unknown, opts?: { | ||
@@ -21,7 +21,3 @@ readonly isError?: boolean; | ||
| }; | ||
| export declare function toolError(code: string, message: string, remediation?: string): ReturnType<typeof jsonContent>; | ||
| export declare function isToolFailure(value: unknown): value is ToolFailure; | ||
| /** Wrap an async tool handler so thrown errors become structured MCP errors. */ | ||
| export declare function withToolErrors<TArgs extends unknown[]>(fn: (...args: TArgs) => Promise<unknown>): (...args: TArgs) => Promise<ReturnType<typeof jsonContent>>; | ||
| export declare function stringFieldOrNull(value: unknown): string | null; | ||
| export declare function toolError(code: string, message: string, remediation?: string, failureClass?: string): ReturnType<typeof jsonContent>; | ||
| //# sourceMappingURL=mcpResponse.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mcpResponse.d.ts","sourceRoot":"","sources":["../src/mcpResponse.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE3C,wBAAgB,WAAW,CACzB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACpC;IACD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAQA;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,UAAU,CAAC,OAAO,WAAW,CAAC,CAUhC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAQlE;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,SAAS,OAAO,EAAE,EACpD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,GACvC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAwB7D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAE/D"} | ||
| {"version":3,"file":"mcpResponse.d.ts","sourceRoot":"","sources":["../src/mcpResponse.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;CACH,CAAC;AAEF,wBAAgB,WAAW,CACzB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACpC;IACD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAQA;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,UAAU,CAAC,OAAO,WAAW,CAAC,CAWhC"} |
+2
-30
@@ -11,3 +11,3 @@ /** Structured MCP tool response helpers (stdio JSON content). */ | ||
| } | ||
| export function toolError(code, message, remediation) { | ||
| export function toolError(code, message, remediation, failureClass) { | ||
| const body = { | ||
@@ -18,2 +18,3 @@ ok: false, | ||
| message, | ||
| ...(failureClass ? { class: failureClass } : {}), | ||
| ...(remediation ? { remediation } : {}), | ||
@@ -24,30 +25,1 @@ }, | ||
| } | ||
| export function isToolFailure(value) { | ||
| return Boolean(value && | ||
| typeof value === "object" && | ||
| value.ok === false && | ||
| value.error && | ||
| typeof value.error.code === "string"); | ||
| } | ||
| /** Wrap an async tool handler so thrown errors become structured MCP errors. */ | ||
| export function withToolErrors(fn) { | ||
| return async (...args) => { | ||
| try { | ||
| const result = await fn(...args); | ||
| if (isToolFailure(result)) { | ||
| return toolError(result.error.code, result.error.message, result.error.remediation); | ||
| } | ||
| return jsonContent(result); | ||
| } | ||
| catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| if (/organizationId is required/i.test(message)) { | ||
| return toolError("organization_id_required", message, "Set TRIGGUARD_ORG_ID or TRIGGUARD_ORGANIZATION_ID (and TRIGGUARD_API_KEY). API key alone is not enough for authorize_action."); | ||
| } | ||
| return toolError("gateway_error", message, "Check TRIGGUARD_GATEWAY_URL, credentials, and network connectivity; then retry. Use health_check for diagnostics."); | ||
| } | ||
| }; | ||
| } | ||
| export function stringFieldOrNull(value) { | ||
| return typeof value === "string" && value.trim().length > 0 ? value : null; | ||
| } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAcL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAqR3E;AAED,wBAAsB,gBAAgB,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E"} | ||
| {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,OAAO,EAcL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA4S3E;AAED,wBAAsB,gBAAgB,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E"} |
+79
-49
@@ -5,12 +5,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import * as z from "zod"; | ||
| import { withToolErrors } from "./mcpResponse.js"; | ||
| import { jsonContent, toolError } from "./mcpResponse.js"; | ||
| import { enrichFailure, mapToolFailure, wrapFetchWithCapture } from "./errors.js"; | ||
| import { handleAuthorizeAction, handleExplainDecision, handleGetPolicy, handleGetSurface, handleHealthCheck, handleListOrgPolicies, handleListPendingPolicyChanges, handleListSurfaces, handlePreviewOrgPolicyIntent, handleVerifyReceipt, loadMcpServerConfig, mcpServerPackageVersion, resolvePolicyPlatformConfig, } from "./tools.js"; | ||
| const SESSION_REMEDIATION = "Set TRIGGUARD_SESSION_TOKEN (from tg login) and TRIGGUARD_ORG_ID. Gateway API key alone is not enough for org policy tools."; | ||
| export function createTrigGuardMcpServer(config) { | ||
| const { fetchImpl, capture } = wrapFetchWithCapture(config.fetchImpl); | ||
| const runtime = { ...config, fetchImpl }; | ||
| const agent = createTrigGuardAgent({ | ||
| gatewayUrl: config.gatewayUrl, | ||
| apiKey: config.apiKey, | ||
| organizationId: config.organizationId, | ||
| defaultActorId: config.defaultActorId, | ||
| gatewayUrl: runtime.gatewayUrl, | ||
| apiKey: runtime.apiKey, | ||
| organizationId: runtime.organizationId, | ||
| defaultActorId: runtime.defaultActorId, | ||
| fetchImpl, | ||
| }); | ||
| const fail = (err) => mapToolFailure(enrichFailure(err, capture.last)); | ||
| const server = new McpServer({ | ||
@@ -21,3 +26,3 @@ name: "trigguard-mcp-server", | ||
| server.registerTool("authorize_action", { | ||
| description: "Request TrigGuard authority for an action. Returns PERMIT, DENY, or SILENCE with execution receipt metadata and v1.1 explanation fields (reason_code, escalation_*, policy_version, evaluation_id). Requires TRIGGUARD_API_KEY and TRIGGUARD_ORG_ID (or TRIGGUARD_ORGANIZATION_ID).", | ||
| description: "Request TrigGuard authority for an action. Returns PERMIT, DENY, SILENCE, or ESCALATE with execution receipt metadata and v1.1 explanation fields (reason_code, escalation_*, policy_version, evaluation_id). SILENCE is withhold/no-commitment; ESCALATE is an approval lifecycle. Neither is PERMIT. Requires TRIGGUARD_API_KEY (gateway API key).", | ||
| inputSchema: { | ||
@@ -38,3 +43,10 @@ surface: z.string().describe("Execution surface (e.g. deploy.release)"), | ||
| }, | ||
| }, withToolErrors(async (input) => handleAuthorizeAction(agent, config, input))); | ||
| }, async (input) => { | ||
| try { | ||
| return jsonContent(await handleAuthorizeAction(agent, runtime, input)); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| server.registerTool("verify_receipt", { | ||
@@ -56,5 +68,12 @@ description: "Verify a TrigGuard execution receipt by execution id. Optional include_receipt (default false) returns the receipt body. Requires gateway API key for authenticated lookup when the gateway enforces auth.", | ||
| }, | ||
| }, withToolErrors(async (input) => handleVerifyReceipt(agent, input))); | ||
| }, async (input) => { | ||
| try { | ||
| return jsonContent(await handleVerifyReceipt(agent, input)); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| server.registerTool("explain_decision", { | ||
| description: "Explain a TrigGuard authority decision using an execution_id and/or a prior authorize_result payload. Returns structured reason_code, evidence_completeness, evidence_gaps, and next actions. Prefer passing authorize_result when available. Read-only — does not authorize.", | ||
| description: "Explain a TrigGuard authority decision using an execution_id and/or a prior authorize_result payload. Returns structured reason_code, missing requirements, and next actions. Read-only — does not authorize.", | ||
| inputSchema: { | ||
@@ -74,3 +93,14 @@ execution_id: z.string().optional().describe("Execution id (exec_…) from authorize_action"), | ||
| }, | ||
| }, withToolErrors(async (input) => handleExplainDecision(agent, input))); | ||
| }, async (input) => { | ||
| try { | ||
| const out = await handleExplainDecision(agent, input); | ||
| if (out && typeof out === "object" && "ok" in out && out.ok === false) { | ||
| return toolError(out.error.code, out.error.message, out.error.remediation); | ||
| } | ||
| return jsonContent(out); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| server.registerTool("get_surface", { | ||
@@ -88,3 +118,10 @@ description: "Read execution surface registry metadata for one surface id (read-only, no policy evaluation).", | ||
| }, | ||
| }, withToolErrors(async (input) => handleGetSurface(config, input))); | ||
| }, async (input) => { | ||
| try { | ||
| return jsonContent(await handleGetSurface(runtime, input)); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| server.registerTool("list_surfaces", { | ||
@@ -103,5 +140,12 @@ description: "List registered execution surfaces from the gateway well-known registry (or local registry override). Read-only discovery.", | ||
| }, | ||
| }, withToolErrors(async (input) => handleListSurfaces(config, input))); | ||
| }, async (input) => { | ||
| try { | ||
| return jsonContent(await handleListSurfaces(runtime, input)); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| server.registerTool("get_policy", { | ||
| description: "Read human-readable policy metadata for a surface from the bundled policy artifact. Does NOT evaluate live org policy — call authorize_action for PERMIT/DENY/SILENCE.", | ||
| description: "Read human-readable policy metadata for a surface from the bundled policy artifact. Does NOT evaluate live org policy — call authorize_action for PERMIT/DENY/SILENCE/ESCALATE.", | ||
| inputSchema: { | ||
@@ -117,3 +161,3 @@ surface: z.string().describe("Surface id"), | ||
| }, | ||
| }, withToolErrors(async (input) => handleGetPolicy(input))); | ||
| }, async (input) => jsonContent(handleGetPolicy(input))); | ||
| server.registerTool("list_org_policies", { | ||
@@ -129,16 +173,9 @@ description: "List org policies from the TrigGuard control plane Policy Platform. Requires TRIGGUARD_SESSION_TOKEN + TRIGGUARD_ORG_ID (session JWT rail — not gateway API key alone). Read-only.", | ||
| }, | ||
| }, withToolErrors(async () => { | ||
| }, async () => { | ||
| const resolved = resolvePolicyPlatformConfig(); | ||
| if (!resolved.ok) { | ||
| return { | ||
| ok: false, | ||
| error: { | ||
| code: "session_auth_required", | ||
| message: resolved.error, | ||
| remediation: SESSION_REMEDIATION, | ||
| }, | ||
| }; | ||
| return toolError("session_auth_required", resolved.error, SESSION_REMEDIATION); | ||
| } | ||
| return handleListOrgPolicies(resolved.config); | ||
| })); | ||
| return jsonContent(await handleListOrgPolicies(resolved.config)); | ||
| }); | ||
| server.registerTool("list_pending_policy_changes", { | ||
@@ -154,16 +191,9 @@ description: "List pending policy change requests awaiting approval. Requires TRIGGUARD_SESSION_TOKEN + TRIGGUARD_ORG_ID. Read-only.", | ||
| }, | ||
| }, withToolErrors(async () => { | ||
| }, async () => { | ||
| const resolved = resolvePolicyPlatformConfig(); | ||
| if (!resolved.ok) { | ||
| return { | ||
| ok: false, | ||
| error: { | ||
| code: "session_auth_required", | ||
| message: resolved.error, | ||
| remediation: SESSION_REMEDIATION, | ||
| }, | ||
| }; | ||
| return toolError("session_auth_required", resolved.error, SESSION_REMEDIATION); | ||
| } | ||
| return handleListPendingPolicyChanges(resolved.config); | ||
| })); | ||
| return jsonContent(await handleListPendingPolicyChanges(resolved.config)); | ||
| }); | ||
| server.registerTool("preview_policy_intent", { | ||
@@ -198,18 +228,11 @@ description: "Compile-only preview of a policy intent against the Policy Platform. Requires TRIGGUARD_SESSION_TOKEN + TRIGGUARD_ORG_ID. Does not persist. Not a substitute for authorize_action.", | ||
| }, | ||
| }, withToolErrors(async (input) => { | ||
| }, async (input) => { | ||
| const resolved = resolvePolicyPlatformConfig(); | ||
| if (!resolved.ok) { | ||
| return { | ||
| ok: false, | ||
| error: { | ||
| code: "session_auth_required", | ||
| message: resolved.error, | ||
| remediation: SESSION_REMEDIATION, | ||
| }, | ||
| }; | ||
| return toolError("session_auth_required", resolved.error, SESSION_REMEDIATION); | ||
| } | ||
| return handlePreviewOrgPolicyIntent(resolved.config, input); | ||
| })); | ||
| return jsonContent(await handlePreviewOrgPolicyIntent(resolved.config, input)); | ||
| }); | ||
| server.registerTool("health_check", { | ||
| description: "Check TrigGuard gateway reachability, policy engine status, registry hash, and whether API key / org id / session JWT env vars are present (values are never returned).", | ||
| description: "Check TrigGuard gateway reachability, policy engine status, registry hash, and whether API key / session JWT env vars are present (values are never returned).", | ||
| inputSchema: { | ||
@@ -228,3 +251,10 @@ detailed: z | ||
| }, | ||
| }, withToolErrors(async (input) => handleHealthCheck(config, input))); | ||
| }, async (input) => { | ||
| try { | ||
| return jsonContent(await handleHealthCheck(runtime, input)); | ||
| } | ||
| catch (err) { | ||
| return fail(err); | ||
| } | ||
| }); | ||
| return server; | ||
@@ -231,0 +261,0 @@ } |
+31
-9
@@ -5,3 +5,2 @@ import type { TrigGuardAgent } from "@trigguard/agent-sdk"; | ||
| import { type FetchFn } from "./gatewayRegistry.js"; | ||
| import type { ToolFailure } from "./mcpResponse.js"; | ||
| import { type PolicyPlatformConfig, type PreviewPolicyIntentInput } from "./policyPlatform.js"; | ||
@@ -17,9 +16,17 @@ export type McpServerConfig = { | ||
| }; | ||
| /** | ||
| * Organisation precedence: TRIGGUARD_ORGANIZATION_ID, then TRIGGUARD_ORG_ID, | ||
| * then TRIGGUARD_ACTIVE_ORG_ID. Conflicting explicit values fail closed. | ||
| */ | ||
| export declare function resolveOrganizationIdFromEnv(env?: NodeJS.ProcessEnv): { | ||
| ok: true; | ||
| organizationId?: string; | ||
| } | { | ||
| ok: false; | ||
| code: string; | ||
| message: string; | ||
| }; | ||
| export declare function loadMcpServerConfig(): McpServerConfig; | ||
| export declare function assertAuthorizeCredentials(config: McpServerConfig): void; | ||
| export declare function mcpServerPackageVersion(): string; | ||
| /** | ||
| * Fail closed before calling the gateway when API-key authorize cannot satisfy | ||
| * execution-sdk organization binding. Never throws. | ||
| */ | ||
| export declare function validateAuthorizeConfig(config: McpServerConfig): ToolFailure | null; | ||
| export type AuthorizeActionInput = { | ||
@@ -35,2 +42,3 @@ readonly surface: string; | ||
| readonly verify_url: string | null; | ||
| /** v1.1 additive fields (always present; null when gateway omitted them). */ | ||
| readonly reason_code: string | null; | ||
@@ -41,6 +49,13 @@ readonly escalation_required: boolean; | ||
| readonly evaluation_id: string | null; | ||
| /** Receipt metadata preserved from the gateway. Offline Ed25519 verify stays in execution-sdk / tg verify. */ | ||
| readonly protocol_version: string | null; | ||
| readonly surface: string | null; | ||
| readonly authority_key_id: string | null; | ||
| readonly receipt_present: boolean; | ||
| readonly signature_present: boolean; | ||
| }; | ||
| export declare function handleAuthorizeAction(agent: TrigGuardAgent, config: McpServerConfig, input: AuthorizeActionInput): Promise<AuthorizeActionOutput | ToolFailure>; | ||
| export declare function handleAuthorizeAction(agent: TrigGuardAgent, config: McpServerConfig, input: AuthorizeActionInput): Promise<AuthorizeActionOutput>; | ||
| export type VerifyReceiptInput = { | ||
| readonly execution_id: string; | ||
| /** When true, include the receipt body from gateway lookup (v1.1; default false). */ | ||
| readonly include_receipt?: boolean; | ||
@@ -94,5 +109,13 @@ }; | ||
| readonly execution_id?: string; | ||
| /** Optional pass-through of a prior authorize / gateway body still in agent context. */ | ||
| readonly authorize_result?: Record<string, unknown>; | ||
| }; | ||
| export declare function handleExplainDecision(agent: TrigGuardAgent, input: ExplainDecisionInput): Promise<ExplainDecisionOutput | ToolFailure>; | ||
| export declare function handleExplainDecision(agent: TrigGuardAgent, input: ExplainDecisionInput): Promise<ExplainDecisionOutput | { | ||
| ok: false; | ||
| error: { | ||
| code: string; | ||
| message: string; | ||
| remediation?: string; | ||
| }; | ||
| }>; | ||
| export type HealthCheckInput = { | ||
@@ -115,3 +138,2 @@ readonly detailed?: boolean; | ||
| readonly session_token_present: boolean; | ||
| readonly organization_id_present: boolean; | ||
| }; | ||
@@ -118,0 +140,0 @@ readonly details?: unknown; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"} |
+73
-87
@@ -5,9 +5,32 @@ import { readFileSync } from "node:fs"; | ||
| import { BUILTIN_SURFACE_DEFINITIONS, createSurfaceRegistry } from "@trigguard/surface-registry"; | ||
| import { recallAuthorizeEvidence, rememberAuthorizeEvidence } from "./authorizeCache.js"; | ||
| import { buildExplainDecision, extractAuthorizationEnrichment, } from "./decisionExplain.js"; | ||
| import { fetchGatewayRegistry } from "./gatewayRegistry.js"; | ||
| import { stringFieldOrNull } from "./mcpResponse.js"; | ||
| import { loadPolicyMetadataForSurface } from "./policyMetadata.js"; | ||
| import { listOrgPolicies, listPendingOrgPolicyChanges, loadPolicyPlatformConfig, previewOrgPolicyIntent, } from "./policyPlatform.js"; | ||
| import { codedError } from "./errors.js"; | ||
| import { assertCanonicalWireDecision } from "./wireDecision.js"; | ||
| const MAX_CONTEXT_JSON_BYTES = 64 * 1024; | ||
| /** | ||
| * Organisation precedence: TRIGGUARD_ORGANIZATION_ID, then TRIGGUARD_ORG_ID, | ||
| * then TRIGGUARD_ACTIVE_ORG_ID. Conflicting explicit values fail closed. | ||
| */ | ||
| export function resolveOrganizationIdFromEnv(env = process.env) { | ||
| const a = env.TRIGGUARD_ORGANIZATION_ID?.trim() || ""; | ||
| const b = env.TRIGGUARD_ORG_ID?.trim() || ""; | ||
| const c = env.TRIGGUARD_ACTIVE_ORG_ID?.trim() || ""; | ||
| if (a && b && a !== b) { | ||
| return { | ||
| ok: false, | ||
| code: "org_binding_conflict", | ||
| message: "TRIGGUARD_ORGANIZATION_ID and TRIGGUARD_ORG_ID disagree", | ||
| }; | ||
| } | ||
| const organizationId = a || b || c || undefined; | ||
| return { ok: true, organizationId }; | ||
| } | ||
| export function loadMcpServerConfig() { | ||
| const org = resolveOrganizationIdFromEnv(); | ||
| if (!org.ok) { | ||
| throw codedError(org.code, org.message); | ||
| } | ||
| const gatewayUrl = process.env.TRIGGUARD_GATEWAY_URL?.trim() || | ||
@@ -17,10 +40,20 @@ process.env.TRIGGUARD_ENDPOINT?.trim() || | ||
| const apiKey = process.env.TRIGGUARD_API_KEY?.trim() || undefined; | ||
| const organizationId = process.env.TRIGGUARD_ORGANIZATION_ID?.trim() || | ||
| process.env.TRIGGUARD_ORG_ID?.trim() || | ||
| process.env.TRIGGUARD_ACTIVE_ORG_ID?.trim() || | ||
| undefined; | ||
| const defaultActorId = process.env.TRIGGUARD_MCP_ACTOR_ID?.trim() || "trigguard-mcp-server"; | ||
| const registryPath = process.env.TRIGGUARD_SURFACE_REGISTRY_PATH?.trim() || undefined; | ||
| return { gatewayUrl, apiKey, organizationId, defaultActorId, registryPath }; | ||
| return { | ||
| gatewayUrl, | ||
| apiKey, | ||
| organizationId: org.organizationId, | ||
| defaultActorId, | ||
| registryPath, | ||
| }; | ||
| } | ||
| export function assertAuthorizeCredentials(config) { | ||
| if (!config.apiKey) { | ||
| throw codedError("missing_api_key", "TRIGGUARD_API_KEY is required for authorize_action"); | ||
| } | ||
| if (!config.organizationId) { | ||
| throw codedError("missing_organization", "organizationId is required for production API-key authorization"); | ||
| } | ||
| } | ||
| export function mcpServerPackageVersion() { | ||
@@ -30,39 +63,16 @@ try { | ||
| const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); | ||
| return typeof pkg.version === "string" ? pkg.version : "1.1.0"; | ||
| return typeof pkg.version === "string" ? pkg.version : "1.2.0"; | ||
| } | ||
| catch { | ||
| return "1.1.0"; | ||
| return "1.2.0"; | ||
| } | ||
| } | ||
| /** | ||
| * Fail closed before calling the gateway when API-key authorize cannot satisfy | ||
| * execution-sdk organization binding. Never throws. | ||
| */ | ||
| export function validateAuthorizeConfig(config) { | ||
| if (config.apiKey && !config.organizationId) { | ||
| return { | ||
| ok: false, | ||
| error: { | ||
| code: "organization_id_required", | ||
| message: "TRIGGUARD_ORG_ID (or TRIGGUARD_ORGANIZATION_ID) is required when TRIGGUARD_API_KEY is set", | ||
| remediation: "Export TRIGGUARD_ORG_ID from tg whoami / console, alongside TRIGGUARD_API_KEY. API key alone cannot authorize.", | ||
| }, | ||
| }; | ||
| export async function handleAuthorizeAction(agent, config, input) { | ||
| assertAuthorizeCredentials(config); | ||
| if (input.context) { | ||
| const bytes = Buffer.byteLength(JSON.stringify(input.context), "utf8"); | ||
| if (bytes > MAX_CONTEXT_JSON_BYTES) { | ||
| throw codedError("payload_too_large", "context exceeds 64 KiB"); | ||
| } | ||
| } | ||
| if (!config.apiKey) { | ||
| return { | ||
| ok: false, | ||
| error: { | ||
| code: "api_key_required", | ||
| message: "TRIGGUARD_API_KEY is required for authorize_action", | ||
| remediation: "Set TRIGGUARD_API_KEY (tg_live_…) and TRIGGUARD_ORG_ID in the MCP server environment (not in chat).", | ||
| }, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| export async function handleAuthorizeAction(agent, config, input) { | ||
| const invalid = validateAuthorizeConfig(config); | ||
| if (invalid) | ||
| return invalid; | ||
| const decision = await agent.authorize({ | ||
@@ -74,2 +84,3 @@ surface: input.surface, | ||
| }); | ||
| const wire = assertCanonicalWireDecision(decision.label()); | ||
| const enrichment = extractAuthorizationEnrichment({ | ||
@@ -80,4 +91,12 @@ body: decision.raw.body, | ||
| }); | ||
| const out = { | ||
| decision: decision.label(), | ||
| const body = decision.raw.body && typeof decision.raw.body === "object" | ||
| ? decision.raw.body | ||
| : {}; | ||
| const receipt = decision.receipt; | ||
| const protocolVersion = (typeof body.protocol === "string" && body.protocol) || | ||
| (typeof receipt?.protocolVersion === "string" | ||
| ? receipt.protocolVersion | ||
| : null); | ||
| return { | ||
| decision: wire, | ||
| execution_id: decision.executionId ?? null, | ||
@@ -87,13 +106,8 @@ receipt_hash: decision.receipt?.receiptHash ?? null, | ||
| ...enrichment, | ||
| protocol_version: protocolVersion || null, | ||
| surface: receipt?.surface ?? input.surface, | ||
| authority_key_id: receipt?.authorityKeyId ?? null, | ||
| receipt_present: Boolean(receipt), | ||
| signature_present: Boolean(receipt?.authoritySignature), | ||
| }; | ||
| const rawBody = decision.raw.body && typeof decision.raw.body === "object" && !Array.isArray(decision.raw.body) | ||
| ? decision.raw.body | ||
| : {}; | ||
| rememberAuthorizeEvidence(out.execution_id, { | ||
| enrichment, | ||
| body: { ...rawBody, ...out }, | ||
| decision: out.decision, | ||
| surface: input.surface, | ||
| }); | ||
| return out; | ||
| } | ||
@@ -107,4 +121,4 @@ export async function handleVerifyReceipt(agent, input) { | ||
| valid: result.valid, | ||
| decision: receipt ? stringFieldOrNull(receipt.decision) : null, | ||
| surface: receipt ? stringFieldOrNull(receipt.surface) : null, | ||
| decision: receipt ? String(receipt.decision ?? null) : null, | ||
| surface: receipt ? String(receipt.surface ?? null) : null, | ||
| }; | ||
@@ -142,10 +156,5 @@ if (input.include_receipt) { | ||
| } | ||
| function coerceLimit(limitRaw, fallback = 50) { | ||
| const n = typeof limitRaw === "number" ? limitRaw : Number(limitRaw); | ||
| if (!Number.isFinite(n)) | ||
| return fallback; | ||
| return Math.min(Math.max(1, Math.floor(n)), 200); | ||
| } | ||
| export async function handleListSurfaces(config, input = {}) { | ||
| const limit = coerceLimit(input.limit, 50); | ||
| const limitRaw = input.limit ?? 50; | ||
| const limit = Math.min(Math.max(1, Math.floor(limitRaw)), 200); | ||
| const prefix = input.prefix?.trim() || ""; | ||
@@ -226,9 +235,7 @@ let surfaces; | ||
| surface: typeof body.surface === "string" ? body.surface : null, | ||
| fromAuthorizeResult: true, | ||
| }); | ||
| } | ||
| const executionId = input.execution_id; | ||
| const cached = recallAuthorizeEvidence(executionId); | ||
| const verified = await agent.verifyExecution(executionId); | ||
| if (!verified.receipt && !cached) { | ||
| if (!verified.receipt) { | ||
| return { | ||
@@ -239,30 +246,10 @@ ok: false, | ||
| message: `No receipt found for ${executionId}`, | ||
| remediation: "Confirm the execution_id from authorize_action and that the gateway ledger retained it. If authorize just succeeded in this session, retry explain_decision with authorize_result.", | ||
| remediation: "Confirm the execution_id from authorize_action and that the gateway ledger retained it.", | ||
| }, | ||
| }; | ||
| } | ||
| const receipt = verified.receipt && typeof verified.receipt === "object" | ||
| ? verified.receipt | ||
| : {}; | ||
| const mergedBody = { | ||
| execution_id: executionId, | ||
| receipt, | ||
| ...(cached?.body ?? {}), | ||
| }; | ||
| if (cached?.enrichment.reason_code && !mergedBody.reason_code) { | ||
| mergedBody.reason_code = cached.enrichment.reason_code; | ||
| } | ||
| if (cached?.enrichment.policy_version && !mergedBody.policy_bundle_version) { | ||
| mergedBody.policy_bundle_version = cached.enrichment.policy_version; | ||
| } | ||
| if (cached?.enrichment.escalation_required && !mergedBody.escalation && cached.body.escalation) { | ||
| mergedBody.escalation = cached.body.escalation; | ||
| } | ||
| return buildExplainDecision({ | ||
| body: mergedBody, | ||
| receipt, | ||
| body: { execution_id: executionId, receipt: verified.receipt }, | ||
| receipt: verified.receipt, | ||
| executionId, | ||
| decision: cached?.decision ?? null, | ||
| surface: cached?.surface ?? null, | ||
| fromSessionCache: Boolean(cached), | ||
| }); | ||
@@ -343,3 +330,2 @@ } | ||
| session_token_present: sessionPresent, | ||
| organization_id_present: Boolean(config.organizationId), | ||
| }, | ||
@@ -346,0 +332,0 @@ }; |
| { | ||
| "server": "trigguard-mcp-server", | ||
| "version": "1.1.0", | ||
| "version": "1.2.0", | ||
| "package": "@trigguard/mcp-server", | ||
| "transport": "stdio", | ||
| "authority_path": "MCP → @trigguard/agent-sdk → POST /v1/execute", | ||
| "authority_path": "MCP → @trigguard/agent-sdk → POST /v1/authorize", | ||
| "semver_policy": "Additive minors (1.x). Breaking renames/removals require 2.0.0.", | ||
| "auth_rails": { | ||
| "gateway_api_key": { | ||
| "env": ["TRIGGUARD_API_KEY"], | ||
| "env": ["TRIGGUARD_API_KEY", "TRIGGUARD_ORG_ID"], | ||
| "tools": ["authorize_action", "verify_receipt", "explain_decision", "get_surface", "list_surfaces", "health_check"] | ||
@@ -23,3 +23,3 @@ }, | ||
| "mutates_reality": false, | ||
| "description": "Request TrigGuard authority for an action. Returns PERMIT, DENY, or SILENCE with execution receipt metadata and v1.1 explanation fields. Requires TRIGGUARD_API_KEY and TRIGGUARD_ORG_ID.", | ||
| "description": "Request TrigGuard authority for an action. Returns PERMIT, DENY, SILENCE, or ESCALATE with execution receipt metadata and v1.1 explanation fields.", | ||
| "parameters": { | ||
@@ -31,3 +31,3 @@ "surface": { "type": "string", "required": true, "example": "deploy.release" }, | ||
| "returns": { | ||
| "decision": { "enum": ["PERMIT", "DENY", "SILENCE"] }, | ||
| "decision": { "enum": ["PERMIT", "DENY", "SILENCE", "ESCALATE"] }, | ||
| "execution_id": { "type": "string", "nullable": true }, | ||
@@ -214,5 +214,5 @@ "receipt_hash": { "type": "string", "nullable": true }, | ||
| "READ_ONLY": "No authority decision; safe discovery", | ||
| "AUTHORIZATION": "Returns PERMIT/DENY/SILENCE — call before mutating actions", | ||
| "AUTHORIZATION": "Returns PERMIT/DENY/SILENCE/ESCALATE — call before mutating actions", | ||
| "VERIFICATION": "Validates existing receipt evidence" | ||
| } | ||
| } |
+8
-6
| { | ||
| "name": "@trigguard/mcp-server", | ||
| "version": "1.1.0", | ||
| "description": "Production-grade TrigGuard MCP stdio server — authority via @trigguard/agent-sdk only", | ||
| "version": "1.2.0", | ||
| "description": "MCP stdio server for TrigGuard execution authorization (PERMIT/DENY/SILENCE/ESCALATE)", | ||
| "mcpName": "io.github.TrigGuard-AI/trigguard", | ||
| "type": "module", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "bin": { | ||
| "trigguard-mcp-server": "dist/index.js" | ||
| "trigguard-mcp-server": "./dist/index.js" | ||
| }, | ||
@@ -63,5 +64,6 @@ "exports": { | ||
| "README.md", | ||
| "LICENSE", | ||
| "server.json", | ||
| "mcp-tools-metadata.json", | ||
| "RELEASE_NOTES_v1.1.0.md", | ||
| "RELEASE_NOTES_v1.2.0.md", | ||
| "MIGRATION_v1.1.0.md" | ||
@@ -68,0 +70,0 @@ ], |
+66
-89
| # @trigguard/mcp-server | ||
| Production-grade **stdio MCP server** for TrigGuard authority. | ||
| **Execution authorization for AI agents and automated systems.** | ||
| **Version:** 1.1.0 (backwards-compatible additive release over the 7-tool v1.0 surface) | ||
| TrigGuard authorizes execution. It does not execute actions. This package is a stdio MCP transport: tools request authority and return **PERMIT · DENY · SILENCE · ESCALATE** plus a signed receipt. Policy stays on the gateway. | ||
| Transport only — all decisions flow through: | ||
| ``` | ||
| MCP tool → @trigguard/agent-sdk → POST /v1/execute | ||
| MCP client → @trigguard/mcp-server → @trigguard/agent-sdk → POST /v1/authorize | ||
| ``` | ||
| **Decision model:** PERMIT · DENY · SILENCE (no ESCALATE wire state for agents — escalation metadata is returned as explanation fields) | ||
| Do not install `@trigguard/mcp` (abandoned scaffold). | ||
| ## Install (npm) | ||
| ## Install | ||
| ```bash | ||
| npm install -g @trigguard/mcp-server | ||
| export TRIGGUARD_API_KEY=tg_live_… | ||
| npx @trigguard/mcp-server --help | ||
| npx @trigguard/mcp-server | ||
| ``` | ||
| For application code (non-MCP), use [`trigguard`](../../packages/trigguard-sdk/README.md) — `npm install trigguard`. | ||
| Or: `npm install -g @trigguard/mcp-server` | ||
| Full MCP setup: [MCP npm quickstart](../../docs/adoption/MCP_NPM_QUICKSTART.md) | ||
| ## 30-second config (Cursor) | ||
| ## Authentication (two rails) | ||
| | Rail | Env vars | Tools | | ||
| |------|----------|-------| | ||
| | **Gateway API key** | `TRIGGUARD_API_KEY` + `TRIGGUARD_ORG_ID` (or `TRIGGUARD_ORGANIZATION_ID`) (+ optional `TRIGGUARD_GATEWAY_URL`) | `authorize_action`, `verify_receipt`, `explain_decision`, `get_surface`, `list_surfaces`, `health_check` | | ||
| | **Session JWT (org / Policy Platform)** | `TRIGGUARD_SESSION_TOKEN` + `TRIGGUARD_ORG_ID` (+ optional `TRIGGUARD_CONTROL_PLANE_URL`) | `list_org_policies`, `list_pending_policy_changes`, `preview_policy_intent` | | ||
| - API key alone is **not** enough for org policy tools. | ||
| - Session JWT alone is **not** enough for `authorize_action`. | ||
| - Credentials stay in the MCP server process — never pass them in LLM tool arguments. | ||
| Obtain a session via `tg login`, then export the token / org id (see `tg whoami`). | ||
| ## Tools | ||
| | Tool | Auth rail | Purpose | | ||
| |------|-----------|---------| | ||
| | `authorize_action` | API key | Governed PERMIT / DENY / SILENCE + v1.1 explanation fields | | ||
| | `verify_receipt` | API key | Lookup / verify by execution id; optional `include_receipt` | | ||
| | `explain_decision` | API key | Structured “why” for a decision (`execution_id` and/or `authorize_result`) | | ||
| | `get_surface` | Public registry | One surface’s registry metadata (read-only) | | ||
| | `list_surfaces` | Public registry | Discover registered surface ids | | ||
| | `get_policy` | Bundled artifact | Human-readable **metadata only** — not live evaluation | | ||
| | `list_org_policies` | Session JWT | List org policies (read-only) | | ||
| | `list_pending_policy_changes` | Session JWT | Pending change requests (read-only) | | ||
| | `preview_policy_intent` | Session JWT | Compile-only preview — does not persist | | ||
| | `health_check` | None / optional key | Gateway reachability + policy engine status | | ||
| **Not MCP tools (by design):** policy create / approve / activate, audit history browse. Use `tg policy …` or the console. | ||
| ### `authorize_action` response (v1.1 additive fields) | ||
| Existing fields are unchanged. New fields are always present (nullable): | ||
| | Field | Meaning | | ||
| |-------|---------| | ||
| | `reason_code` | Public gateway reason code when available | | ||
| | `escalation_required` | `true` when escalation metadata / ESCALATE evidence is present | | ||
| | `escalation_reason` | Reason associated with escalation when present | | ||
| | `policy_version` | Policy bundle version when available | | ||
| | `evaluation_id` | Decision / evaluation id when available (often same as `execution_id`) | | ||
| ### `verify_receipt` (v1.1) | ||
| | Parameter | Default | Meaning | | ||
| |-----------|---------|---------| | ||
| | `include_receipt` | `false` | When `true`, include the receipt document under `receipt` | | ||
| ## Cursor / Claude configuration | ||
| ```json | ||
@@ -80,6 +28,8 @@ { | ||
| "trigguard": { | ||
| "command": "trigguard-mcp-server", | ||
| "command": "npx", | ||
| "args": ["-y", "@trigguard/mcp-server"], | ||
| "env": { | ||
| "TRIGGUARD_GATEWAY_URL": "https://api.trigguardai.com", | ||
| "TRIGGUARD_API_KEY": "${env:TRIGGUARD_API_KEY}" | ||
| "TRIGGUARD_API_KEY": "${env:TRIGGUARD_API_KEY}", | ||
| "TRIGGUARD_ORG_ID": "${env:TRIGGUARD_ORG_ID}" | ||
| } | ||
@@ -91,43 +41,70 @@ } | ||
| For org policy tools, also set `TRIGGUARD_SESSION_TOKEN` and `TRIGGUARD_ORG_ID` in the MCP server env. | ||
| Claude Desktop uses the same `command` / `args` / `env` under `mcpServers` in `claude_desktop_config.json`. | ||
| ## Environment | ||
| ## One authorization example | ||
| | Variable | Purpose | | ||
| |----------|---------| | ||
| | `TRIGGUARD_GATEWAY_URL` | Gateway base URL (default: `https://api.trigguardai.com`) | | ||
| | `TRIGGUARD_API_KEY` | API key (`tg_live_…`) — required for `authorize_action` | | ||
| | `TRIGGUARD_ORG_ID` / `TRIGGUARD_ORGANIZATION_ID` | Organisation id for API-key authorize (`X-Consumer`) and org policy tools | | ||
| | `TRIGGUARD_MCP_ACTOR_ID` | Actor id for authorize calls (default: `trigguard-mcp-server`) | | ||
| | `TRIGGUARD_SURFACE_REGISTRY_PATH` | Optional local registry override (dev only) | | ||
| | `TRIGGUARD_POLICY_BUNDLE_PATH` | Optional local policy metadata override (dev only) | | ||
| | `TRIGGUARD_SESSION_TOKEN` | Control-plane session JWT for org policy tools | | ||
| | `TRIGGUARD_ORG_ID` | Active org / workspace id for org policy tools | | ||
| | `TRIGGUARD_CONTROL_PLANE_URL` | Control plane base (default: `https://control.trigguardai.com`) | | ||
| Ask the host: | ||
| ## Errors | ||
| > Use TrigGuard `authorize_action` for surface `deploy.release` with repository `TrigGuard-AI/TrigGuard`. | ||
| Auth / validation failures return MCP `isError: true` with a structured body: | ||
| Typical PERMIT payload (shape): | ||
| ```json | ||
| { | ||
| "ok": false, | ||
| "error": { | ||
| "code": "session_auth_required", | ||
| "message": "…", | ||
| "remediation": "…" | ||
| } | ||
| "decision": "PERMIT", | ||
| "execution_id": "exec_…", | ||
| "receipt_hash": "…", | ||
| "verify_url": "https://api.trigguardai.com/verify/exec_…" | ||
| } | ||
| ``` | ||
| Then call `verify_receipt` with that `execution_id`. | ||
| ## Four-state semantics | ||
| TrigGuard supports four authorization states: | ||
| PERMIT | ||
| DENY | ||
| SILENCE | ||
| ESCALATE | ||
| PERMIT, DENY, and SILENCE are currently present in the active production policy bundle. | ||
| ESCALATE is protocol-supported and available for policy configurations that use approval workflows. | ||
| | Decision | Meaning | May the caller execute? | | ||
| |----------|---------|-------------------------| | ||
| | **PERMIT** | Authorized | Yes, the **caller** executes | | ||
| | **DENY** | Refused | No | | ||
| | **SILENCE** | No authorization commitment | No (not an approval queue) | | ||
| | **ESCALATE** | Policy-required intervention | No until released | | ||
| Unknown gateway values fail closed. They are never mapped to PERMIT. SILENCE is not aliased to ESCALATE. SILENCE does not mean approval and does not automatically escalate. ESCALATE is protocol-supported; it is not claimed as live in the current production policy bundle. | ||
| ## Receipt verification | ||
| `verify_receipt` looks up the execution on the gateway. Offline Ed25519 verification of a receipt body is `tg verify` / `@trigguard/execution-sdk` after you hold trusted keys. | ||
| ## Security model | ||
| - No local policy evaluation | ||
| - No default organisation | ||
| - `TRIGGUARD_API_KEY` + `TRIGGUARD_ORG_ID` required for `authorize_action` | ||
| - Credentials live in MCP server env — never in tool arguments | ||
| - This server does not actuate robots, deploys, or payments | ||
| Robotics surfaces (`robot.motion.execute`, `robot.actuator.command`, `robot.zone.enter`, `robot.emergency.stop.override`) are authorization-only in this transport. | ||
| ## Tools | ||
| `authorize_action` · `verify_receipt` · `explain_decision` · `get_surface` · `list_surfaces` · `get_policy` · `health_check` · org policy read tools (session JWT) | ||
| ## Monorepo development | ||
| Contributor-only: | ||
| ```bash | ||
| npm run build -w @trigguard/mcp-server | ||
| TRIGGUARD_API_KEY=tg_live_… npm run start -w @trigguard/mcp-server | ||
| npm test -w @trigguard/mcp-server | ||
| ``` | ||
| ## Release notes | ||
| See [RELEASE_NOTES_v1.1.0.md](./RELEASE_NOTES_v1.1.0.md) and [MIGRATION_v1.1.0.md](./MIGRATION_v1.1.0.md). |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
102882
18.91%42
23.53%1929
21.17%18
-5.26%109
-17.42%10
25%