agent-delegation.mjs
299 lines 10.0 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * MCP tools for agent delegation (Phase 7C-6). Gated by DELEGATION_ENABLED (default off).
3 *
4 * @see docs/AGENT-DELEGATION-V0-SPEC.md
5 */
6
7 import { z } from 'zod';
8
9 import { loadConfig } from '../../lib/config.mjs';
10 import { createProposal } from '../../hub/proposals-store.mjs';
11 import {
12 handleAgentIdentityRegisterProposeRequest,
13 handleAgentIdentityListRequest,
14 handleDelegationConsentProposeRequest,
15 handleDelegationConsentRevokeRequest,
16 handleDelegationGrantMintRequest,
17 handleDelegationGrantRevokeRequest,
18 handleDelegationGrantListRequest,
19 handleDelegationAuditAppendRequest,
20 } from '../../lib/agent/delegation.mjs';
21 import { jsonResponse, jsonError } from '../create-server.mjs';
22
23 /**
24 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
25 */
26 export function registerAgentDelegationTools(server) {
27 server.registerTool(
28 'agent_identity_register',
29 {
30 description:
31 'Propose registration of an agent identity (SD-4). Gated by DELEGATION_ENABLED (default off).',
32 inputSchema: {
33 kind: z.enum(['user_owned', 'org_owned', 'delegate']).describe('Agent kind'),
34 agent_id: z.string().optional().describe('Optional stable agent id (agent_<token>)'),
35 label: z.string().optional().describe('Display label (untrusted)'),
36 scope_ceiling: z.enum(['personal', 'project', 'org']).optional(),
37 vault_id: z.string().optional(),
38 },
39 },
40 async (args) => {
41 try {
42 const config = loadConfig();
43 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
44 const result = await handleAgentIdentityRegisterProposeRequest({
45 dataDir: config.data_dir,
46 vaultId,
47 userId: config.user_id ?? 'cli-user',
48 kind: args.kind,
49 agentId: args.agent_id,
50 label: args.label,
51 scopeCeiling: args.scope_ceiling,
52 createProposal,
53 });
54 if (!result.ok) return jsonError(result.error, result.code);
55 return jsonResponse(result.payload);
56 } catch (e) {
57 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
58 }
59 },
60 );
61
62 server.registerTool(
63 'agent_identity_list',
64 {
65 description: 'List agent identities. Gated by DELEGATION_ENABLED.',
66 inputSchema: {
67 kind: z.enum(['user_owned', 'org_owned', 'delegate']).optional(),
68 status: z.enum(['active', 'suspended', 'revoked']).optional(),
69 vault_id: z.string().optional(),
70 },
71 },
72 async (args) => {
73 try {
74 const config = loadConfig();
75 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
76 const result = handleAgentIdentityListRequest({
77 dataDir: config.data_dir,
78 vaultId,
79 kind: args.kind,
80 status: args.status,
81 });
82 if (!result.ok) return jsonError(result.error, result.code);
83 return jsonResponse(result.payload);
84 } catch (e) {
85 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
86 }
87 },
88 );
89
90 server.registerTool(
91 'delegation_consent_propose',
92 {
93 description:
94 'Propose delegation consent (SD-4 intent: delegation_consent_create). Gated by DELEGATION_ENABLED.',
95 inputSchema: {
96 delegate_agent_id: z.string().describe('Agent id to delegate to'),
97 scope: z.enum(['personal', 'project', 'org']),
98 workspace_id: z.string().optional(),
99 allowed_flow_ids: z.array(z.string()).optional(),
100 allowed_task_kinds: z.array(z.string()).optional(),
101 allowed_task_ids: z.array(z.string()).optional(),
102 expires_at: z.string().optional(),
103 vault_id: z.string().optional(),
104 },
105 },
106 async (args) => {
107 try {
108 const config = loadConfig();
109 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
110 const result = await handleDelegationConsentProposeRequest({
111 dataDir: config.data_dir,
112 vaultId,
113 userId: config.user_id ?? 'cli-user',
114 delegateAgentId: args.delegate_agent_id,
115 scope: args.scope,
116 workspaceId: args.workspace_id,
117 allowedFlowIds: args.allowed_flow_ids,
118 allowedTaskKinds: args.allowed_task_kinds,
119 allowedTaskIds: args.allowed_task_ids,
120 expiresAt: args.expires_at,
121 createProposal,
122 });
123 if (!result.ok) return jsonError(result.error, result.code);
124 return jsonResponse(result.payload);
125 } catch (e) {
126 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
127 }
128 },
129 );
130
131 server.registerTool(
132 'delegation_consent_revoke',
133 {
134 description: 'Revoke delegation consent immediately. Gated by DELEGATION_ENABLED.',
135 inputSchema: {
136 consent_id: z.string(),
137 vault_id: z.string().optional(),
138 },
139 },
140 async (args) => {
141 try {
142 const config = loadConfig();
143 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
144 const result = handleDelegationConsentRevokeRequest({
145 dataDir: config.data_dir,
146 vaultId,
147 consentId: args.consent_id,
148 userId: config.user_id ?? 'cli-user',
149 });
150 if (!result.ok) return jsonError(result.error, result.code);
151 return jsonResponse(result.payload);
152 } catch (e) {
153 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
154 }
155 },
156 );
157
158 server.registerTool(
159 'delegation_grant_mint',
160 {
161 description: 'Mint short-lived delegation grant from active consent. Gated by DELEGATION_ENABLED.',
162 inputSchema: {
163 consent_id: z.string(),
164 actor_agent_id: z.string(),
165 task_ref: z.string().optional(),
166 run_ref: z.string().optional(),
167 flow_id: z.string().optional(),
168 flow_version: z.string().optional(),
169 ttl_seconds: z.number().int().positive().optional(),
170 vault_id: z.string().optional(),
171 },
172 },
173 async (args) => {
174 try {
175 const config = loadConfig();
176 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
177 const result = handleDelegationGrantMintRequest({
178 dataDir: config.data_dir,
179 vaultId,
180 consentId: args.consent_id,
181 actorAgentId: args.actor_agent_id,
182 taskRef: args.task_ref,
183 runRef: args.run_ref,
184 flowId: args.flow_id,
185 flowVersion: args.flow_version,
186 ttlSeconds: args.ttl_seconds,
187 });
188 if (!result.ok) return jsonError(result.error, result.code);
189 return jsonResponse(result.payload);
190 } catch (e) {
191 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
192 }
193 },
194 );
195
196 server.registerTool(
197 'delegation_grant_revoke',
198 {
199 description: 'Revoke delegation grant immediately. Gated by DELEGATION_ENABLED.',
200 inputSchema: {
201 grant_id: z.string(),
202 vault_id: z.string().optional(),
203 },
204 },
205 async (args) => {
206 try {
207 const config = loadConfig();
208 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
209 const result = handleDelegationGrantRevokeRequest({
210 dataDir: config.data_dir,
211 vaultId,
212 grantId: args.grant_id,
213 });
214 if (!result.ok) return jsonError(result.error, result.code);
215 return jsonResponse(result.payload);
216 } catch (e) {
217 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
218 }
219 },
220 );
221
222 server.registerTool(
223 'delegation_grant_list',
224 {
225 description: 'List delegation grant metadata (no bearer). Gated by DELEGATION_ENABLED.',
226 inputSchema: {
227 actor_agent_id: z.string().optional(),
228 vault_id: z.string().optional(),
229 },
230 },
231 async (args) => {
232 try {
233 const config = loadConfig();
234 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
235 const result = handleDelegationGrantListRequest({
236 dataDir: config.data_dir,
237 vaultId,
238 actorAgentId: args.actor_agent_id,
239 });
240 if (!result.ok) return jsonError(result.error, result.code);
241 return jsonResponse(result.payload);
242 } catch (e) {
243 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
244 }
245 },
246 );
247
248 server.registerTool(
249 'delegation_audit_append',
250 {
251 description: 'Append pointer-safe delegation audit entry. Gated by DELEGATION_ENABLED.',
252 inputSchema: {
253 grant_id: z.string(),
254 actor_agent_id: z.string(),
255 principal_ref: z.string().describe('Server-verified hashed principal ref'),
256 action: z.enum([
257 'advance_step',
258 'complete_task',
259 'propose_outcome',
260 'invoke_tool',
261 'mint_subgrant',
262 ]),
263 evidence_refs: z.array(z.string()).min(1),
264 task_ref: z.string().optional(),
265 run_ref: z.string().optional(),
266 flow_id: z.string().optional(),
267 flow_version: z.string().optional(),
268 step_id: z.string().optional(),
269 execution_location: z.enum(['local', 'hosted', 'hybrid']).optional(),
270 vault_id: z.string().optional(),
271 },
272 },
273 async (args) => {
274 try {
275 const config = loadConfig();
276 const vaultId = args.vault_id?.trim() || config.default_vault_id || 'default';
277 const result = handleDelegationAuditAppendRequest({
278 dataDir: config.data_dir,
279 vaultId,
280 grantId: args.grant_id,
281 actorAgentId: args.actor_agent_id,
282 principalRef: args.principal_ref,
283 action: args.action,
284 evidenceRefs: args.evidence_refs,
285 taskRef: args.task_ref,
286 runRef: args.run_ref,
287 flowId: args.flow_id,
288 flowVersion: args.flow_version,
289 stepId: args.step_id,
290 executionLocation: args.execution_location,
291 });
292 if (!result.ok) return jsonError(result.error, result.code);
293 return jsonResponse(result.payload);
294 } catch (e) {
295 return jsonError(e.message || String(e), 'RUNTIME_ERROR');
296 }
297 },
298 );
299 }
File History 2 commits
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b fix(7C-L1b): route hosted delegation proposals through cani… Human minor 30 days ago