@mappies/miniwat-mcp-core 0.9.0 → 0.11.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.cjs CHANGED
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(index_exports);
26
26
 
27
27
  // src/entities/toolDispatcher.ts
28
28
  var import_miniwat_agent_core = require("@mappies/miniwat-agent-core");
29
+ var import_crypto = require("crypto");
29
30
  var ToolDispatcher = class {
30
31
  constructor(mcp) {
31
32
  this.mcp = mcp;
@@ -41,19 +42,70 @@ var ToolDispatcher = class {
41
42
  this.servers.push(new import_miniwat_agent_core.McpServer({ name: server, ...service }));
42
43
  const pck = await import(service.package);
43
44
  for (let t of service.tools) {
45
+ if (t.enabled === false) continue;
44
46
  let definition = pck[t.import];
45
47
  let tool = new import_miniwat_agent_core.Tool({
46
48
  name: definition.name,
49
+ displayName: definition.displayName,
47
50
  description: definition.description,
48
51
  schema: definition.inputSchema,
49
52
  func: pck[definition.name],
50
53
  service: server,
51
- getInputText: pck[`${definition.name}InputText`]
54
+ getInputText: pck[`${definition.name}InputText`],
55
+ readOnly: definition.readOnly === true,
56
+ requiresApproval: definition.requiresApproval === true,
57
+ inputGuardrail: this.resolveInputGuardrail(server, definition, pck),
58
+ outputGuardrail: this.resolveOutputGuardrail(server, definition, pck)
52
59
  });
53
60
  this.tools.push(tool);
54
61
  }
55
62
  }
56
63
  }
64
+ // Resolves a tool definition's named inputGuardrail from the OWNING package's INPUT_GUARDRAILS
65
+ // factory export. Validation is loud and happens at init — never deferred to tool invocation —
66
+ // so the app cannot reach agent.stream() with a partially resolved tool.
67
+ resolveInputGuardrail(server, definition, pck) {
68
+ const name = definition.inputGuardrail;
69
+ if (name === void 0) return void 0;
70
+ const factories = pck.INPUT_GUARDRAILS;
71
+ if (factories === void 0 || typeof factories !== "object") {
72
+ throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' but its package exports no INPUT_GUARDRAILS.`);
73
+ }
74
+ const factory = factories[name];
75
+ if (factory === void 0) {
76
+ throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' not found in the package's INPUT_GUARDRAILS.`);
77
+ }
78
+ if (typeof factory !== "function") {
79
+ throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);
80
+ }
81
+ const guardrail = factory();
82
+ if (guardrail === void 0 || typeof guardrail.evaluate !== "function") {
83
+ throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolInputGuardrail.`);
84
+ }
85
+ return guardrail;
86
+ }
87
+ // Resolves a tool definition's named outputGuardrail from the OWNING package's OUTPUT_GUARDRAILS
88
+ // factory export. Validated loudly at init, mirroring resolveInputGuardrail.
89
+ resolveOutputGuardrail(server, definition, pck) {
90
+ const name = definition.outputGuardrail;
91
+ if (name === void 0) return void 0;
92
+ const factories = pck.OUTPUT_GUARDRAILS;
93
+ if (factories === void 0 || typeof factories !== "object") {
94
+ throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' but its package exports no OUTPUT_GUARDRAILS.`);
95
+ }
96
+ const factory = factories[name];
97
+ if (factory === void 0) {
98
+ throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' not found in the package's OUTPUT_GUARDRAILS.`);
99
+ }
100
+ if (typeof factory !== "function") {
101
+ throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);
102
+ }
103
+ const guardrail = factory();
104
+ if (guardrail === void 0 || typeof guardrail.evaluate !== "function") {
105
+ throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolOutputGuardrail.`);
106
+ }
107
+ return guardrail;
108
+ }
57
109
  async list() {
58
110
  await this.init();
59
111
  return this.tools;
@@ -70,7 +122,30 @@ var ToolDispatcher = class {
70
122
  const tool = await this.get(name);
71
123
  return tool?.getInputText?.(input) || "";
72
124
  }
73
- async execute(name, input) {
125
+ // Evaluates the dispatch-boundary input guardrail against the concrete invocation. Returns
126
+ // undefined when execution may proceed; a ToolAuthorizationResult (defer/deny) when it is
127
+ // blocked. On defer the pending call is recorded via the awaited onDeferred sink BEFORE the
128
+ // deferred result is returned (persist-before-respond); if a tool can defer but no sink is
129
+ // supplied, it FAILS CLOSED (denied) — a deferral must never be reported as unrecoverable.
130
+ async authorize(name, input, tool, auth) {
131
+ if (!auth) {
132
+ return void 0;
133
+ }
134
+ const decision = auth.guardrail.evaluate({ toolName: name, arguments: input }, auth.context);
135
+ if (decision.status === "allow") {
136
+ return void 0;
137
+ }
138
+ if (decision.status === "deny") {
139
+ return { authorization: "deny", succeeded: false, text: decision.reason };
140
+ }
141
+ const pending = { id: (0, import_crypto.randomUUID)(), name, arguments: input ?? {}, status: "pending" };
142
+ if (!auth.onDeferred) {
143
+ return { authorization: "deny", succeeded: false, text: "Authorization is unavailable for this operation." };
144
+ }
145
+ await auth.onDeferred(pending);
146
+ return { authorization: "defer", succeeded: false, text: decision.reason, pending };
147
+ }
148
+ async execute(name, input, auth) {
74
149
  const tool = await this.get(name);
75
150
  if (!tool) {
76
151
  throw new Error(`Tool ${name} could not be found.`);
@@ -78,12 +153,18 @@ var ToolDispatcher = class {
78
153
  if (!tool.func) {
79
154
  throw new Error(`Tool ${name} does not have a function.`);
80
155
  }
156
+ const authorization = await this.authorize(name, input, tool, auth);
157
+ if (authorization) {
158
+ return authorization;
159
+ }
160
+ let result;
81
161
  try {
82
- let result = await tool.func(input);
83
- return result;
162
+ result = await tool.func(input);
84
163
  } catch (e) {
85
164
  throw new Error(`Failed to run ${name} tool: ${e?.message}`);
86
165
  }
166
+ const guardrail = tool.outputGuardrail ?? new import_miniwat_agent_core.DefaultOutputGuardrail();
167
+ return guardrail.evaluate(result);
87
168
  }
88
169
  };
89
170
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/entities/toolDispatcher.ts"],"sourcesContent":["export { ToolDispatcher } from './entities/toolDispatcher';\nexport { McpConfig, McpServer, McpToolImport } from './interfaces/mcpConfig';","import { Tool, McpServer } from \"@mappies/miniwat-agent-core\";\nimport { McpConfig } from \"../interfaces/mcpConfig\";\n\nexport class ToolDispatcher\n{\n private tools:Tool[]\n private servers:McpServer[]\n\n constructor(private mcp?:McpConfig)\n {\n this.tools = []\n this.servers = []\n }\n\n async init()\n {\n if(this.tools.length !== 0) return;\n\n this.tools = [];\n\n if(this.mcp === undefined) return;\n\n for(let server of Object.keys(this.mcp.mcpServers))\n {\n let service = this.mcp.mcpServers[server]\n\n this.servers.push(new McpServer({name: server, ...service}))\n \n const pck = await import(service.package);\n for(let t of service.tools)\n {\n let definition = pck[t.import];\n let tool = new Tool({ \n name: definition.name, \n description: definition.description, \n schema: definition.inputSchema, \n func: pck[definition.name],\n service: server,\n getInputText: pck[`${definition.name}InputText`]\n })\n \n this.tools.push(tool)\n }\n }\n }\n\n async list(): Promise<Tool[]>\n {\n await this.init()\n return this.tools;\n }\n\n async listServers(): Promise<McpServer[]>\n {\n await this.init()\n return this.servers;\n }\n\n async get(name:string): Promise<Tool|undefined>\n {\n await this.init()\n return this.tools.find(tool => tool.name === name);\n }\n\n async getInputText(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n\n return tool?.getInputText?.(input) || '';\n }\n\n async execute(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n \n if(!tool)\n {\n throw new Error(`Tool ${name} could not be found.`);\n }\n\n if(!tool.func)\n {\n throw new Error(`Tool ${name} does not have a function.`);\n }\n\n try\n {\n let result = await tool.func(input);\n return result;\n }\n catch(e:any)\n {\n throw new Error(`Failed to run ${name} tool: ${e?.message}`);\n }\n }\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,gCAAgC;AAGzB,IAAM,iBAAN,MACP;AAAA,EAII,YAAoB,KACpB;AADoB;AAEhB,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU,CAAC;AAAA,EACpB;AAAA,EAEA,MAAM,OACN;AACI,QAAG,KAAK,MAAM,WAAW,EAAG;AAE5B,SAAK,QAAQ,CAAC;AAEd,QAAG,KAAK,QAAQ,OAAW;AAE3B,aAAQ,UAAU,OAAO,KAAK,KAAK,IAAI,UAAU,GACjD;AACI,UAAI,UAAU,KAAK,IAAI,WAAW,MAAM;AAExC,WAAK,QAAQ,KAAK,IAAI,oCAAU,EAAC,MAAM,QAAQ,GAAG,QAAO,CAAC,CAAC;AAE3D,YAAM,MAAM,MAAM,OAAO,QAAQ;AACjC,eAAQ,KAAK,QAAQ,OACrB;AACI,YAAI,aAAa,IAAI,EAAE,MAAM;AAC7B,YAAI,OAAO,IAAI,+BAAK;AAAA,UACI,MAAM,WAAW;AAAA,UACjB,aAAa,WAAW;AAAA,UACxB,QAAQ,WAAW;AAAA,UACnB,MAAM,IAAI,WAAW,IAAI;AAAA,UACzB,SAAS;AAAA,UACT,cAAc,IAAI,GAAG,WAAW,IAAI,WAAW;AAAA,QACnD,CAAC;AAErB,aAAK,MAAM,KAAK,IAAI;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,OACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,cACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,IAAI,MACV;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK,MAAM,KAAK,UAAQ,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,MAAM,aAAa,MAAa,OAChC;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,WAAO,MAAM,eAAe,KAAK,KAAK;AAAA,EAC1C;AAAA,EAEA,MAAM,QAAQ,MAAa,OAC3B;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,QAAG,CAAC,MACJ;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,sBAAsB;AAAA,IACtD;AAEA,QAAG,CAAC,KAAK,MACT;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,4BAA4B;AAAA,IAC5D;AAEA,QACA;AACI,UAAI,SAAS,MAAM,KAAK,KAAK,KAAK;AAClC,aAAO;AAAA,IACX,SACM,GACN;AACI,YAAM,IAAI,MAAM,iBAAiB,IAAI,UAAU,GAAG,OAAO,EAAE;AAAA,IAC/D;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/entities/toolDispatcher.ts"],"sourcesContent":["export { ToolDispatcher } from './entities/toolDispatcher';\nexport { McpConfig } from './interfaces/mcpConfig';\nexport { McpServer } from './interfaces/mcpServer';\nexport { McpToolImport } from './interfaces/mcpToolImport';","import { Tool, McpServer, ToolInputGuardrail, ToolInputGuardrailFactory, ToolOutputGuardrail, ToolOutputGuardrailFactory, ToolOutputGuardrailResult, DefaultOutputGuardrail, ToolAuthorizationOptions, ToolAuthorizationResult, PendingToolCall } from \"@mappies/miniwat-agent-core\";\nimport { randomUUID } from \"crypto\";\nimport { McpConfig } from \"../interfaces/mcpConfig\";\n\nexport class ToolDispatcher\n{\n private tools:Tool[]\n private servers:McpServer[]\n\n constructor(private mcp?:McpConfig)\n {\n this.tools = []\n this.servers = []\n }\n\n async init()\n {\n if(this.tools.length !== 0) return;\n\n this.tools = [];\n\n if(this.mcp === undefined) return;\n\n for(let server of Object.keys(this.mcp.mcpServers))\n {\n let service = this.mcp.mcpServers[server]\n\n this.servers.push(new McpServer({name: server, ...service}))\n \n const pck = await import(service.package);\n for(let t of service.tools)\n {\n // Host intent: a tool explicitly disabled in config is excluded here, during\n // discovery, so the planner never sees it.\n if(t.enabled === false) continue;\n\n let definition = pck[t.import];\n let tool = new Tool({ \n name: definition.name, \n displayName: definition.displayName,\n description: definition.description, \n schema: definition.inputSchema, \n func: pck[definition.name],\n service: server,\n getInputText: pck[`${definition.name}InputText`],\n readOnly: definition.readOnly === true,\n requiresApproval: definition.requiresApproval === true,\n inputGuardrail: this.resolveInputGuardrail(server, definition, pck),\n outputGuardrail: this.resolveOutputGuardrail(server, definition, pck)\n })\n \n this.tools.push(tool)\n }\n }\n }\n\n // Resolves a tool definition's named inputGuardrail from the OWNING package's INPUT_GUARDRAILS\n // factory export. Validation is loud and happens at init — never deferred to tool invocation —\n // so the app cannot reach agent.stream() with a partially resolved tool.\n private resolveInputGuardrail(server:string, definition:any, pck:any): ToolInputGuardrail | undefined\n {\n const name:string | undefined = definition.inputGuardrail;\n\n if(name === undefined) return undefined;\n\n const factories = pck.INPUT_GUARDRAILS;\n\n if(factories === undefined || typeof factories !== \"object\")\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' but its package exports no INPUT_GUARDRAILS.`);\n }\n\n const factory:ToolInputGuardrailFactory | undefined = factories[name];\n\n if(factory === undefined)\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' not found in the package's INPUT_GUARDRAILS.`);\n }\n\n if(typeof factory !== \"function\")\n {\n throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);\n }\n\n const guardrail = factory();\n\n if(guardrail === undefined || typeof guardrail.evaluate !== \"function\")\n {\n throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolInputGuardrail.`);\n }\n\n return guardrail;\n }\n\n // Resolves a tool definition's named outputGuardrail from the OWNING package's OUTPUT_GUARDRAILS\n // factory export. Validated loudly at init, mirroring resolveInputGuardrail.\n private resolveOutputGuardrail(server:string, definition:any, pck:any): ToolOutputGuardrail | undefined\n {\n const name:string | undefined = definition.outputGuardrail;\n\n if(name === undefined) return undefined;\n\n const factories = pck.OUTPUT_GUARDRAILS;\n\n if(factories === undefined || typeof factories !== \"object\")\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' but its package exports no OUTPUT_GUARDRAILS.`);\n }\n\n const factory:ToolOutputGuardrailFactory | undefined = factories[name];\n\n if(factory === undefined)\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' not found in the package's OUTPUT_GUARDRAILS.`);\n }\n\n if(typeof factory !== \"function\")\n {\n throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);\n }\n\n const guardrail = factory();\n\n if(guardrail === undefined || typeof guardrail.evaluate !== \"function\")\n {\n throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolOutputGuardrail.`);\n }\n\n return guardrail;\n }\n\n async list(): Promise<Tool[]>\n {\n await this.init()\n return this.tools;\n }\n\n async listServers(): Promise<McpServer[]>\n {\n await this.init()\n return this.servers;\n }\n\n async get(name:string): Promise<Tool|undefined>\n {\n await this.init()\n return this.tools.find(tool => tool.name === name);\n }\n\n async getInputText(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n\n return tool?.getInputText?.(input) || '';\n }\n\n // Evaluates the dispatch-boundary input guardrail against the concrete invocation. Returns\n // undefined when execution may proceed; a ToolAuthorizationResult (defer/deny) when it is\n // blocked. On defer the pending call is recorded via the awaited onDeferred sink BEFORE the\n // deferred result is returned (persist-before-respond); if a tool can defer but no sink is\n // supplied, it FAILS CLOSED (denied) — a deferral must never be reported as unrecoverable.\n private async authorize(name:string, input:object | undefined, tool:Tool, auth?:ToolAuthorizationOptions): Promise<ToolAuthorizationResult | undefined>\n {\n if(!auth)\n {\n return undefined;\n }\n\n const decision = auth.guardrail.evaluate({ toolName: name, arguments: input }, auth.context);\n\n if(decision.status === \"allow\")\n {\n return undefined;\n }\n\n if(decision.status === \"deny\")\n {\n return { authorization: \"deny\", succeeded: false, text: decision.reason };\n }\n\n // defer: preserve the invocation for a later approval turn.\n const pending:PendingToolCall = { id: randomUUID(), name, arguments: (input ?? {}) as Record<string, unknown>, status: \"pending\" };\n\n if(!auth.onDeferred)\n {\n // Fail closed: a deferral we cannot durably record must not surface as a usable\n // approval-required result.\n return { authorization: \"deny\", succeeded: false, text: \"Authorization is unavailable for this operation.\" };\n }\n\n // Persist-before-respond: the pending call is durably recorded before the deferred result\n // (and the approval request derived from it) can reach the user. A rejection propagates.\n await auth.onDeferred(pending);\n\n return { authorization: \"defer\", succeeded: false, text: decision.reason, pending };\n }\n\n async execute(name:string, input?:object, auth?:ToolAuthorizationOptions): Promise<ToolOutputGuardrailResult | ToolAuthorizationResult>\n {\n const tool = await this.get(name);\n \n if(!tool)\n {\n throw new Error(`Tool ${name} could not be found.`);\n }\n\n if(!tool.func)\n {\n throw new Error(`Tool ${name} does not have a function.`);\n }\n\n // Dispatch-boundary authorization runs BEFORE the tool, so an unauthorized side effect never\n // executes — regardless of which planner (or no planner) proposed it. The guardrail is the\n // existing allow/defer/deny input guardrail evaluated against the CONCRETE invocation.\n const authorization = await this.authorize(name, input, tool, auth);\n\n if(authorization)\n {\n return authorization;\n }\n\n let result;\n\n try\n {\n result = await tool.func(input);\n }\n catch(e:any)\n {\n throw new Error(`Failed to run ${name} tool: ${e?.message}`);\n }\n\n // The output guardrail is the SINGLE post-execution authority. It runs here, at the\n // provider-agnostic chokepoint, so the real result is classified BEFORE any provider relays\n // it. Its verdict carries both the user-facing text and the semantic status to the caller;\n // a `blocked` verdict withholds the real result in favor of a replacement. A tool without an\n // explicit guardrail gets the framework default (a normal return is a success).\n const guardrail = tool.outputGuardrail ?? new DefaultOutputGuardrail();\n\n return guardrail.evaluate(result);\n }\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,gCAAuP;AACvP,oBAA2B;AAGpB,IAAM,iBAAN,MACP;AAAA,EAII,YAAoB,KACpB;AADoB;AAEhB,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU,CAAC;AAAA,EACpB;AAAA,EAEA,MAAM,OACN;AACI,QAAG,KAAK,MAAM,WAAW,EAAG;AAE5B,SAAK,QAAQ,CAAC;AAEd,QAAG,KAAK,QAAQ,OAAW;AAE3B,aAAQ,UAAU,OAAO,KAAK,KAAK,IAAI,UAAU,GACjD;AACI,UAAI,UAAU,KAAK,IAAI,WAAW,MAAM;AAExC,WAAK,QAAQ,KAAK,IAAI,oCAAU,EAAC,MAAM,QAAQ,GAAG,QAAO,CAAC,CAAC;AAE3D,YAAM,MAAM,MAAM,OAAO,QAAQ;AACjC,eAAQ,KAAK,QAAQ,OACrB;AAGI,YAAG,EAAE,YAAY,MAAO;AAExB,YAAI,aAAa,IAAI,EAAE,MAAM;AAC7B,YAAI,OAAO,IAAI,+BAAK;AAAA,UACI,MAAM,WAAW;AAAA,UACjB,aAAa,WAAW;AAAA,UACxB,aAAa,WAAW;AAAA,UACxB,QAAQ,WAAW;AAAA,UACnB,MAAM,IAAI,WAAW,IAAI;AAAA,UACzB,SAAS;AAAA,UACT,cAAc,IAAI,GAAG,WAAW,IAAI,WAAW;AAAA,UAC/C,UAAU,WAAW,aAAa;AAAA,UAClC,kBAAkB,WAAW,qBAAqB;AAAA,UAClD,gBAAgB,KAAK,sBAAsB,QAAQ,YAAY,GAAG;AAAA,UAClE,iBAAiB,KAAK,uBAAuB,QAAQ,YAAY,GAAG;AAAA,QACxE,CAAC;AAErB,aAAK,MAAM,KAAK,IAAI;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,QAAe,YAAgB,KAC7D;AACI,UAAM,OAA0B,WAAW;AAE3C,QAAG,SAAS,OAAW,QAAO;AAE9B,UAAM,YAAY,IAAI;AAEtB,QAAG,cAAc,UAAa,OAAO,cAAc,UACnD;AACI,YAAM,IAAI,MAAM,SAAS,WAAW,IAAI,gBAAgB,MAAM,2BAA2B,IAAI,gDAAgD;AAAA,IACjJ;AAEA,UAAM,UAAgD,UAAU,IAAI;AAEpE,QAAG,YAAY,QACf;AACI,YAAM,IAAI,MAAM,SAAS,WAAW,IAAI,gBAAgB,MAAM,2BAA2B,IAAI,gDAAgD;AAAA,IACjJ;AAEA,QAAG,OAAO,YAAY,YACtB;AACI,YAAM,IAAI,MAAM,qBAAqB,IAAI,iBAAiB,MAAM,8BAA8B;AAAA,IAClG;AAEA,UAAM,YAAY,QAAQ;AAE1B,QAAG,cAAc,UAAa,OAAO,UAAU,aAAa,YAC5D;AACI,YAAM,IAAI,MAAM,qBAAqB,IAAI,iBAAiB,MAAM,+CAA+C;AAAA,IACnH;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA,EAIQ,uBAAuB,QAAe,YAAgB,KAC9D;AACI,UAAM,OAA0B,WAAW;AAE3C,QAAG,SAAS,OAAW,QAAO;AAE9B,UAAM,YAAY,IAAI;AAEtB,QAAG,cAAc,UAAa,OAAO,cAAc,UACnD;AACI,YAAM,IAAI,MAAM,SAAS,WAAW,IAAI,gBAAgB,MAAM,4BAA4B,IAAI,iDAAiD;AAAA,IACnJ;AAEA,UAAM,UAAiD,UAAU,IAAI;AAErE,QAAG,YAAY,QACf;AACI,YAAM,IAAI,MAAM,SAAS,WAAW,IAAI,gBAAgB,MAAM,4BAA4B,IAAI,iDAAiD;AAAA,IACnJ;AAEA,QAAG,OAAO,YAAY,YACtB;AACI,YAAM,IAAI,MAAM,sBAAsB,IAAI,iBAAiB,MAAM,8BAA8B;AAAA,IACnG;AAEA,UAAM,YAAY,QAAQ;AAE1B,QAAG,cAAc,UAAa,OAAO,UAAU,aAAa,YAC5D;AACI,YAAM,IAAI,MAAM,sBAAsB,IAAI,iBAAiB,MAAM,gDAAgD;AAAA,IACrH;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,OACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,cACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,IAAI,MACV;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK,MAAM,KAAK,UAAQ,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,MAAM,aAAa,MAAa,OAChC;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,WAAO,MAAM,eAAe,KAAK,KAAK;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,UAAU,MAAa,OAA0B,MAAW,MAC1E;AACI,QAAG,CAAC,MACJ;AACI,aAAO;AAAA,IACX;AAEA,UAAM,WAAW,KAAK,UAAU,SAAS,EAAE,UAAU,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE3F,QAAG,SAAS,WAAW,SACvB;AACI,aAAO;AAAA,IACX;AAEA,QAAG,SAAS,WAAW,QACvB;AACI,aAAO,EAAE,eAAe,QAAQ,WAAW,OAAO,MAAM,SAAS,OAAO;AAAA,IAC5E;AAGA,UAAM,UAA0B,EAAE,QAAI,0BAAW,GAAG,MAAM,WAAY,SAAS,CAAC,GAA+B,QAAQ,UAAU;AAEjI,QAAG,CAAC,KAAK,YACT;AAGI,aAAO,EAAE,eAAe,QAAQ,WAAW,OAAO,MAAM,mDAAmD;AAAA,IAC/G;AAIA,UAAM,KAAK,WAAW,OAAO;AAE7B,WAAO,EAAE,eAAe,SAAS,WAAW,OAAO,MAAM,SAAS,QAAQ,QAAQ;AAAA,EACtF;AAAA,EAEA,MAAM,QAAQ,MAAa,OAAe,MAC1C;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,QAAG,CAAC,MACJ;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,sBAAsB;AAAA,IACtD;AAEA,QAAG,CAAC,KAAK,MACT;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,4BAA4B;AAAA,IAC5D;AAKA,UAAM,gBAAgB,MAAM,KAAK,UAAU,MAAM,OAAO,MAAM,IAAI;AAElE,QAAG,eACH;AACI,aAAO;AAAA,IACX;AAEA,QAAI;AAEJ,QACA;AACI,eAAS,MAAM,KAAK,KAAK,KAAK;AAAA,IAClC,SACM,GACN;AACI,YAAM,IAAI,MAAM,iBAAiB,IAAI,UAAU,GAAG,OAAO,EAAE;AAAA,IAC/D;AAOA,UAAM,YAAY,KAAK,mBAAmB,IAAI,iDAAuB;AAErE,WAAO,UAAU,SAAS,MAAM;AAAA,EACpC;AACJ;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,16 +1,19 @@
1
- import { Tool, McpServer as McpServer$1 } from '@mappies/miniwat-agent-core';
1
+ import { Tool, McpServer as McpServer$1, ToolAuthorizationOptions, ToolOutputGuardrailResult, ToolAuthorizationResult } from '@mappies/miniwat-agent-core';
2
2
 
3
- interface McpConfig {
4
- mcpServers: Record<string, McpServer>;
3
+ interface McpToolImport {
4
+ import: string;
5
+ enabled?: boolean;
5
6
  }
7
+
6
8
  interface McpServer {
7
9
  package: string;
8
10
  description?: string;
9
11
  secrets?: string[];
10
12
  tools: McpToolImport[];
11
13
  }
12
- interface McpToolImport {
13
- import: string;
14
+
15
+ interface McpConfig {
16
+ mcpServers: Record<string, McpServer>;
14
17
  }
15
18
 
16
19
  declare class ToolDispatcher {
@@ -19,11 +22,14 @@ declare class ToolDispatcher {
19
22
  private servers;
20
23
  constructor(mcp?: McpConfig | undefined);
21
24
  init(): Promise<void>;
25
+ private resolveInputGuardrail;
26
+ private resolveOutputGuardrail;
22
27
  list(): Promise<Tool[]>;
23
28
  listServers(): Promise<McpServer$1[]>;
24
29
  get(name: string): Promise<Tool | undefined>;
25
30
  getInputText(name: string, input?: object): Promise<string>;
26
- execute(name: string, input?: object): Promise<string>;
31
+ private authorize;
32
+ execute(name: string, input?: object, auth?: ToolAuthorizationOptions): Promise<ToolOutputGuardrailResult | ToolAuthorizationResult>;
27
33
  }
28
34
 
29
35
  export { type McpConfig, type McpServer, type McpToolImport, ToolDispatcher };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,19 @@
1
- import { Tool, McpServer as McpServer$1 } from '@mappies/miniwat-agent-core';
1
+ import { Tool, McpServer as McpServer$1, ToolAuthorizationOptions, ToolOutputGuardrailResult, ToolAuthorizationResult } from '@mappies/miniwat-agent-core';
2
2
 
3
- interface McpConfig {
4
- mcpServers: Record<string, McpServer>;
3
+ interface McpToolImport {
4
+ import: string;
5
+ enabled?: boolean;
5
6
  }
7
+
6
8
  interface McpServer {
7
9
  package: string;
8
10
  description?: string;
9
11
  secrets?: string[];
10
12
  tools: McpToolImport[];
11
13
  }
12
- interface McpToolImport {
13
- import: string;
14
+
15
+ interface McpConfig {
16
+ mcpServers: Record<string, McpServer>;
14
17
  }
15
18
 
16
19
  declare class ToolDispatcher {
@@ -19,11 +22,14 @@ declare class ToolDispatcher {
19
22
  private servers;
20
23
  constructor(mcp?: McpConfig | undefined);
21
24
  init(): Promise<void>;
25
+ private resolveInputGuardrail;
26
+ private resolveOutputGuardrail;
22
27
  list(): Promise<Tool[]>;
23
28
  listServers(): Promise<McpServer$1[]>;
24
29
  get(name: string): Promise<Tool | undefined>;
25
30
  getInputText(name: string, input?: object): Promise<string>;
26
- execute(name: string, input?: object): Promise<string>;
31
+ private authorize;
32
+ execute(name: string, input?: object, auth?: ToolAuthorizationOptions): Promise<ToolOutputGuardrailResult | ToolAuthorizationResult>;
27
33
  }
28
34
 
29
35
  export { type McpConfig, type McpServer, type McpToolImport, ToolDispatcher };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/entities/toolDispatcher.ts
2
- import { Tool, McpServer } from "@mappies/miniwat-agent-core";
2
+ import { Tool, McpServer, DefaultOutputGuardrail } from "@mappies/miniwat-agent-core";
3
+ import { randomUUID } from "crypto";
3
4
  var ToolDispatcher = class {
4
5
  constructor(mcp) {
5
6
  this.mcp = mcp;
@@ -15,19 +16,70 @@ var ToolDispatcher = class {
15
16
  this.servers.push(new McpServer({ name: server, ...service }));
16
17
  const pck = await import(service.package);
17
18
  for (let t of service.tools) {
19
+ if (t.enabled === false) continue;
18
20
  let definition = pck[t.import];
19
21
  let tool = new Tool({
20
22
  name: definition.name,
23
+ displayName: definition.displayName,
21
24
  description: definition.description,
22
25
  schema: definition.inputSchema,
23
26
  func: pck[definition.name],
24
27
  service: server,
25
- getInputText: pck[`${definition.name}InputText`]
28
+ getInputText: pck[`${definition.name}InputText`],
29
+ readOnly: definition.readOnly === true,
30
+ requiresApproval: definition.requiresApproval === true,
31
+ inputGuardrail: this.resolveInputGuardrail(server, definition, pck),
32
+ outputGuardrail: this.resolveOutputGuardrail(server, definition, pck)
26
33
  });
27
34
  this.tools.push(tool);
28
35
  }
29
36
  }
30
37
  }
38
+ // Resolves a tool definition's named inputGuardrail from the OWNING package's INPUT_GUARDRAILS
39
+ // factory export. Validation is loud and happens at init — never deferred to tool invocation —
40
+ // so the app cannot reach agent.stream() with a partially resolved tool.
41
+ resolveInputGuardrail(server, definition, pck) {
42
+ const name = definition.inputGuardrail;
43
+ if (name === void 0) return void 0;
44
+ const factories = pck.INPUT_GUARDRAILS;
45
+ if (factories === void 0 || typeof factories !== "object") {
46
+ throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' but its package exports no INPUT_GUARDRAILS.`);
47
+ }
48
+ const factory = factories[name];
49
+ if (factory === void 0) {
50
+ throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' not found in the package's INPUT_GUARDRAILS.`);
51
+ }
52
+ if (typeof factory !== "function") {
53
+ throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);
54
+ }
55
+ const guardrail = factory();
56
+ if (guardrail === void 0 || typeof guardrail.evaluate !== "function") {
57
+ throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolInputGuardrail.`);
58
+ }
59
+ return guardrail;
60
+ }
61
+ // Resolves a tool definition's named outputGuardrail from the OWNING package's OUTPUT_GUARDRAILS
62
+ // factory export. Validated loudly at init, mirroring resolveInputGuardrail.
63
+ resolveOutputGuardrail(server, definition, pck) {
64
+ const name = definition.outputGuardrail;
65
+ if (name === void 0) return void 0;
66
+ const factories = pck.OUTPUT_GUARDRAILS;
67
+ if (factories === void 0 || typeof factories !== "object") {
68
+ throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' but its package exports no OUTPUT_GUARDRAILS.`);
69
+ }
70
+ const factory = factories[name];
71
+ if (factory === void 0) {
72
+ throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' not found in the package's OUTPUT_GUARDRAILS.`);
73
+ }
74
+ if (typeof factory !== "function") {
75
+ throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);
76
+ }
77
+ const guardrail = factory();
78
+ if (guardrail === void 0 || typeof guardrail.evaluate !== "function") {
79
+ throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolOutputGuardrail.`);
80
+ }
81
+ return guardrail;
82
+ }
31
83
  async list() {
32
84
  await this.init();
33
85
  return this.tools;
@@ -44,7 +96,30 @@ var ToolDispatcher = class {
44
96
  const tool = await this.get(name);
45
97
  return tool?.getInputText?.(input) || "";
46
98
  }
47
- async execute(name, input) {
99
+ // Evaluates the dispatch-boundary input guardrail against the concrete invocation. Returns
100
+ // undefined when execution may proceed; a ToolAuthorizationResult (defer/deny) when it is
101
+ // blocked. On defer the pending call is recorded via the awaited onDeferred sink BEFORE the
102
+ // deferred result is returned (persist-before-respond); if a tool can defer but no sink is
103
+ // supplied, it FAILS CLOSED (denied) — a deferral must never be reported as unrecoverable.
104
+ async authorize(name, input, tool, auth) {
105
+ if (!auth) {
106
+ return void 0;
107
+ }
108
+ const decision = auth.guardrail.evaluate({ toolName: name, arguments: input }, auth.context);
109
+ if (decision.status === "allow") {
110
+ return void 0;
111
+ }
112
+ if (decision.status === "deny") {
113
+ return { authorization: "deny", succeeded: false, text: decision.reason };
114
+ }
115
+ const pending = { id: randomUUID(), name, arguments: input ?? {}, status: "pending" };
116
+ if (!auth.onDeferred) {
117
+ return { authorization: "deny", succeeded: false, text: "Authorization is unavailable for this operation." };
118
+ }
119
+ await auth.onDeferred(pending);
120
+ return { authorization: "defer", succeeded: false, text: decision.reason, pending };
121
+ }
122
+ async execute(name, input, auth) {
48
123
  const tool = await this.get(name);
49
124
  if (!tool) {
50
125
  throw new Error(`Tool ${name} could not be found.`);
@@ -52,12 +127,18 @@ var ToolDispatcher = class {
52
127
  if (!tool.func) {
53
128
  throw new Error(`Tool ${name} does not have a function.`);
54
129
  }
130
+ const authorization = await this.authorize(name, input, tool, auth);
131
+ if (authorization) {
132
+ return authorization;
133
+ }
134
+ let result;
55
135
  try {
56
- let result = await tool.func(input);
57
- return result;
136
+ result = await tool.func(input);
58
137
  } catch (e) {
59
138
  throw new Error(`Failed to run ${name} tool: ${e?.message}`);
60
139
  }
140
+ const guardrail = tool.outputGuardrail ?? new DefaultOutputGuardrail();
141
+ return guardrail.evaluate(result);
61
142
  }
62
143
  };
63
144
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/entities/toolDispatcher.ts"],"sourcesContent":["import { Tool, McpServer } from \"@mappies/miniwat-agent-core\";\nimport { McpConfig } from \"../interfaces/mcpConfig\";\n\nexport class ToolDispatcher\n{\n private tools:Tool[]\n private servers:McpServer[]\n\n constructor(private mcp?:McpConfig)\n {\n this.tools = []\n this.servers = []\n }\n\n async init()\n {\n if(this.tools.length !== 0) return;\n\n this.tools = [];\n\n if(this.mcp === undefined) return;\n\n for(let server of Object.keys(this.mcp.mcpServers))\n {\n let service = this.mcp.mcpServers[server]\n\n this.servers.push(new McpServer({name: server, ...service}))\n \n const pck = await import(service.package);\n for(let t of service.tools)\n {\n let definition = pck[t.import];\n let tool = new Tool({ \n name: definition.name, \n description: definition.description, \n schema: definition.inputSchema, \n func: pck[definition.name],\n service: server,\n getInputText: pck[`${definition.name}InputText`]\n })\n \n this.tools.push(tool)\n }\n }\n }\n\n async list(): Promise<Tool[]>\n {\n await this.init()\n return this.tools;\n }\n\n async listServers(): Promise<McpServer[]>\n {\n await this.init()\n return this.servers;\n }\n\n async get(name:string): Promise<Tool|undefined>\n {\n await this.init()\n return this.tools.find(tool => tool.name === name);\n }\n\n async getInputText(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n\n return tool?.getInputText?.(input) || '';\n }\n\n async execute(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n \n if(!tool)\n {\n throw new Error(`Tool ${name} could not be found.`);\n }\n\n if(!tool.func)\n {\n throw new Error(`Tool ${name} does not have a function.`);\n }\n\n try\n {\n let result = await tool.func(input);\n return result;\n }\n catch(e:any)\n {\n throw new Error(`Failed to run ${name} tool: ${e?.message}`);\n }\n }\n}"],"mappings":";AAAA,SAAS,MAAM,iBAAiB;AAGzB,IAAM,iBAAN,MACP;AAAA,EAII,YAAoB,KACpB;AADoB;AAEhB,SAAK,QAAQ,CAAC;AACd,SAAK,UAAU,CAAC;AAAA,EACpB;AAAA,EAEA,MAAM,OACN;AACI,QAAG,KAAK,MAAM,WAAW,EAAG;AAE5B,SAAK,QAAQ,CAAC;AAEd,QAAG,KAAK,QAAQ,OAAW;AAE3B,aAAQ,UAAU,OAAO,KAAK,KAAK,IAAI,UAAU,GACjD;AACI,UAAI,UAAU,KAAK,IAAI,WAAW,MAAM;AAExC,WAAK,QAAQ,KAAK,IAAI,UAAU,EAAC,MAAM,QAAQ,GAAG,QAAO,CAAC,CAAC;AAE3D,YAAM,MAAM,MAAM,OAAO,QAAQ;AACjC,eAAQ,KAAK,QAAQ,OACrB;AACI,YAAI,aAAa,IAAI,EAAE,MAAM;AAC7B,YAAI,OAAO,IAAI,KAAK;AAAA,UACI,MAAM,WAAW;AAAA,UACjB,aAAa,WAAW;AAAA,UACxB,QAAQ,WAAW;AAAA,UACnB,MAAM,IAAI,WAAW,IAAI;AAAA,UACzB,SAAS;AAAA,UACT,cAAc,IAAI,GAAG,WAAW,IAAI,WAAW;AAAA,QACnD,CAAC;AAErB,aAAK,MAAM,KAAK,IAAI;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,OACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,cACN;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,IAAI,MACV;AACI,UAAM,KAAK,KAAK;AAChB,WAAO,KAAK,MAAM,KAAK,UAAQ,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,MAAM,aAAa,MAAa,OAChC;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,WAAO,MAAM,eAAe,KAAK,KAAK;AAAA,EAC1C;AAAA,EAEA,MAAM,QAAQ,MAAa,OAC3B;AACI,UAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAEhC,QAAG,CAAC,MACJ;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,sBAAsB;AAAA,IACtD;AAEA,QAAG,CAAC,KAAK,MACT;AACI,YAAM,IAAI,MAAM,QAAQ,IAAI,4BAA4B;AAAA,IAC5D;AAEA,QACA;AACI,UAAI,SAAS,MAAM,KAAK,KAAK,KAAK;AAClC,aAAO;AAAA,IACX,SACM,GACN;AACI,YAAM,IAAI,MAAM,iBAAiB,IAAI,UAAU,GAAG,OAAO,EAAE;AAAA,IAC/D;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../src/entities/toolDispatcher.ts"],"sourcesContent":["import { Tool, McpServer, ToolInputGuardrail, ToolInputGuardrailFactory, ToolOutputGuardrail, ToolOutputGuardrailFactory, ToolOutputGuardrailResult, DefaultOutputGuardrail, ToolAuthorizationOptions, ToolAuthorizationResult, PendingToolCall } from \"@mappies/miniwat-agent-core\";\nimport { randomUUID } from \"crypto\";\nimport { McpConfig } from \"../interfaces/mcpConfig\";\n\nexport class ToolDispatcher\n{\n private tools:Tool[]\n private servers:McpServer[]\n\n constructor(private mcp?:McpConfig)\n {\n this.tools = []\n this.servers = []\n }\n\n async init()\n {\n if(this.tools.length !== 0) return;\n\n this.tools = [];\n\n if(this.mcp === undefined) return;\n\n for(let server of Object.keys(this.mcp.mcpServers))\n {\n let service = this.mcp.mcpServers[server]\n\n this.servers.push(new McpServer({name: server, ...service}))\n \n const pck = await import(service.package);\n for(let t of service.tools)\n {\n // Host intent: a tool explicitly disabled in config is excluded here, during\n // discovery, so the planner never sees it.\n if(t.enabled === false) continue;\n\n let definition = pck[t.import];\n let tool = new Tool({ \n name: definition.name, \n displayName: definition.displayName,\n description: definition.description, \n schema: definition.inputSchema, \n func: pck[definition.name],\n service: server,\n getInputText: pck[`${definition.name}InputText`],\n readOnly: definition.readOnly === true,\n requiresApproval: definition.requiresApproval === true,\n inputGuardrail: this.resolveInputGuardrail(server, definition, pck),\n outputGuardrail: this.resolveOutputGuardrail(server, definition, pck)\n })\n \n this.tools.push(tool)\n }\n }\n }\n\n // Resolves a tool definition's named inputGuardrail from the OWNING package's INPUT_GUARDRAILS\n // factory export. Validation is loud and happens at init — never deferred to tool invocation —\n // so the app cannot reach agent.stream() with a partially resolved tool.\n private resolveInputGuardrail(server:string, definition:any, pck:any): ToolInputGuardrail | undefined\n {\n const name:string | undefined = definition.inputGuardrail;\n\n if(name === undefined) return undefined;\n\n const factories = pck.INPUT_GUARDRAILS;\n\n if(factories === undefined || typeof factories !== \"object\")\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' but its package exports no INPUT_GUARDRAILS.`);\n }\n\n const factory:ToolInputGuardrailFactory | undefined = factories[name];\n\n if(factory === undefined)\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names inputGuardrail '${name}' not found in the package's INPUT_GUARDRAILS.`);\n }\n\n if(typeof factory !== \"function\")\n {\n throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);\n }\n\n const guardrail = factory();\n\n if(guardrail === undefined || typeof guardrail.evaluate !== \"function\")\n {\n throw new Error(`INPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolInputGuardrail.`);\n }\n\n return guardrail;\n }\n\n // Resolves a tool definition's named outputGuardrail from the OWNING package's OUTPUT_GUARDRAILS\n // factory export. Validated loudly at init, mirroring resolveInputGuardrail.\n private resolveOutputGuardrail(server:string, definition:any, pck:any): ToolOutputGuardrail | undefined\n {\n const name:string | undefined = definition.outputGuardrail;\n\n if(name === undefined) return undefined;\n\n const factories = pck.OUTPUT_GUARDRAILS;\n\n if(factories === undefined || typeof factories !== \"object\")\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' but its package exports no OUTPUT_GUARDRAILS.`);\n }\n\n const factory:ToolOutputGuardrailFactory | undefined = factories[name];\n\n if(factory === undefined)\n {\n throw new Error(`Tool '${definition.name}' on server '${server}' names outputGuardrail '${name}' not found in the package's OUTPUT_GUARDRAILS.`);\n }\n\n if(typeof factory !== \"function\")\n {\n throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' is not a factory function.`);\n }\n\n const guardrail = factory();\n\n if(guardrail === undefined || typeof guardrail.evaluate !== \"function\")\n {\n throw new Error(`OUTPUT_GUARDRAILS['${name}'] on server '${server}' did not produce a valid ToolOutputGuardrail.`);\n }\n\n return guardrail;\n }\n\n async list(): Promise<Tool[]>\n {\n await this.init()\n return this.tools;\n }\n\n async listServers(): Promise<McpServer[]>\n {\n await this.init()\n return this.servers;\n }\n\n async get(name:string): Promise<Tool|undefined>\n {\n await this.init()\n return this.tools.find(tool => tool.name === name);\n }\n\n async getInputText(name:string, input?:object): Promise<string>\n {\n const tool = await this.get(name);\n\n return tool?.getInputText?.(input) || '';\n }\n\n // Evaluates the dispatch-boundary input guardrail against the concrete invocation. Returns\n // undefined when execution may proceed; a ToolAuthorizationResult (defer/deny) when it is\n // blocked. On defer the pending call is recorded via the awaited onDeferred sink BEFORE the\n // deferred result is returned (persist-before-respond); if a tool can defer but no sink is\n // supplied, it FAILS CLOSED (denied) — a deferral must never be reported as unrecoverable.\n private async authorize(name:string, input:object | undefined, tool:Tool, auth?:ToolAuthorizationOptions): Promise<ToolAuthorizationResult | undefined>\n {\n if(!auth)\n {\n return undefined;\n }\n\n const decision = auth.guardrail.evaluate({ toolName: name, arguments: input }, auth.context);\n\n if(decision.status === \"allow\")\n {\n return undefined;\n }\n\n if(decision.status === \"deny\")\n {\n return { authorization: \"deny\", succeeded: false, text: decision.reason };\n }\n\n // defer: preserve the invocation for a later approval turn.\n const pending:PendingToolCall = { id: randomUUID(), name, arguments: (input ?? {}) as Record<string, unknown>, status: \"pending\" };\n\n if(!auth.onDeferred)\n {\n // Fail closed: a deferral we cannot durably record must not surface as a usable\n // approval-required result.\n return { authorization: \"deny\", succeeded: false, text: \"Authorization is unavailable for this operation.\" };\n }\n\n // Persist-before-respond: the pending call is durably recorded before the deferred result\n // (and the approval request derived from it) can reach the user. A rejection propagates.\n await auth.onDeferred(pending);\n\n return { authorization: \"defer\", succeeded: false, text: decision.reason, pending };\n }\n\n async execute(name:string, input?:object, auth?:ToolAuthorizationOptions): Promise<ToolOutputGuardrailResult | ToolAuthorizationResult>\n {\n const tool = await this.get(name);\n \n if(!tool)\n {\n throw new Error(`Tool ${name} could not be found.`);\n }\n\n if(!tool.func)\n {\n throw new Error(`Tool ${name} does not have a function.`);\n }\n\n // Dispatch-boundary authorization runs BEFORE the tool, so an unauthorized side effect never\n // executes — regardless of which planner (or no planner) proposed it. The guardrail is the\n // existing allow/defer/deny input guardrail evaluated against the CONCRETE invocation.\n const authorization = await this.authorize(name, input, tool, auth);\n\n if(authorization)\n {\n return authorization;\n }\n\n let result;\n\n try\n {\n result = await tool.func(input);\n }\n catch(e:any)\n {\n throw new Error(`Failed to run ${name} tool: ${e?.message}`);\n }\n\n // The output guardrail is the SINGLE post-execution authority. It runs here, at the\n // provider-agnostic chokepoint, so the real result is classified BEFORE any provider relays\n // it. Its verdict carries both the user-facing text and the semantic status to the caller;\n // a `blocked` verdict withholds the real result in favor of a replacement. A tool without an\n // explicit guardrail gets the framework default (a normal return is a success).\n const guardrail = tool.outputGuardrail ?? new DefaultOutputGuardrail();\n\n return guardrail.evaluate(result);\n }\n}"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mappies/miniwat-mcp-core",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Shared entities and executor for Miniwat MCP",
5
5
  "homepage": "https://gitlab.com/mappies/miniwat-mcp-core#readme",
6
6
  "bugs": {
@@ -43,6 +43,6 @@
43
43
  "vitest": "^5.0.0"
44
44
  },
45
45
  "dependencies": {
46
- "@mappies/miniwat-agent-core": "^0.18.0"
46
+ "@mappies/miniwat-agent-core": "^0.21.0"
47
47
  }
48
48
  }