@servicialo/mcp-server 0.7.0 → 0.8.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/README.en.md +392 -0
- package/README.md +315 -147
- package/dist/adapter-http.d.ts +51 -0
- package/dist/adapter-http.d.ts.map +1 -0
- package/dist/adapter-http.js +187 -0
- package/dist/adapter-http.js.map +1 -0
- package/dist/adapter.d.ts +42 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +42 -0
- package/dist/adapter.js.map +1 -0
- package/dist/client.d.ts +5 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -59
- package/dist/index.js.map +1 -1
- package/dist/mandate.d.ts +321 -0
- package/dist/mandate.d.ts.map +1 -0
- package/dist/mandate.js +349 -0
- package/dist/mandate.js.map +1 -0
- package/dist/provider-profile.d.ts +985 -0
- package/dist/provider-profile.d.ts.map +1 -0
- package/dist/provider-profile.js +210 -0
- package/dist/provider-profile.js.map +1 -0
- package/dist/tools/authenticated/cerrar.d.ts +5 -5
- package/dist/tools/authenticated/cerrar.d.ts.map +1 -1
- package/dist/tools/authenticated/cerrar.js.map +1 -1
- package/dist/tools/authenticated/comprometer.d.ts +4 -4
- package/dist/tools/authenticated/comprometer.d.ts.map +1 -1
- package/dist/tools/authenticated/comprometer.js.map +1 -1
- package/dist/tools/authenticated/delivery.d.ts +4 -4
- package/dist/tools/authenticated/delivery.d.ts.map +1 -1
- package/dist/tools/authenticated/delivery.js.map +1 -1
- package/dist/tools/authenticated/entender.d.ts +3 -3
- package/dist/tools/authenticated/entender.d.ts.map +1 -1
- package/dist/tools/authenticated/entender.js.map +1 -1
- package/dist/tools/authenticated/lifecycle.d.ts +5 -5
- package/dist/tools/authenticated/lifecycle.d.ts.map +1 -1
- package/dist/tools/authenticated/lifecycle.js.map +1 -1
- package/dist/tools/authenticated/resolve-auth.d.ts +118 -0
- package/dist/tools/authenticated/resolve-auth.d.ts.map +1 -0
- package/dist/tools/authenticated/resolve-auth.js +62 -0
- package/dist/tools/authenticated/resolve-auth.js.map +1 -0
- package/dist/tools/authenticated/resource.d.ts +295 -0
- package/dist/tools/authenticated/resource.d.ts.map +1 -0
- package/dist/tools/authenticated/resource.js +125 -0
- package/dist/tools/authenticated/resource.js.map +1 -0
- package/dist/tools/public/a2a.d.ts +18 -0
- package/dist/tools/public/a2a.d.ts.map +1 -0
- package/dist/tools/public/a2a.js +15 -0
- package/dist/tools/public/a2a.js.map +1 -0
- package/dist/tools/public/availability.d.ts +2 -2
- package/dist/tools/public/availability.d.ts.map +1 -1
- package/dist/tools/public/availability.js.map +1 -1
- package/dist/tools/public/registry.d.ts +8 -3
- package/dist/tools/public/registry.d.ts.map +1 -1
- package/dist/tools/public/registry.js +8 -0
- package/dist/tools/public/registry.js.map +1 -1
- package/dist/tools/public/resolve.d.ts +60 -0
- package/dist/tools/public/resolve.d.ts.map +1 -0
- package/dist/tools/public/resolve.js +60 -0
- package/dist/tools/public/resolve.js.map +1 -0
- package/dist/tools/public/services.d.ts +2 -2
- package/dist/tools/public/services.d.ts.map +1 -1
- package/dist/tools/public/services.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Servicialo v0.8 — Delegated Agency Model
|
|
3
|
+
* Mandate validation middleware for the MCP server.
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* 1. Zod schemas for ServiceMandate and audit entries
|
|
7
|
+
* 2. A validation function that enforces the 8 mandate checks (§10.3.2)
|
|
8
|
+
* 3. A middleware wrapper for tool handlers
|
|
9
|
+
* 4. Audit logging on every agent action
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { ServicialoAdapter } from './adapter.js';
|
|
13
|
+
export declare const MandatePrincipalType: z.ZodEnum<["professional", "patient", "organization"]>;
|
|
14
|
+
export declare const MandateActingFor: z.ZodEnum<["professional", "patient", "organization"]>;
|
|
15
|
+
export declare const MandateStatus: z.ZodEnum<["active", "expired", "revoked", "suspended"]>;
|
|
16
|
+
export declare const MandateConstraintsSchema: z.ZodOptional<z.ZodObject<{
|
|
17
|
+
max_actions_per_day: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
allowed_hours: z.ZodOptional<z.ZodObject<{
|
|
19
|
+
start: z.ZodString;
|
|
20
|
+
end: z.ZodString;
|
|
21
|
+
timezone: z.ZodString;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
timezone: string;
|
|
24
|
+
start: string;
|
|
25
|
+
end: string;
|
|
26
|
+
}, {
|
|
27
|
+
timezone: string;
|
|
28
|
+
start: string;
|
|
29
|
+
end: string;
|
|
30
|
+
}>>;
|
|
31
|
+
ip_allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
32
|
+
require_confirmation_above: z.ZodOptional<z.ZodObject<{
|
|
33
|
+
amount: z.ZodNumber;
|
|
34
|
+
currency: z.ZodString;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
amount: number;
|
|
37
|
+
currency: string;
|
|
38
|
+
}, {
|
|
39
|
+
amount: number;
|
|
40
|
+
currency: string;
|
|
41
|
+
}>>;
|
|
42
|
+
service_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
max_actions_per_day?: number | undefined;
|
|
45
|
+
allowed_hours?: {
|
|
46
|
+
timezone: string;
|
|
47
|
+
start: string;
|
|
48
|
+
end: string;
|
|
49
|
+
} | undefined;
|
|
50
|
+
ip_allowlist?: string[] | undefined;
|
|
51
|
+
require_confirmation_above?: {
|
|
52
|
+
amount: number;
|
|
53
|
+
currency: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
service_types?: string[] | undefined;
|
|
56
|
+
}, {
|
|
57
|
+
max_actions_per_day?: number | undefined;
|
|
58
|
+
allowed_hours?: {
|
|
59
|
+
timezone: string;
|
|
60
|
+
start: string;
|
|
61
|
+
end: string;
|
|
62
|
+
} | undefined;
|
|
63
|
+
ip_allowlist?: string[] | undefined;
|
|
64
|
+
require_confirmation_above?: {
|
|
65
|
+
amount: number;
|
|
66
|
+
currency: string;
|
|
67
|
+
} | undefined;
|
|
68
|
+
service_types?: string[] | undefined;
|
|
69
|
+
}>>;
|
|
70
|
+
export declare const ServiceMandateSchema: z.ZodObject<{
|
|
71
|
+
mandate_id: z.ZodString;
|
|
72
|
+
principal_id: z.ZodString;
|
|
73
|
+
principal_type: z.ZodEnum<["professional", "patient", "organization"]>;
|
|
74
|
+
agent_id: z.ZodString;
|
|
75
|
+
agent_name: z.ZodOptional<z.ZodString>;
|
|
76
|
+
acting_for: z.ZodEnum<["professional", "patient", "organization"]>;
|
|
77
|
+
context: z.ZodString;
|
|
78
|
+
scopes: z.ZodArray<z.ZodString, "many">;
|
|
79
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
80
|
+
max_actions_per_day: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
allowed_hours: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
start: z.ZodString;
|
|
83
|
+
end: z.ZodString;
|
|
84
|
+
timezone: z.ZodString;
|
|
85
|
+
}, "strip", z.ZodTypeAny, {
|
|
86
|
+
timezone: string;
|
|
87
|
+
start: string;
|
|
88
|
+
end: string;
|
|
89
|
+
}, {
|
|
90
|
+
timezone: string;
|
|
91
|
+
start: string;
|
|
92
|
+
end: string;
|
|
93
|
+
}>>;
|
|
94
|
+
ip_allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
95
|
+
require_confirmation_above: z.ZodOptional<z.ZodObject<{
|
|
96
|
+
amount: z.ZodNumber;
|
|
97
|
+
currency: z.ZodString;
|
|
98
|
+
}, "strip", z.ZodTypeAny, {
|
|
99
|
+
amount: number;
|
|
100
|
+
currency: string;
|
|
101
|
+
}, {
|
|
102
|
+
amount: number;
|
|
103
|
+
currency: string;
|
|
104
|
+
}>>;
|
|
105
|
+
service_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
106
|
+
}, "strip", z.ZodTypeAny, {
|
|
107
|
+
max_actions_per_day?: number | undefined;
|
|
108
|
+
allowed_hours?: {
|
|
109
|
+
timezone: string;
|
|
110
|
+
start: string;
|
|
111
|
+
end: string;
|
|
112
|
+
} | undefined;
|
|
113
|
+
ip_allowlist?: string[] | undefined;
|
|
114
|
+
require_confirmation_above?: {
|
|
115
|
+
amount: number;
|
|
116
|
+
currency: string;
|
|
117
|
+
} | undefined;
|
|
118
|
+
service_types?: string[] | undefined;
|
|
119
|
+
}, {
|
|
120
|
+
max_actions_per_day?: number | undefined;
|
|
121
|
+
allowed_hours?: {
|
|
122
|
+
timezone: string;
|
|
123
|
+
start: string;
|
|
124
|
+
end: string;
|
|
125
|
+
} | undefined;
|
|
126
|
+
ip_allowlist?: string[] | undefined;
|
|
127
|
+
require_confirmation_above?: {
|
|
128
|
+
amount: number;
|
|
129
|
+
currency: string;
|
|
130
|
+
} | undefined;
|
|
131
|
+
service_types?: string[] | undefined;
|
|
132
|
+
}>>;
|
|
133
|
+
issued_at: z.ZodString;
|
|
134
|
+
expires_at: z.ZodString;
|
|
135
|
+
revoked_at: z.ZodOptional<z.ZodString>;
|
|
136
|
+
revocation_reason: z.ZodOptional<z.ZodString>;
|
|
137
|
+
status: z.ZodEnum<["active", "expired", "revoked", "suspended"]>;
|
|
138
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
139
|
+
}, "strip", z.ZodTypeAny, {
|
|
140
|
+
status: "active" | "expired" | "revoked" | "suspended";
|
|
141
|
+
mandate_id: string;
|
|
142
|
+
principal_id: string;
|
|
143
|
+
principal_type: "organization" | "professional" | "patient";
|
|
144
|
+
agent_id: string;
|
|
145
|
+
acting_for: "organization" | "professional" | "patient";
|
|
146
|
+
context: string;
|
|
147
|
+
scopes: string[];
|
|
148
|
+
issued_at: string;
|
|
149
|
+
expires_at: string;
|
|
150
|
+
agent_name?: string | undefined;
|
|
151
|
+
constraints?: {
|
|
152
|
+
max_actions_per_day?: number | undefined;
|
|
153
|
+
allowed_hours?: {
|
|
154
|
+
timezone: string;
|
|
155
|
+
start: string;
|
|
156
|
+
end: string;
|
|
157
|
+
} | undefined;
|
|
158
|
+
ip_allowlist?: string[] | undefined;
|
|
159
|
+
require_confirmation_above?: {
|
|
160
|
+
amount: number;
|
|
161
|
+
currency: string;
|
|
162
|
+
} | undefined;
|
|
163
|
+
service_types?: string[] | undefined;
|
|
164
|
+
} | undefined;
|
|
165
|
+
revoked_at?: string | undefined;
|
|
166
|
+
revocation_reason?: string | undefined;
|
|
167
|
+
metadata?: Record<string, unknown> | undefined;
|
|
168
|
+
}, {
|
|
169
|
+
status: "active" | "expired" | "revoked" | "suspended";
|
|
170
|
+
mandate_id: string;
|
|
171
|
+
principal_id: string;
|
|
172
|
+
principal_type: "organization" | "professional" | "patient";
|
|
173
|
+
agent_id: string;
|
|
174
|
+
acting_for: "organization" | "professional" | "patient";
|
|
175
|
+
context: string;
|
|
176
|
+
scopes: string[];
|
|
177
|
+
issued_at: string;
|
|
178
|
+
expires_at: string;
|
|
179
|
+
agent_name?: string | undefined;
|
|
180
|
+
constraints?: {
|
|
181
|
+
max_actions_per_day?: number | undefined;
|
|
182
|
+
allowed_hours?: {
|
|
183
|
+
timezone: string;
|
|
184
|
+
start: string;
|
|
185
|
+
end: string;
|
|
186
|
+
} | undefined;
|
|
187
|
+
ip_allowlist?: string[] | undefined;
|
|
188
|
+
require_confirmation_above?: {
|
|
189
|
+
amount: number;
|
|
190
|
+
currency: string;
|
|
191
|
+
} | undefined;
|
|
192
|
+
service_types?: string[] | undefined;
|
|
193
|
+
} | undefined;
|
|
194
|
+
revoked_at?: string | undefined;
|
|
195
|
+
revocation_reason?: string | undefined;
|
|
196
|
+
metadata?: Record<string, unknown> | undefined;
|
|
197
|
+
}>;
|
|
198
|
+
export type ServiceMandate = z.infer<typeof ServiceMandateSchema>;
|
|
199
|
+
export declare const AuditResultType: z.ZodEnum<["success", "failure", "rejected", "halted"]>;
|
|
200
|
+
export declare const AuditEntrySchema: z.ZodObject<{
|
|
201
|
+
audit_id: z.ZodOptional<z.ZodString>;
|
|
202
|
+
mandate_id: z.ZodString;
|
|
203
|
+
agent_id: z.ZodString;
|
|
204
|
+
principal_id: z.ZodString;
|
|
205
|
+
acting_for: z.ZodEnum<["professional", "patient", "organization"]>;
|
|
206
|
+
action: z.ZodString;
|
|
207
|
+
action_input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
208
|
+
action_result: z.ZodEnum<["success", "failure", "rejected", "halted"]>;
|
|
209
|
+
failure_reason: z.ZodOptional<z.ZodString>;
|
|
210
|
+
resource_id: z.ZodOptional<z.ZodString>;
|
|
211
|
+
context: z.ZodString;
|
|
212
|
+
ip_address: z.ZodOptional<z.ZodString>;
|
|
213
|
+
request_id: z.ZodOptional<z.ZodString>;
|
|
214
|
+
timestamp: z.ZodOptional<z.ZodString>;
|
|
215
|
+
}, "strip", z.ZodTypeAny, {
|
|
216
|
+
mandate_id: string;
|
|
217
|
+
principal_id: string;
|
|
218
|
+
agent_id: string;
|
|
219
|
+
acting_for: "organization" | "professional" | "patient";
|
|
220
|
+
context: string;
|
|
221
|
+
action: string;
|
|
222
|
+
action_result: "success" | "failure" | "rejected" | "halted";
|
|
223
|
+
resource_id?: string | undefined;
|
|
224
|
+
timestamp?: string | undefined;
|
|
225
|
+
audit_id?: string | undefined;
|
|
226
|
+
action_input?: Record<string, unknown> | undefined;
|
|
227
|
+
failure_reason?: string | undefined;
|
|
228
|
+
ip_address?: string | undefined;
|
|
229
|
+
request_id?: string | undefined;
|
|
230
|
+
}, {
|
|
231
|
+
mandate_id: string;
|
|
232
|
+
principal_id: string;
|
|
233
|
+
agent_id: string;
|
|
234
|
+
acting_for: "organization" | "professional" | "patient";
|
|
235
|
+
context: string;
|
|
236
|
+
action: string;
|
|
237
|
+
action_result: "success" | "failure" | "rejected" | "halted";
|
|
238
|
+
resource_id?: string | undefined;
|
|
239
|
+
timestamp?: string | undefined;
|
|
240
|
+
audit_id?: string | undefined;
|
|
241
|
+
action_input?: Record<string, unknown> | undefined;
|
|
242
|
+
failure_reason?: string | undefined;
|
|
243
|
+
ip_address?: string | undefined;
|
|
244
|
+
request_id?: string | undefined;
|
|
245
|
+
}>;
|
|
246
|
+
export type AuditEntry = z.infer<typeof AuditEntrySchema>;
|
|
247
|
+
export declare const ActorWithMandateSchema: z.ZodObject<{
|
|
248
|
+
type: z.ZodEnum<["client", "provider", "organization", "agent"]>;
|
|
249
|
+
id: z.ZodString;
|
|
250
|
+
mandate_id: z.ZodOptional<z.ZodString>;
|
|
251
|
+
on_behalf_of: z.ZodOptional<z.ZodObject<{
|
|
252
|
+
type: z.ZodString;
|
|
253
|
+
id: z.ZodString;
|
|
254
|
+
}, "strip", z.ZodTypeAny, {
|
|
255
|
+
type: string;
|
|
256
|
+
id: string;
|
|
257
|
+
}, {
|
|
258
|
+
type: string;
|
|
259
|
+
id: string;
|
|
260
|
+
}>>;
|
|
261
|
+
}, "strip", z.ZodTypeAny, {
|
|
262
|
+
type: "client" | "provider" | "organization" | "agent";
|
|
263
|
+
id: string;
|
|
264
|
+
on_behalf_of?: {
|
|
265
|
+
type: string;
|
|
266
|
+
id: string;
|
|
267
|
+
} | undefined;
|
|
268
|
+
mandate_id?: string | undefined;
|
|
269
|
+
}, {
|
|
270
|
+
type: "client" | "provider" | "organization" | "agent";
|
|
271
|
+
id: string;
|
|
272
|
+
on_behalf_of?: {
|
|
273
|
+
type: string;
|
|
274
|
+
id: string;
|
|
275
|
+
} | undefined;
|
|
276
|
+
mandate_id?: string | undefined;
|
|
277
|
+
}>;
|
|
278
|
+
export type ActorWithMandate = z.infer<typeof ActorWithMandateSchema>;
|
|
279
|
+
/**
|
|
280
|
+
* Maps MCP tool names to the scope(s) required to execute them.
|
|
281
|
+
* An agent must hold at least one of the listed scopes in its mandate.
|
|
282
|
+
*/
|
|
283
|
+
export declare const TOOL_SCOPE_MAP: Record<string, string[]>;
|
|
284
|
+
export declare class MandateValidationError extends Error {
|
|
285
|
+
readonly code: string;
|
|
286
|
+
readonly mandate_id?: string | undefined;
|
|
287
|
+
constructor(code: string, message: string, mandate_id?: string | undefined);
|
|
288
|
+
}
|
|
289
|
+
export interface MandateValidationContext {
|
|
290
|
+
mandate: ServiceMandate;
|
|
291
|
+
agent_id: string;
|
|
292
|
+
tool_name: string;
|
|
293
|
+
context: string;
|
|
294
|
+
/** For conflict-of-interest check: the acting_for of the counterparty agent, if any */
|
|
295
|
+
counterparty_acting_for?: string;
|
|
296
|
+
/** Current time override for testing */
|
|
297
|
+
now?: Date;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Validates a ServiceMandate against the 8 checks defined in §10.3.2.
|
|
301
|
+
* Throws MandateValidationError on failure.
|
|
302
|
+
*/
|
|
303
|
+
export declare function validateMandate(ctx: MandateValidationContext): void;
|
|
304
|
+
export declare function logAuditEntry(client: ServicialoAdapter, entry: Omit<AuditEntry, 'audit_id' | 'timestamp'>): Promise<void>;
|
|
305
|
+
export declare function checkAndIncrementDailyActions(client: ServicialoAdapter, mandate: ServiceMandate): Promise<void>;
|
|
306
|
+
export declare function resolveMandate(client: ServicialoAdapter, mandateId: string): Promise<ServiceMandate>;
|
|
307
|
+
export interface MandateMiddlewareOptions {
|
|
308
|
+
/** Extract the organization context from tool args */
|
|
309
|
+
extractContext?: (args: Record<string, unknown>) => string;
|
|
310
|
+
/** Extract the primary resource ID for audit */
|
|
311
|
+
extractResourceId?: (args: Record<string, unknown>) => string | undefined;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Wraps a tool handler with mandate validation and audit logging.
|
|
315
|
+
*
|
|
316
|
+
* For tools where `actor.type === 'agent'`, validates the mandate before
|
|
317
|
+
* executing and logs an audit entry after. For non-agent actors, passes
|
|
318
|
+
* through without mandate checks.
|
|
319
|
+
*/
|
|
320
|
+
export declare function withMandateValidation<TArgs extends Record<string, unknown>>(toolName: string, handler: (client: ServicialoAdapter, args: TArgs) => Promise<unknown>, options?: MandateMiddlewareOptions): (client: ServicialoAdapter, args: TArgs) => Promise<unknown>;
|
|
321
|
+
//# sourceMappingURL=mandate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mandate.d.ts","sourceRoot":"","sources":["../src/mandate.ts"],"names":[],"mappings":"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"}
|
package/dist/mandate.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Servicialo v0.8 — Delegated Agency Model
|
|
3
|
+
* Mandate validation middleware for the MCP server.
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* 1. Zod schemas for ServiceMandate and audit entries
|
|
7
|
+
* 2. A validation function that enforces the 8 mandate checks (§10.3.2)
|
|
8
|
+
* 3. A middleware wrapper for tool handlers
|
|
9
|
+
* 4. Audit logging on every agent action
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// 1. Schemas
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const MandatePrincipalType = z.enum(['professional', 'patient', 'organization']);
|
|
16
|
+
export const MandateActingFor = z.enum(['professional', 'patient', 'organization']);
|
|
17
|
+
export const MandateStatus = z.enum(['active', 'expired', 'revoked', 'suspended']);
|
|
18
|
+
export const MandateConstraintsSchema = z.object({
|
|
19
|
+
max_actions_per_day: z.number().int().positive().optional(),
|
|
20
|
+
allowed_hours: z.object({
|
|
21
|
+
start: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
|
|
22
|
+
end: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
|
|
23
|
+
timezone: z.string(),
|
|
24
|
+
}).optional(),
|
|
25
|
+
ip_allowlist: z.array(z.string()).optional(),
|
|
26
|
+
require_confirmation_above: z.object({
|
|
27
|
+
amount: z.number().min(0),
|
|
28
|
+
currency: z.string().regex(/^[A-Z]{3}$/),
|
|
29
|
+
}).optional(),
|
|
30
|
+
service_types: z.array(z.string()).optional(),
|
|
31
|
+
}).optional();
|
|
32
|
+
export const ServiceMandateSchema = z.object({
|
|
33
|
+
mandate_id: z.string().uuid(),
|
|
34
|
+
principal_id: z.string(),
|
|
35
|
+
principal_type: MandatePrincipalType,
|
|
36
|
+
agent_id: z.string(),
|
|
37
|
+
agent_name: z.string().optional(),
|
|
38
|
+
acting_for: MandateActingFor,
|
|
39
|
+
context: z.string().regex(/^[a-z_]+:.+$/),
|
|
40
|
+
scopes: z.array(z.string().regex(/^[a-z_]+:[a-z_]+$/)).min(1),
|
|
41
|
+
constraints: MandateConstraintsSchema,
|
|
42
|
+
issued_at: z.string().datetime(),
|
|
43
|
+
expires_at: z.string().datetime(),
|
|
44
|
+
revoked_at: z.string().datetime().optional(),
|
|
45
|
+
revocation_reason: z.string().optional(),
|
|
46
|
+
status: MandateStatus,
|
|
47
|
+
metadata: z.record(z.unknown()).optional(),
|
|
48
|
+
});
|
|
49
|
+
export const AuditResultType = z.enum(['success', 'failure', 'rejected', 'halted']);
|
|
50
|
+
export const AuditEntrySchema = z.object({
|
|
51
|
+
audit_id: z.string().uuid().optional(), // generated server-side
|
|
52
|
+
mandate_id: z.string().uuid(),
|
|
53
|
+
agent_id: z.string(),
|
|
54
|
+
principal_id: z.string(),
|
|
55
|
+
acting_for: MandateActingFor,
|
|
56
|
+
action: z.string(),
|
|
57
|
+
action_input: z.record(z.unknown()).optional(),
|
|
58
|
+
action_result: AuditResultType,
|
|
59
|
+
failure_reason: z.string().optional(),
|
|
60
|
+
resource_id: z.string().optional(),
|
|
61
|
+
context: z.string(),
|
|
62
|
+
ip_address: z.string().optional(),
|
|
63
|
+
request_id: z.string().optional(),
|
|
64
|
+
timestamp: z.string().datetime().optional(),
|
|
65
|
+
});
|
|
66
|
+
// Extended actor schema for v0.8 — adds mandate_id
|
|
67
|
+
export const ActorWithMandateSchema = z.object({
|
|
68
|
+
type: z.enum(['client', 'provider', 'organization', 'agent']),
|
|
69
|
+
id: z.string(),
|
|
70
|
+
mandate_id: z.string().uuid().optional()
|
|
71
|
+
.describe('Required when type = agent. References a valid ServiceMandate.'),
|
|
72
|
+
on_behalf_of: z.object({
|
|
73
|
+
type: z.string(),
|
|
74
|
+
id: z.string(),
|
|
75
|
+
}).optional(),
|
|
76
|
+
});
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// 2. Scope mapping — maps tool names to required scopes
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
/**
|
|
81
|
+
* Maps MCP tool names to the scope(s) required to execute them.
|
|
82
|
+
* An agent must hold at least one of the listed scopes in its mandate.
|
|
83
|
+
*/
|
|
84
|
+
export const TOOL_SCOPE_MAP = {
|
|
85
|
+
// Phase 1 — Discovery (no mandate required for public tools)
|
|
86
|
+
'registry.search': [],
|
|
87
|
+
'registry.get_organization': [],
|
|
88
|
+
'scheduling.check_availability': [],
|
|
89
|
+
'services.list': [],
|
|
90
|
+
// Phase 2 — Entender
|
|
91
|
+
'service.get': ['service:read'],
|
|
92
|
+
'contract.get': ['service:read', 'order:read'],
|
|
93
|
+
// Phase 3 — Comprometer
|
|
94
|
+
'clients.get_or_create': ['patient:write'],
|
|
95
|
+
'scheduling.book': ['schedule:write'],
|
|
96
|
+
'scheduling.confirm': ['schedule:write'],
|
|
97
|
+
// Phase 4 — Ciclo de Vida
|
|
98
|
+
'lifecycle.get_state': ['service:read'],
|
|
99
|
+
'lifecycle.transition': ['service:write'],
|
|
100
|
+
'scheduling.reschedule': ['schedule:write'],
|
|
101
|
+
'scheduling.cancel': ['schedule:write'],
|
|
102
|
+
// Phase 5 — Verificar Entrega
|
|
103
|
+
'delivery.checkin': ['evidence:write'],
|
|
104
|
+
'delivery.checkout': ['evidence:write'],
|
|
105
|
+
'delivery.record_evidence': ['evidence:write'],
|
|
106
|
+
// Phase 6 — Cerrar
|
|
107
|
+
'documentation.create': ['document:write'],
|
|
108
|
+
'payments.create_sale': ['payment:write'],
|
|
109
|
+
'payments.record_payment': ['payment:write'],
|
|
110
|
+
'payments.get_status': ['payment:read'],
|
|
111
|
+
// Service Orders
|
|
112
|
+
'service_orders.list': ['order:read'],
|
|
113
|
+
'service_orders.get': ['order:read'],
|
|
114
|
+
'service_orders.create': ['order:write'],
|
|
115
|
+
'service_orders.propose': ['order:write'],
|
|
116
|
+
'service_orders.activate': ['order:write'],
|
|
117
|
+
'service_orders.get_ledger': ['order:read'],
|
|
118
|
+
// Mandate management
|
|
119
|
+
'mandates.list': ['mandate:read'],
|
|
120
|
+
'mandates.get': ['mandate:read'],
|
|
121
|
+
'mandates.suspend': ['mandate:admin'],
|
|
122
|
+
};
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// 3. Validation errors
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
export class MandateValidationError extends Error {
|
|
127
|
+
code;
|
|
128
|
+
mandate_id;
|
|
129
|
+
constructor(code, message, mandate_id) {
|
|
130
|
+
super(message);
|
|
131
|
+
this.code = code;
|
|
132
|
+
this.mandate_id = mandate_id;
|
|
133
|
+
this.name = 'MandateValidationError';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Validates a ServiceMandate against the 8 checks defined in §10.3.2.
|
|
138
|
+
* Throws MandateValidationError on failure.
|
|
139
|
+
*/
|
|
140
|
+
export function validateMandate(ctx) {
|
|
141
|
+
const { mandate, agent_id, tool_name, context } = ctx;
|
|
142
|
+
const now = ctx.now ?? new Date();
|
|
143
|
+
// 1. Status check
|
|
144
|
+
if (mandate.status !== 'active') {
|
|
145
|
+
throw new MandateValidationError('MANDATE_INACTIVE', `Mandate ${mandate.mandate_id} status is '${mandate.status}'`, mandate.mandate_id);
|
|
146
|
+
}
|
|
147
|
+
// 2. Temporal validity
|
|
148
|
+
const issuedAt = new Date(mandate.issued_at);
|
|
149
|
+
const expiresAt = new Date(mandate.expires_at);
|
|
150
|
+
if (now < issuedAt) {
|
|
151
|
+
throw new MandateValidationError('MANDATE_NOT_YET_VALID', `Mandate ${mandate.mandate_id} is not yet valid (issued_at: ${mandate.issued_at})`, mandate.mandate_id);
|
|
152
|
+
}
|
|
153
|
+
if (now >= expiresAt) {
|
|
154
|
+
throw new MandateValidationError('MANDATE_EXPIRED', `Mandate ${mandate.mandate_id} has expired (expires_at: ${mandate.expires_at})`, mandate.mandate_id);
|
|
155
|
+
}
|
|
156
|
+
if (mandate.revoked_at) {
|
|
157
|
+
throw new MandateValidationError('MANDATE_REVOKED', `Mandate ${mandate.mandate_id} was revoked at ${mandate.revoked_at}`, mandate.mandate_id);
|
|
158
|
+
}
|
|
159
|
+
// 3. Agent identity
|
|
160
|
+
if (mandate.agent_id !== agent_id) {
|
|
161
|
+
throw new MandateValidationError('AGENT_MISMATCH', `Agent '${agent_id}' does not match mandate agent '${mandate.agent_id}'`, mandate.mandate_id);
|
|
162
|
+
}
|
|
163
|
+
// 4. Scope coverage
|
|
164
|
+
const requiredScopes = TOOL_SCOPE_MAP[tool_name];
|
|
165
|
+
if (requiredScopes === undefined) {
|
|
166
|
+
throw new MandateValidationError('UNKNOWN_TOOL', `Tool '${tool_name}' is not registered in the scope map`, mandate.mandate_id);
|
|
167
|
+
}
|
|
168
|
+
// Public tools (empty scope array) don't require mandate validation
|
|
169
|
+
if (requiredScopes.length > 0) {
|
|
170
|
+
const hasScope = requiredScopes.some(scope => mandate.scopes.includes(scope));
|
|
171
|
+
if (!hasScope) {
|
|
172
|
+
throw new MandateValidationError('SCOPE_INSUFFICIENT', `Mandate lacks required scope(s): ${requiredScopes.join(' | ')}. Granted: ${mandate.scopes.join(', ')}`, mandate.mandate_id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 5. Context match
|
|
176
|
+
if (context && mandate.context !== context && !context.startsWith(mandate.context + ':')) {
|
|
177
|
+
throw new MandateValidationError('CONTEXT_MISMATCH', `Context '${context}' does not match mandate context '${mandate.context}'`, mandate.mandate_id);
|
|
178
|
+
}
|
|
179
|
+
// 6. Conflict-of-interest check
|
|
180
|
+
if (ctx.counterparty_acting_for && ctx.counterparty_acting_for === mandate.acting_for) {
|
|
181
|
+
// Same agent acting for both sides — protocol violation
|
|
182
|
+
throw new MandateValidationError('CONFLICT_OF_INTEREST', `Agent cannot act for '${mandate.acting_for}' when counterparty agent also acts for '${ctx.counterparty_acting_for}'`, mandate.mandate_id);
|
|
183
|
+
}
|
|
184
|
+
// 7. Constraints
|
|
185
|
+
if (mandate.constraints) {
|
|
186
|
+
validateConstraints(mandate, now);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function validateConstraints(mandate, now) {
|
|
190
|
+
const constraints = mandate.constraints;
|
|
191
|
+
if (!constraints)
|
|
192
|
+
return;
|
|
193
|
+
// Allowed hours
|
|
194
|
+
if (constraints.allowed_hours) {
|
|
195
|
+
const { start, end, timezone } = constraints.allowed_hours;
|
|
196
|
+
const currentTime = now.toLocaleTimeString('en-US', {
|
|
197
|
+
hour12: false,
|
|
198
|
+
hour: '2-digit',
|
|
199
|
+
minute: '2-digit',
|
|
200
|
+
timeZone: timezone,
|
|
201
|
+
});
|
|
202
|
+
if (currentTime < start || currentTime >= end) {
|
|
203
|
+
throw new MandateValidationError('OUTSIDE_ALLOWED_HOURS', `Action at ${currentTime} (${timezone}) is outside allowed hours ${start}-${end}`, mandate.mandate_id);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// max_actions_per_day is checked asynchronously via the database counter
|
|
207
|
+
// (see incrementDailyActionCount below)
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// 5. Audit logger
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
export async function logAuditEntry(client, entry) {
|
|
213
|
+
try {
|
|
214
|
+
await client.post('/api/coordinalo/mandate-audit', {
|
|
215
|
+
...entry,
|
|
216
|
+
timestamp: new Date().toISOString(),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
// Audit logging failure should not block the operation,
|
|
221
|
+
// but must be reported. In production, send to error tracking.
|
|
222
|
+
console.error('[mandate-audit] Failed to log audit entry:', err);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// 6. Daily action counter
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
export async function checkAndIncrementDailyActions(client, mandate) {
|
|
229
|
+
if (!mandate.constraints?.max_actions_per_day)
|
|
230
|
+
return;
|
|
231
|
+
try {
|
|
232
|
+
const result = await client.post('/api/coordinalo/mandate-actions/increment', {
|
|
233
|
+
mandateId: mandate.mandate_id,
|
|
234
|
+
});
|
|
235
|
+
const count = result?.actionCount ?? 0;
|
|
236
|
+
if (count > mandate.constraints.max_actions_per_day) {
|
|
237
|
+
throw new MandateValidationError('DAILY_LIMIT_EXCEEDED', `Daily action limit (${mandate.constraints.max_actions_per_day}) exceeded for mandate ${mandate.mandate_id}`, mandate.mandate_id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
if (err instanceof MandateValidationError)
|
|
242
|
+
throw err;
|
|
243
|
+
// If the counter service is unavailable, fail open with warning
|
|
244
|
+
console.warn('[mandate] Daily action counter unavailable:', err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// 7. Mandate resolution — fetch mandate from backend
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
export async function resolveMandate(client, mandateId) {
|
|
251
|
+
const result = await client.get(`/api/coordinalo/mandates/${mandateId}`);
|
|
252
|
+
const parsed = ServiceMandateSchema.safeParse(result);
|
|
253
|
+
if (!parsed.success) {
|
|
254
|
+
throw new MandateValidationError('MANDATE_INVALID', `Mandate ${mandateId} failed schema validation: ${parsed.error.message}`, mandateId);
|
|
255
|
+
}
|
|
256
|
+
return parsed.data;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Wraps a tool handler with mandate validation and audit logging.
|
|
260
|
+
*
|
|
261
|
+
* For tools where `actor.type === 'agent'`, validates the mandate before
|
|
262
|
+
* executing and logs an audit entry after. For non-agent actors, passes
|
|
263
|
+
* through without mandate checks.
|
|
264
|
+
*/
|
|
265
|
+
export function withMandateValidation(toolName, handler, options) {
|
|
266
|
+
return async (client, args) => {
|
|
267
|
+
const actor = args.actor;
|
|
268
|
+
// Non-agent actors bypass mandate validation
|
|
269
|
+
if (!actor || actor.type !== 'agent') {
|
|
270
|
+
return handler(client, args);
|
|
271
|
+
}
|
|
272
|
+
// Agent actors require a mandate
|
|
273
|
+
if (!actor.mandate_id) {
|
|
274
|
+
throw new MandateValidationError('MANDATE_REQUIRED', `Agent '${actor.id}' must provide a mandate_id to execute '${toolName}'`);
|
|
275
|
+
}
|
|
276
|
+
// Resolve and validate mandate
|
|
277
|
+
const mandate = await resolveMandate(client, actor.mandate_id);
|
|
278
|
+
const context = options?.extractContext?.(args)
|
|
279
|
+
?? client.orgId
|
|
280
|
+
? `org:${client.orgId}`
|
|
281
|
+
: mandate.context;
|
|
282
|
+
validateMandate({
|
|
283
|
+
mandate,
|
|
284
|
+
agent_id: actor.id,
|
|
285
|
+
tool_name: toolName,
|
|
286
|
+
context,
|
|
287
|
+
});
|
|
288
|
+
// Check daily action limit
|
|
289
|
+
await checkAndIncrementDailyActions(client, mandate);
|
|
290
|
+
// Write-ahead audit entry (action_result = 'success' will be updated on failure)
|
|
291
|
+
const resourceId = options?.extractResourceId?.(args);
|
|
292
|
+
try {
|
|
293
|
+
const result = await handler(client, args);
|
|
294
|
+
// Log success
|
|
295
|
+
await logAuditEntry(client, {
|
|
296
|
+
mandate_id: mandate.mandate_id,
|
|
297
|
+
agent_id: mandate.agent_id,
|
|
298
|
+
principal_id: mandate.principal_id,
|
|
299
|
+
acting_for: mandate.acting_for,
|
|
300
|
+
action: toolName,
|
|
301
|
+
action_input: sanitizeInput(args),
|
|
302
|
+
action_result: 'success',
|
|
303
|
+
resource_id: resourceId,
|
|
304
|
+
context: mandate.context,
|
|
305
|
+
});
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
// Log failure
|
|
310
|
+
await logAuditEntry(client, {
|
|
311
|
+
mandate_id: mandate.mandate_id,
|
|
312
|
+
agent_id: mandate.agent_id,
|
|
313
|
+
principal_id: mandate.principal_id,
|
|
314
|
+
acting_for: mandate.acting_for,
|
|
315
|
+
action: toolName,
|
|
316
|
+
action_input: sanitizeInput(args),
|
|
317
|
+
action_result: err instanceof MandateValidationError ? 'rejected' : 'failure',
|
|
318
|
+
failure_reason: err instanceof Error ? err.message : String(err),
|
|
319
|
+
resource_id: resourceId,
|
|
320
|
+
context: mandate.context,
|
|
321
|
+
});
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// 9. Input sanitization — redact sensitive fields before audit logging
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
const SENSITIVE_FIELDS = new Set([
|
|
330
|
+
'password', 'token', 'api_key', 'secret', 'credential',
|
|
331
|
+
'clinical_notes', 'diagnosis', 'medical_history',
|
|
332
|
+
'rut', 'ssn', 'national_id',
|
|
333
|
+
]);
|
|
334
|
+
function sanitizeInput(args) {
|
|
335
|
+
const sanitized = {};
|
|
336
|
+
for (const [key, value] of Object.entries(args)) {
|
|
337
|
+
if (SENSITIVE_FIELDS.has(key.toLowerCase())) {
|
|
338
|
+
sanitized[key] = '[REDACTED]';
|
|
339
|
+
}
|
|
340
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
341
|
+
sanitized[key] = sanitizeInput(value);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
sanitized[key] = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return sanitized;
|
|
348
|
+
}
|
|
349
|
+
//# sourceMappingURL=mandate.js.map
|