@agentvalet/mcp-server 0.2.5 → 0.3.1
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.js +138 -3
- package/dist/instructions.js +6 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,7 +54,7 @@ const LIST_PLATFORMS_TOOL = {
|
|
|
54
54
|
};
|
|
55
55
|
const USE_PLATFORM_TOOL = {
|
|
56
56
|
name: "use_platform",
|
|
57
|
-
description: "use_platform: Call an external platform API (Airtable, GitHub, Slack, etc.) through the AgentValet proxy.\nInput: platform (string), endpoint (string), method (GET|POST|PUT|PATCH|DELETE), scope (string), data (object, optional).\nReturns: upstream API response body.\nAuth: Bearer JWT.",
|
|
57
|
+
description: "use_platform: Call an external platform API (Airtable, GitHub, Slack, etc.) through the AgentValet proxy.\nInput: platform (string), endpoint (string), method (GET|POST|PUT|PATCH|DELETE), scope (string), data (object, optional).\nReturns: upstream API response body. May take up to 50 seconds when the action requires owner approval — the call will block while we wait, then return the approved result transparently. If approval doesn't land in time, returns a `pending_approval` envelope and the action runs asynchronously; the user is notified when it completes.\nAuth: Bearer JWT.",
|
|
58
58
|
inputSchema: {
|
|
59
59
|
type: "object",
|
|
60
60
|
properties: {
|
|
@@ -184,6 +184,11 @@ const REPORT_SELF_DIAGNOSTIC_TOOL = {
|
|
|
184
184
|
required: ["severity", "message"],
|
|
185
185
|
},
|
|
186
186
|
};
|
|
187
|
+
const LIST_MY_PENDING_ACTIONS_TOOL = {
|
|
188
|
+
name: "list_my_pending_actions",
|
|
189
|
+
description: "list_my_pending_actions: Returns this agent's currently-pending approval requests AND any that completed in the last 24 hours. Use this at session start when the user mentions an earlier action, or when use_platform's long-poll timed out and the user comes back asking what happened.\nInput: None.\nReturns: { pending: [{approval_id, platform_id, scope, created_at, expires_at}], recently_completed: [{approval_id, platform_id, scope, status, executed_at, result_summary, execution_error}] }.\nAuth: Bearer agent JWT (sent automatically).",
|
|
190
|
+
inputSchema: { type: "object", properties: {} },
|
|
191
|
+
};
|
|
187
192
|
// TODO: intent_resolve tool — planned for future release
|
|
188
193
|
// ---------------------------------------------------------------------------
|
|
189
194
|
// MCP server setup
|
|
@@ -197,6 +202,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
197
202
|
AGENT_STATUS_TOOL,
|
|
198
203
|
AUTHZEN_EVALUATE_TOOL,
|
|
199
204
|
REPORT_SELF_DIAGNOSTIC_TOOL,
|
|
205
|
+
LIST_MY_PENDING_ACTIONS_TOOL,
|
|
200
206
|
],
|
|
201
207
|
}));
|
|
202
208
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -221,13 +227,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
221
227
|
isError: true,
|
|
222
228
|
};
|
|
223
229
|
}
|
|
230
|
+
const progressToken = request.params._meta?.progressToken;
|
|
224
231
|
return await handleUsePlatform({
|
|
225
232
|
platform: args.platform,
|
|
226
233
|
endpoint: args.endpoint,
|
|
227
234
|
method: args.method,
|
|
228
235
|
scope: args.scope,
|
|
229
236
|
data: args.data,
|
|
230
|
-
});
|
|
237
|
+
}, progressToken);
|
|
231
238
|
}
|
|
232
239
|
if (name === "agent_register") {
|
|
233
240
|
if (!args || typeof args.owner_id !== "string" || typeof args.agent_name !== "string" || !Array.isArray(args.requested_scopes)) {
|
|
@@ -247,6 +254,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
247
254
|
}
|
|
248
255
|
return await handleAuthzenEvaluate(args.platform_id, args.scope);
|
|
249
256
|
}
|
|
257
|
+
if (name === "list_my_pending_actions") {
|
|
258
|
+
return await handleListMyPendingActions();
|
|
259
|
+
}
|
|
250
260
|
if (name === "report_self_diagnostic") {
|
|
251
261
|
if (!args || typeof args.severity !== "string" || typeof args.message !== "string") {
|
|
252
262
|
return errorContent("Invalid or missing arguments: severity and message are required");
|
|
@@ -304,7 +314,13 @@ async function handleListPlatforms() {
|
|
|
304
314
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
305
315
|
return { content: [{ type: "text", text: body }] };
|
|
306
316
|
}
|
|
307
|
-
|
|
317
|
+
// Long-poll budget — under Claude Desktop's hardcoded 60s tool timeout
|
|
318
|
+
// (with 10s of safety). After this we return a graceful "queued" message and
|
|
319
|
+
// the action lands in the user's pending-actions list (Layer 4).
|
|
320
|
+
const APPROVAL_POLL_BUDGET_MS = 50_000;
|
|
321
|
+
const APPROVAL_POLL_INTERVAL_MS = 2_000;
|
|
322
|
+
const APPROVAL_PROGRESS_INTERVAL_MS = 5_000;
|
|
323
|
+
async function handleUsePlatform(params, progressToken) {
|
|
308
324
|
if (AGENT_PRIVATE_KEY_RAW === null) {
|
|
309
325
|
await notifyBindSecret();
|
|
310
326
|
return pendingFirstCallResponse();
|
|
@@ -327,10 +343,112 @@ async function handleUsePlatform(params) {
|
|
|
327
343
|
return errorContent(`Network error: ${err instanceof Error ? err.message : err}`);
|
|
328
344
|
}
|
|
329
345
|
const body = await response.text();
|
|
346
|
+
// 202 + approval_id → enter long-poll. The proxy returns this when the
|
|
347
|
+
// owner needs to approve the call. We poll /v1/approvals/:id every ~2s
|
|
348
|
+
// for up to 50s; if the owner approves in time the proxy re-runs the
|
|
349
|
+
// upstream call and we return its result to the agent transparently.
|
|
350
|
+
if (response.status === 202) {
|
|
351
|
+
const parsed = safeJsonParse(body);
|
|
352
|
+
const approvalId = parsed?.approval_id;
|
|
353
|
+
if (approvalId) {
|
|
354
|
+
return await waitForApproval(approvalId, params, progressToken);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
330
357
|
if (!response.ok)
|
|
331
358
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
332
359
|
return { content: [{ type: "text", text: body }] };
|
|
333
360
|
}
|
|
361
|
+
async function waitForApproval(approvalId, originalCall, progressToken) {
|
|
362
|
+
const startedAt = Date.now();
|
|
363
|
+
let lastProgressAt = 0;
|
|
364
|
+
// Initial progress so the user sees activity immediately.
|
|
365
|
+
await sendProgress(progressToken, 0, APPROVAL_POLL_BUDGET_MS, `Owner approval required for ${originalCall.platform}:${originalCall.scope} — waiting…`);
|
|
366
|
+
while (Date.now() - startedAt < APPROVAL_POLL_BUDGET_MS) {
|
|
367
|
+
await sleep(APPROVAL_POLL_INTERVAL_MS);
|
|
368
|
+
const elapsed = Date.now() - startedAt;
|
|
369
|
+
let res;
|
|
370
|
+
try {
|
|
371
|
+
res = await fetchWithAuth(`${PROXY_URL}/v1/approvals/${approvalId}`, { method: "GET" });
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Transient network error — try again next tick.
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!res.ok) {
|
|
378
|
+
// 404/410 etc. — treat as terminal. Surface back to the agent.
|
|
379
|
+
const text = await res.text();
|
|
380
|
+
return errorContent(`Approval status error ${res.status}: ${text}`);
|
|
381
|
+
}
|
|
382
|
+
const status = (await res.json());
|
|
383
|
+
if (status.status === "approved" && status.executed_at && status.result) {
|
|
384
|
+
// Re-execution complete. Return the upstream response transparently —
|
|
385
|
+
// the agent sees this as if the original use_platform call just succeeded.
|
|
386
|
+
const r = status.result;
|
|
387
|
+
const summary = `[Owner approved after ${Math.round(elapsed / 1000)}s]\n` +
|
|
388
|
+
JSON.stringify(r.data ?? null);
|
|
389
|
+
// Mirror the non-2xx-from-upstream behaviour of the regular path.
|
|
390
|
+
if (r.status < 200 || r.status >= 300) {
|
|
391
|
+
return errorContent(`Upstream returned ${r.status}: ${JSON.stringify(r.data)}`);
|
|
392
|
+
}
|
|
393
|
+
return { content: [{ type: "text", text: summary }] };
|
|
394
|
+
}
|
|
395
|
+
if (status.status === "denied") {
|
|
396
|
+
return errorContent(`Owner denied this action${status.execution_error ? `: ${status.execution_error}` : "."}`);
|
|
397
|
+
}
|
|
398
|
+
if (status.status === "expired") {
|
|
399
|
+
return errorContent("Approval request expired before the owner responded.");
|
|
400
|
+
}
|
|
401
|
+
if (status.execution_error) {
|
|
402
|
+
// Approved but re-execution failed (network / upstream / agent revoked).
|
|
403
|
+
return errorContent(`Approved but re-execution failed: ${status.execution_error}`);
|
|
404
|
+
}
|
|
405
|
+
// Still pending — emit a progress update at most once per ~5s so the
|
|
406
|
+
// user can see the wait advancing in the Claude Desktop tool UI.
|
|
407
|
+
if (Date.now() - lastProgressAt >= APPROVAL_PROGRESS_INTERVAL_MS) {
|
|
408
|
+
await sendProgress(progressToken, elapsed, APPROVAL_POLL_BUDGET_MS, `Waiting for owner approval — ${Math.round(elapsed / 1000)}s elapsed`);
|
|
409
|
+
lastProgressAt = Date.now();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Timed out — fall through to async-recap path. The action is still
|
|
413
|
+
// queued and will run when the owner approves; the user is notified via
|
|
414
|
+
// push/email at that point. Layer 4's list_my_pending_actions surfaces it.
|
|
415
|
+
return {
|
|
416
|
+
content: [{
|
|
417
|
+
type: "text",
|
|
418
|
+
text: JSON.stringify({
|
|
419
|
+
status: "pending_approval",
|
|
420
|
+
approval_id: approvalId,
|
|
421
|
+
message: `Owner hasn't approved within ${APPROVAL_POLL_BUDGET_MS / 1000}s. ` +
|
|
422
|
+
`The action is queued — your owner will be notified, and you'll see it ` +
|
|
423
|
+
`complete next time we chat (or you can ask me to check pending actions).`,
|
|
424
|
+
}),
|
|
425
|
+
}],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function safeJsonParse(text) {
|
|
429
|
+
try {
|
|
430
|
+
return JSON.parse(text);
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function sleep(ms) {
|
|
437
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
438
|
+
}
|
|
439
|
+
async function sendProgress(token, progress, total, message) {
|
|
440
|
+
if (token === undefined)
|
|
441
|
+
return;
|
|
442
|
+
try {
|
|
443
|
+
await server.notification({
|
|
444
|
+
method: "notifications/progress",
|
|
445
|
+
params: { progressToken: token, progress, total, message },
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// Best-effort — never let a failed notification break the call.
|
|
450
|
+
}
|
|
451
|
+
}
|
|
334
452
|
async function handleAgentRegister(args) {
|
|
335
453
|
let response;
|
|
336
454
|
try {
|
|
@@ -385,6 +503,23 @@ async function handleAuthzenEvaluate(platformId, scope) {
|
|
|
385
503
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
386
504
|
return { content: [{ type: "text", text: body }] };
|
|
387
505
|
}
|
|
506
|
+
async function handleListMyPendingActions() {
|
|
507
|
+
if (AGENT_PRIVATE_KEY_RAW === null) {
|
|
508
|
+
await notifyBindSecret();
|
|
509
|
+
return pendingFirstCallResponse();
|
|
510
|
+
}
|
|
511
|
+
let response;
|
|
512
|
+
try {
|
|
513
|
+
response = await fetchWithAuth(`${PROXY_URL}/v1/agents/me/pending-actions`, { method: "GET" });
|
|
514
|
+
}
|
|
515
|
+
catch (err) {
|
|
516
|
+
return errorContent(`Network error: ${err instanceof Error ? err.message : err}`);
|
|
517
|
+
}
|
|
518
|
+
const text = await response.text();
|
|
519
|
+
if (!response.ok)
|
|
520
|
+
return errorContent(`Proxy error ${response.status}: ${text}`);
|
|
521
|
+
return { content: [{ type: "text", text }] };
|
|
522
|
+
}
|
|
388
523
|
async function handleReportSelfDiagnostic(args) {
|
|
389
524
|
if (AGENT_PRIVATE_KEY_RAW === null) {
|
|
390
525
|
await notifyBindSecret();
|
package/dist/instructions.js
CHANGED
|
@@ -11,8 +11,12 @@ Tool selection:
|
|
|
11
11
|
3. Call \`use_platform\` with the exact platform, endpoint, method, and scope returned by \`list_platforms\`.
|
|
12
12
|
|
|
13
13
|
Response handling:
|
|
14
|
-
-
|
|
14
|
+
- \`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.
|
|
15
|
+
- 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 — duplicate calls will queue duplicate approvals.
|
|
15
16
|
- Do not retry a denied call with a different scope.
|
|
16
17
|
- 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.
|
|
17
18
|
|
|
18
|
-
Read scopes are auto-approved. Write scopes may require approval. Destructive scopes always require approval
|
|
19
|
+
Read scopes are auto-approved. Write scopes may require approval. Destructive scopes always require approval.
|
|
20
|
+
|
|
21
|
+
Catching up on async actions:
|
|
22
|
+
- If the user mentions a previous action ("did the Slack post go through?", "what happened to that earlier request?") OR if the previous \`use_platform\` returned a \`pending_approval\` envelope, call \`list_my_pending_actions\` to surface what's pending and what's recently completed. Tell the user the result naturally — don't dump the JSON.`;
|