@agentvalet/mcp-server 1.15.0 → 1.18.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/gate-client.js +7 -1
- package/dist/index.js +2 -0
- package/dist/instructions.js +1 -1
- package/dist/registration-keys.js +88 -0
- package/dist/tools/handlers.js +154 -3
- package/dist/tools/schemas.js +33 -7
- package/package.json +1 -1
package/dist/gate-client.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// 1. Send a request to the public gate (POST /v1/actions) or the Observe
|
|
8
8
|
// relay (POST /v1/observe/actions).
|
|
9
|
-
// 2. Map the wire response to exactly one of the
|
|
9
|
+
// 2. Map the wire response to exactly one of the seven documented decisions, so
|
|
10
10
|
// a connector switches on a single discriminated union instead of
|
|
11
11
|
// re-deriving status-code semantics in every integration.
|
|
12
12
|
//
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
/** Agent states that mean "the agent itself is halted", surfaced as a 403 by the
|
|
19
19
|
* permission check. Distinct from a policy denial of an otherwise-healthy agent. */
|
|
20
20
|
const SUSPENDED_REASONS = new Set(["agent_suspended", "circuit_breaker_open"]);
|
|
21
|
+
/** An expired child identity (G6): permanent for this agent, so not "suspended". */
|
|
22
|
+
const EXPIRED_REASONS = new Set(["agent_expired"]);
|
|
21
23
|
function readField(body, key) {
|
|
22
24
|
if (body && typeof body === "object" && key in body) {
|
|
23
25
|
const v = body[key];
|
|
@@ -34,6 +36,7 @@ function readField(body, key) {
|
|
|
34
36
|
* 202 -> pending_approval
|
|
35
37
|
* 403 reason in {agent_suspended,
|
|
36
38
|
* circuit_breaker_open} -> suspended
|
|
39
|
+
* 403 reason agent_expired -> expired
|
|
37
40
|
* 401 / 5xx -> error
|
|
38
41
|
* other 4xx (402/403/412/429/...) -> denied
|
|
39
42
|
*/
|
|
@@ -55,6 +58,9 @@ export function classifyGateResponse(status, body) {
|
|
|
55
58
|
if (reason && SUSPENDED_REASONS.has(reason)) {
|
|
56
59
|
return { decision: "suspended", status, reason, raw: body };
|
|
57
60
|
}
|
|
61
|
+
if (reason && EXPIRED_REASONS.has(reason)) {
|
|
62
|
+
return { decision: "expired", status, reason, raw: body };
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
// Identity rejection and upstream/server failures are not policy decisions.
|
|
60
66
|
// 424 is the proxy's upstream-failure code (a 502/504 downgraded so
|
package/dist/index.js
CHANGED
|
@@ -180,6 +180,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
180
180
|
scope: args.scope,
|
|
181
181
|
data: bodyArg,
|
|
182
182
|
...(typeof args.connection_id === "string" ? { connection_id: args.connection_id } : {}),
|
|
183
|
+
...(args.caller && typeof args.caller === "object" ? { caller: args.caller } : {}),
|
|
183
184
|
...(typeof args.reason === "string" ? { reason: args.reason } : {}),
|
|
184
185
|
}, progressToken);
|
|
185
186
|
}
|
|
@@ -213,6 +214,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
213
214
|
// No `data` alias on the new tools: new surface, no legacy.
|
|
214
215
|
...(args.body !== undefined ? { data: args.body } : {}),
|
|
215
216
|
...(typeof args.connection_id === "string" ? { connection_id: args.connection_id } : {}),
|
|
217
|
+
...(args.caller && typeof args.caller === "object" ? { caller: args.caller } : {}),
|
|
216
218
|
...(typeof args.reason === "string" ? { reason: args.reason } : {}),
|
|
217
219
|
}, progressToken);
|
|
218
220
|
}
|
package/dist/instructions.js
CHANGED
|
@@ -20,7 +20,7 @@ Matching a platform to a vendor: \`platformName\` is a label the OWNER typed and
|
|
|
20
20
|
|
|
21
21
|
Response handling:
|
|
22
22
|
- \`use_platform\` now waits up to 50 seconds for owner approval automatically. Most approvals complete in this window — the call simply takes longer and returns the upstream result like a normal success. Tell the user "waiting for owner approval" if the call is taking more than ~5 seconds.
|
|
23
|
-
- If \`use_platform\` returns a \`pending_approval\` envelope after that wait, the action is queued and will run asynchronously when the owner approves. Tell the user clearly. Do NOT retry —
|
|
23
|
+
- If \`use_platform\` returns a \`pending_approval\` envelope after that wait, the action is queued and will run asynchronously when the owner approves. Tell the user clearly. Do NOT retry — wait, or check back later for the outcome. An identical retry while the approval is pending returns the same approval id (no second approval is queued), but it does not speed anything up.
|
|
24
24
|
- Do not retry a denied call with a different scope.
|
|
25
25
|
- If a \`use_platform\` error response includes a \`report_hint\` block, you may briefly ask the user "Want me to lodge this with your AgentValet owner?" — on yes, call \`report_self_diagnostic\` with a one-sentence narrative plus the \`correlation_id\` from the hint so the owner can investigate.
|
|
26
26
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// apps/mcp-server/src/registration-keys.ts
|
|
2
|
+
//
|
|
3
|
+
// Local key custody for the agent_register -> owner approval -> agent_status
|
|
4
|
+
// flow (Claude Desktop / MCPB self-registration).
|
|
5
|
+
//
|
|
6
|
+
// agent_register generates the RSA keypair HERE and sends only the public key
|
|
7
|
+
// (POST /v1/register public_key_pem). The private key waits on disk, keyed by
|
|
8
|
+
// the registration token, until agent_status sees the approval and installs it
|
|
9
|
+
// as the agent's key. The private key never leaves this machine, never reaches
|
|
10
|
+
// the dashboard, and is never put in a tool result the model can see.
|
|
11
|
+
//
|
|
12
|
+
// Layout (under ~/.agentvalet, dir 0700, key files 0600):
|
|
13
|
+
// pending/<registration_token>.key private key awaiting approval
|
|
14
|
+
// agents/<agent_id>.key installed key for an approved agent
|
|
15
|
+
// agent.key + agent.json the DEFAULT identity config.ts boots
|
|
16
|
+
// from — written ONLY when neither file
|
|
17
|
+
// exists, so a registration never clobbers
|
|
18
|
+
// an identity another agent already owns
|
|
19
|
+
// (see invariant_register_clobbers_existing_agent_key).
|
|
20
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { getAgentvaletDir, getIdentityPath, getKeyPath, persistBindArtifacts } from "./bind.js";
|
|
24
|
+
const SAFE_SEGMENT = /^[A-Za-z0-9_-]{1,128}$/;
|
|
25
|
+
export function generateRegistrationKeypair() {
|
|
26
|
+
return generateKeyPairSync("rsa", {
|
|
27
|
+
modulusLength: 2048,
|
|
28
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
29
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function ensureDir(dir) {
|
|
33
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
34
|
+
}
|
|
35
|
+
export function pendingKeyPath(token) {
|
|
36
|
+
if (!SAFE_SEGMENT.test(token))
|
|
37
|
+
return null;
|
|
38
|
+
return join(getAgentvaletDir(), "pending", `${token}.key`);
|
|
39
|
+
}
|
|
40
|
+
export function savePendingKey(token, privateKeyPem) {
|
|
41
|
+
const path = pendingKeyPath(token);
|
|
42
|
+
if (!path)
|
|
43
|
+
throw new Error("Unexpected registration token format; refusing to use it as a file name");
|
|
44
|
+
ensureDir(join(getAgentvaletDir(), "pending"));
|
|
45
|
+
writeFileSync(path, privateKeyPem, { mode: 0o600 });
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
export function readPendingKey(token) {
|
|
49
|
+
const path = pendingKeyPath(token);
|
|
50
|
+
if (!path || !existsSync(path))
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(path, "utf-8").trim();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function deletePendingKey(token) {
|
|
60
|
+
const path = pendingKeyPath(token);
|
|
61
|
+
if (path)
|
|
62
|
+
rmSync(path, { force: true });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Installs an approved agent's private key. Always writes
|
|
66
|
+
* agents/<agent_id>.key; additionally writes the default agent.key +
|
|
67
|
+
* agent.json when (and only when) no default identity exists yet, so the next
|
|
68
|
+
* server start picks it up with no env vars at all.
|
|
69
|
+
*/
|
|
70
|
+
export function installAgentKey(opts) {
|
|
71
|
+
if (!SAFE_SEGMENT.test(opts.agentId)) {
|
|
72
|
+
throw new Error("Unexpected agent_id format; refusing to use it as a file name");
|
|
73
|
+
}
|
|
74
|
+
const agentsDir = join(getAgentvaletDir(), "agents");
|
|
75
|
+
ensureDir(agentsDir);
|
|
76
|
+
const keyPath = join(agentsDir, `${opts.agentId}.key`);
|
|
77
|
+
writeFileSync(keyPath, opts.privateKeyPem, { mode: 0o600 });
|
|
78
|
+
const defaultTaken = existsSync(getKeyPath()) || existsSync(getIdentityPath());
|
|
79
|
+
if (!defaultTaken) {
|
|
80
|
+
persistBindArtifacts({
|
|
81
|
+
agent_id: opts.agentId,
|
|
82
|
+
owner_id: opts.ownerId,
|
|
83
|
+
proxy_url: opts.proxyUrl,
|
|
84
|
+
bound_at: new Date().toISOString(),
|
|
85
|
+
}, opts.privateKeyPem);
|
|
86
|
+
}
|
|
87
|
+
return { keyPath, becameDefault: !defaultTaken };
|
|
88
|
+
}
|
package/dist/tools/handlers.js
CHANGED
|
@@ -8,6 +8,8 @@ import { errorContent, jsonContent, tryParseJson, safeJsonParse, sleep, diagnose
|
|
|
8
8
|
import { requireCredentials, fetchWithAuth } from "../auth.js";
|
|
9
9
|
import { recordPermissionsVersion } from "../permissions-watcher.js";
|
|
10
10
|
import { parseConsentRefusal, elicitConsent, consentFallbackText, connectUrlFor } from "./consent.js";
|
|
11
|
+
import { importPKCS8 } from "jose";
|
|
12
|
+
import { generateRegistrationKeypair, savePendingKey, readPendingKey, deletePendingKey, installAgentKey, } from "../registration-keys.js";
|
|
11
13
|
export async function handleListPlatforms(ctx) {
|
|
12
14
|
const gate = await requireCredentials(ctx);
|
|
13
15
|
if (gate)
|
|
@@ -99,6 +101,7 @@ consentRetried = false) {
|
|
|
99
101
|
scope: params.scope,
|
|
100
102
|
...(params.data !== undefined && { data: params.data }),
|
|
101
103
|
...(params.connection_id ? { connection_id: params.connection_id } : {}),
|
|
104
|
+
...(params.caller ? { caller: params.caller } : {}),
|
|
102
105
|
// Agent-supplied justification. Forwarded verbatim; the proxy normalizes
|
|
103
106
|
// and caps it before it reaches the approver's screen.
|
|
104
107
|
...(params.reason ? { reason: params.reason } : {}),
|
|
@@ -249,13 +252,34 @@ async function sendProgress(ctx, token, progress, total, message) {
|
|
|
249
252
|
// Best-effort — never let a failed notification break the call.
|
|
250
253
|
}
|
|
251
254
|
}
|
|
255
|
+
// Unwraps the proxy's { data, _meta } envelope when present.
|
|
256
|
+
function unwrapData(parsed) {
|
|
257
|
+
if (!parsed || typeof parsed !== "object")
|
|
258
|
+
return null;
|
|
259
|
+
const obj = parsed;
|
|
260
|
+
if (obj.data && typeof obj.data === "object")
|
|
261
|
+
return obj.data;
|
|
262
|
+
return obj;
|
|
263
|
+
}
|
|
264
|
+
function structured(result) {
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
267
|
+
structuredContent: result,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// agent_register: the keypair is generated HERE and only the public key is
|
|
271
|
+
// sent, so the private key never leaves this machine (and never transits the
|
|
272
|
+
// owner's dashboard). It waits on disk until agent_status sees the approval.
|
|
252
273
|
export async function handleAgentRegister(ctx, args) {
|
|
274
|
+
const { publicKey, privateKey } = generateRegistrationKeypair();
|
|
253
275
|
let response;
|
|
254
276
|
try {
|
|
255
277
|
response = await fetchWithTimeout(`${ctx.PROXY_URL}/v1/register`, {
|
|
256
278
|
method: "POST",
|
|
257
279
|
headers: { "Content-Type": "application/json" },
|
|
258
|
-
|
|
280
|
+
// public_key_pem last so a model-supplied value can never override it —
|
|
281
|
+
// we would not hold the matching private key.
|
|
282
|
+
body: JSON.stringify({ ...args, public_key_pem: publicKey }),
|
|
259
283
|
});
|
|
260
284
|
}
|
|
261
285
|
catch (err) {
|
|
@@ -264,7 +288,65 @@ export async function handleAgentRegister(ctx, args) {
|
|
|
264
288
|
const body = await response.text();
|
|
265
289
|
if (!response.ok)
|
|
266
290
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
267
|
-
|
|
291
|
+
const data = unwrapData(tryParseJson(body));
|
|
292
|
+
const token = typeof data?.registration_token === "string" ? data.registration_token : null;
|
|
293
|
+
if (!data || !token)
|
|
294
|
+
return jsonContent(body);
|
|
295
|
+
// Only keep the private key if the proxy actually bound our public key. An
|
|
296
|
+
// older proxy ignores public_key_pem and issues its own keypair; agent_status
|
|
297
|
+
// then collects that one via claim-key instead.
|
|
298
|
+
let keyStoredLocally = false;
|
|
299
|
+
if (data.keypair_source === "client") {
|
|
300
|
+
try {
|
|
301
|
+
savePendingKey(token, privateKey);
|
|
302
|
+
keyStoredLocally = true;
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
return errorContent(`Registration ${token} was submitted, but the private key could not be saved locally ` +
|
|
306
|
+
`(${err instanceof Error ? err.message : err}). Ask the owner to reject it and register again.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return structured({
|
|
310
|
+
...data,
|
|
311
|
+
key_stored_locally: keyStoredLocally,
|
|
312
|
+
next_step: "Ask the owner to approve this agent in the AgentValet dashboard, then call agent_status with the registration_token. " +
|
|
313
|
+
"The private key stays on this machine; agent_status installs it once approved.",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async function claimServerIssuedKey(ctx, token) {
|
|
317
|
+
try {
|
|
318
|
+
const res = await fetchWithTimeout(`${ctx.PROXY_URL}/v1/register/claim-key/${encodeURIComponent(token)}`, {
|
|
319
|
+
method: "GET",
|
|
320
|
+
headers: { "Content-Type": "application/json" },
|
|
321
|
+
});
|
|
322
|
+
if (!res.ok)
|
|
323
|
+
return null;
|
|
324
|
+
const json = (await res.json());
|
|
325
|
+
return typeof json.private_key === "string" && json.private_key.includes("PRIVATE KEY")
|
|
326
|
+
? json.private_key
|
|
327
|
+
: null;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// If this server booted with no usable key (state B/C), adopt the newly
|
|
334
|
+
// approved identity in-process so platform tools work without a restart.
|
|
335
|
+
async function activateInProcess(ctx, agentId, ownerId, pem) {
|
|
336
|
+
if (ctx.AGENT_PRIVATE_KEY_RAW !== null)
|
|
337
|
+
return false;
|
|
338
|
+
if (ctx.AGENT_ID && ctx.AGENT_ID !== agentId)
|
|
339
|
+
return false;
|
|
340
|
+
try {
|
|
341
|
+
ctx.privateKey = await importPKCS8(pem, "RS256");
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
ctx.AGENT_ID = agentId;
|
|
347
|
+
ctx.OWNER_ID = ownerId;
|
|
348
|
+
ctx.AGENT_PRIVATE_KEY_RAW = pem;
|
|
349
|
+
return true;
|
|
268
350
|
}
|
|
269
351
|
export async function handleAgentStatus(ctx, token) {
|
|
270
352
|
let response;
|
|
@@ -280,7 +362,76 @@ export async function handleAgentStatus(ctx, token) {
|
|
|
280
362
|
const body = await response.text();
|
|
281
363
|
if (!response.ok)
|
|
282
364
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
283
|
-
|
|
365
|
+
const data = unwrapData(tryParseJson(body));
|
|
366
|
+
if (!data || typeof data.status !== "string")
|
|
367
|
+
return jsonContent(body);
|
|
368
|
+
if (data.status === "rejected" || data.status === "expired") {
|
|
369
|
+
deletePendingKey(token);
|
|
370
|
+
return structured(data);
|
|
371
|
+
}
|
|
372
|
+
if (data.status !== "approved")
|
|
373
|
+
return structured(data);
|
|
374
|
+
const agentId = typeof data.agent_id === "string" ? data.agent_id : "";
|
|
375
|
+
const mcpEnv = (data.mcp_config
|
|
376
|
+
?.mcpServers?.agentvalet?.env) ?? {};
|
|
377
|
+
const ownerId = typeof data.owner_id === "string" ? data.owner_id : (mcpEnv.OWNER_ID ?? "");
|
|
378
|
+
// Locate the private key: our own (client keypair) or, for a legacy
|
|
379
|
+
// server-issued keypair, the one-time claim-key pickup.
|
|
380
|
+
let pem = null;
|
|
381
|
+
let source = null;
|
|
382
|
+
if (data.keypair_source === "client") {
|
|
383
|
+
pem = readPendingKey(token);
|
|
384
|
+
source = "client";
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
pem = await claimServerIssuedKey(ctx, token);
|
|
388
|
+
source = "server";
|
|
389
|
+
}
|
|
390
|
+
const result = {
|
|
391
|
+
status: "approved",
|
|
392
|
+
agent_id: agentId,
|
|
393
|
+
owner_id: ownerId,
|
|
394
|
+
keypair_source: source,
|
|
395
|
+
};
|
|
396
|
+
if (!pem || !agentId || !ownerId) {
|
|
397
|
+
result.key_installed = false;
|
|
398
|
+
result.next_step =
|
|
399
|
+
source === "client"
|
|
400
|
+
? "Approved, but this machine does not hold the private key for this registration (it was generated by a different install, or already installed). " +
|
|
401
|
+
"Check ~/.agentvalet/agents/, or register again from this machine."
|
|
402
|
+
: "Approved, but the one-time private key could not be collected (already claimed, or the 5-minute window after approval has passed). " +
|
|
403
|
+
"Register again from this machine so the key is generated locally.";
|
|
404
|
+
return structured(result);
|
|
405
|
+
}
|
|
406
|
+
let installed;
|
|
407
|
+
try {
|
|
408
|
+
installed = installAgentKey({ agentId, ownerId, proxyUrl: ctx.PROXY_URL, privateKeyPem: pem });
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
return errorContent(`Approved as ${agentId}, but the private key could not be saved: ${err instanceof Error ? err.message : err}`);
|
|
412
|
+
}
|
|
413
|
+
deletePendingKey(token);
|
|
414
|
+
const activated = await activateInProcess(ctx, agentId, ownerId, pem);
|
|
415
|
+
result.key_installed = true;
|
|
416
|
+
result.key_path = installed.keyPath;
|
|
417
|
+
result.default_identity = installed.becameDefault;
|
|
418
|
+
result.activated = activated;
|
|
419
|
+
// The config to use this agent elsewhere: a key PATH, never the key itself.
|
|
420
|
+
result.mcp_config = {
|
|
421
|
+
mcpServers: {
|
|
422
|
+
agentvalet: {
|
|
423
|
+
command: "npx",
|
|
424
|
+
args: ["-y", "@agentvalet/mcp-server"],
|
|
425
|
+
env: { AGENT_ID: agentId, OWNER_ID: ownerId, PROXY_URL: ctx.PROXY_URL, AGENT_PRIVATE_KEY_PATH: installed.keyPath },
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
result.next_step = activated
|
|
430
|
+
? "Registered and active in this session. Call list_platforms to see what the owner granted."
|
|
431
|
+
: installed.becameDefault
|
|
432
|
+
? "Registered. The key is installed as this machine's default AgentValet identity; restart the MCP server (or Claude Desktop) to use it."
|
|
433
|
+
: "Registered. Another AgentValet identity is already configured on this machine, so it was left untouched; use the mcp_config above (AGENT_PRIVATE_KEY_PATH) to run this agent.";
|
|
434
|
+
return structured(result);
|
|
284
435
|
}
|
|
285
436
|
export async function handleAuthzenEvaluate(ctx, platformId, scope) {
|
|
286
437
|
const gate = await requireCredentials(ctx);
|
package/dist/tools/schemas.js
CHANGED
|
@@ -98,6 +98,11 @@ export const USE_PLATFORM_TOOL = {
|
|
|
98
98
|
type: "string",
|
|
99
99
|
description: "Optional. Target a specific connection when a platform has more than one connected (e.g. two GitHub orgs). Value is the connection id shown in the dashboard. Omit to use the default connection.",
|
|
100
100
|
},
|
|
101
|
+
caller: {
|
|
102
|
+
type: "object",
|
|
103
|
+
description: "Optional. Who the human is that this call is for, when this agent serves many people: { assertion: '<JWT from an identity provider the org registered>' } (verified) or { source, id } (asserted; honoured only where the platform allows it). On a platform with per-person tokens, the person's own upstream token is used; if they have not authorized yet the call returns consent_required with a consent_url to give them.",
|
|
104
|
+
properties: { assertion: { type: "string" }, source: { type: "string" }, id: { type: "string" } },
|
|
105
|
+
},
|
|
101
106
|
reason: {
|
|
102
107
|
type: "string",
|
|
103
108
|
description: "Optional but strongly encouraged. One sentence on WHY you are making this call. If the action requires owner approval, this is shown to the human who decides — without it their approval is a blind yes/no. Max 500 characters (longer is truncated). Plain text; markup is not rendered.",
|
|
@@ -125,6 +130,11 @@ export const READ_PLATFORM_TOOL = {
|
|
|
125
130
|
type: "string",
|
|
126
131
|
description: "Optional. Target a specific connection when a platform has more than one connected. Omit to use the default.",
|
|
127
132
|
},
|
|
133
|
+
caller: {
|
|
134
|
+
type: "object",
|
|
135
|
+
description: "Optional. Who the human is that this call is for, when this agent serves many people: { assertion: '<JWT from an identity provider the org registered>' } (verified) or { source, id } (asserted; honoured only where the platform allows it). On a platform with per-person tokens, the person's own upstream token is used; if they have not authorized yet the call returns consent_required with a consent_url to give them.",
|
|
136
|
+
properties: { assertion: { type: "string" }, source: { type: "string" }, id: { type: "string" } },
|
|
137
|
+
},
|
|
128
138
|
reason: {
|
|
129
139
|
type: "string",
|
|
130
140
|
description: "Optional but strongly encouraged. One sentence on WHY you are making this call. Shown to the human approver if the call is gated. Max 500 characters; plain text.",
|
|
@@ -140,7 +150,7 @@ export const WRITE_PLATFORM_TOOL = {
|
|
|
140
150
|
type: "object",
|
|
141
151
|
properties: {
|
|
142
152
|
platform: { type: "string", description: "Platform ID (e.g. github, slack, stripe)" },
|
|
143
|
-
endpoint: { type: "string", description: "Path in the platform's own API (e.g. /
|
|
153
|
+
endpoint: { type: "string", description: "Path in the platform's own API (e.g. /chat.postMessage)" },
|
|
144
154
|
method: {
|
|
145
155
|
type: "string",
|
|
146
156
|
enum: ["POST", "PUT", "PATCH"],
|
|
@@ -156,6 +166,11 @@ export const WRITE_PLATFORM_TOOL = {
|
|
|
156
166
|
type: "string",
|
|
157
167
|
description: "Optional. Target a specific connection when a platform has more than one connected. Omit to use the default.",
|
|
158
168
|
},
|
|
169
|
+
caller: {
|
|
170
|
+
type: "object",
|
|
171
|
+
description: "Optional. Who the human is that this call is for, when this agent serves many people: { assertion: '<JWT from an identity provider the org registered>' } (verified) or { source, id } (asserted; honoured only where the platform allows it). On a platform with per-person tokens, the person's own upstream token is used; if they have not authorized yet the call returns consent_required with a consent_url to give them.",
|
|
172
|
+
properties: { assertion: { type: "string" }, source: { type: "string" }, id: { type: "string" } },
|
|
173
|
+
},
|
|
159
174
|
reason: {
|
|
160
175
|
type: "string",
|
|
161
176
|
description: "Optional but strongly encouraged. One sentence on WHY you are making this call. Shown to the human approver if the call is gated. Max 500 characters; plain text.",
|
|
@@ -181,6 +196,11 @@ export const DELETE_PLATFORM_TOOL = {
|
|
|
181
196
|
type: "string",
|
|
182
197
|
description: "Optional. Target a specific connection when a platform has more than one connected. Omit to use the default.",
|
|
183
198
|
},
|
|
199
|
+
caller: {
|
|
200
|
+
type: "object",
|
|
201
|
+
description: "Optional. Who the human is that this call is for, when this agent serves many people: { assertion: '<JWT from an identity provider the org registered>' } (verified) or { source, id } (asserted; honoured only where the platform allows it). On a platform with per-person tokens, the person's own upstream token is used; if they have not authorized yet the call returns consent_required with a consent_url to give them.",
|
|
202
|
+
properties: { assertion: { type: "string" }, source: { type: "string" }, id: { type: "string" } },
|
|
203
|
+
},
|
|
184
204
|
reason: {
|
|
185
205
|
type: "string",
|
|
186
206
|
description: "Optional but strongly encouraged. One sentence on WHY you are deleting this. Shown to the human approver if the call is gated. Max 500 characters; plain text.",
|
|
@@ -191,7 +211,7 @@ export const DELETE_PLATFORM_TOOL = {
|
|
|
191
211
|
};
|
|
192
212
|
export const AGENT_REGISTER_TOOL = {
|
|
193
213
|
name: "agent_register",
|
|
194
|
-
description: "agent_register: Self-register this agent with an owner. No auth required.\nInput: owner_id (string), agent_name (string), requested_scopes (array of {platformId, scopes}).\nReturns: registration_token, poll_url, client_id, scope,
|
|
214
|
+
description: "agent_register: Self-register this agent with an owner. No auth required.\nInput: owner_id (string), agent_name (string), requested_scopes (array of {platformId, scopes}).\nThe agent's RSA keypair is generated on this machine; only the public key is sent and the private key stays here.\nReturns: registration_token, poll_url, client_id, scope, keypair_source, key_stored_locally.\nNext: once the owner approves in the dashboard, call agent_status with the registration_token to install the key.\nAuth: None.",
|
|
195
215
|
inputSchema: {
|
|
196
216
|
type: "object",
|
|
197
217
|
properties: {
|
|
@@ -226,13 +246,15 @@ export const AGENT_REGISTER_TOOL = {
|
|
|
226
246
|
client_id: { type: "string" },
|
|
227
247
|
scope: { type: "string" },
|
|
228
248
|
expires_in: { type: "number" },
|
|
249
|
+
keypair_source: { type: "string", enum: ["client", "server"] },
|
|
250
|
+
key_stored_locally: { type: "boolean" },
|
|
229
251
|
},
|
|
230
252
|
required: ["registration_token"],
|
|
231
253
|
},
|
|
232
254
|
};
|
|
233
255
|
export const AGENT_STATUS_TOOL = {
|
|
234
256
|
name: "agent_status",
|
|
235
|
-
description: "agent_status: Poll registration status using the token from agent_register.\nInput: token (string, required).\nReturns: status (\"
|
|
257
|
+
description: "agent_status: Poll registration status using the token from agent_register.\nInput: token (string, required).\nOn approval it installs the agent's private key on this machine (~/.agentvalet/agents/<agent_id>.key, plus the default identity if none exists) and activates it for this session when the server had no identity yet. The private key is never returned in the result.\nReturns: status (\"pending\"|\"approved\"|\"rejected\"|\"expired\"), agent_id, key_installed, key_path, activated, mcp_config (if approved).\nAuth: None.",
|
|
236
258
|
inputSchema: {
|
|
237
259
|
type: "object",
|
|
238
260
|
properties: {
|
|
@@ -246,8 +268,11 @@ export const AGENT_STATUS_TOOL = {
|
|
|
246
268
|
outputSchema: {
|
|
247
269
|
type: "object",
|
|
248
270
|
properties: {
|
|
249
|
-
status: { type: "string", enum: ["pending_approval", "approved", "rejected"] },
|
|
271
|
+
status: { type: "string", enum: ["pending", "pending_approval", "approved", "rejected", "expired"] },
|
|
250
272
|
agent_id: { type: "string" },
|
|
273
|
+
key_installed: { type: "boolean" },
|
|
274
|
+
key_path: { type: "string" },
|
|
275
|
+
activated: { type: "boolean" },
|
|
251
276
|
mcp_config: { type: "object" },
|
|
252
277
|
},
|
|
253
278
|
required: ["status"],
|
|
@@ -356,9 +381,10 @@ export const LIST_MY_PENDING_ACTIONS_TOOL = {
|
|
|
356
381
|
platform_id: { type: "string" },
|
|
357
382
|
scope: { type: "string" },
|
|
358
383
|
status: { type: "string" },
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
// null until there is something to report (expired, denied, not yet run).
|
|
385
|
+
executed_at: { type: ["string", "null"] },
|
|
386
|
+
result_summary: { type: ["string", "null"] },
|
|
387
|
+
execution_error: { type: ["string", "null"] },
|
|
362
388
|
},
|
|
363
389
|
required: ["approval_id", "status"],
|
|
364
390
|
},
|
package/package.json
CHANGED