@agentvalet/mcp-server 0.2.4 → 0.3.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.js +113 -4
- package/dist/instructions.js +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,12 +49,12 @@ function pendingFirstCallResponse() {
|
|
|
49
49
|
const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
50
50
|
const LIST_PLATFORMS_TOOL = {
|
|
51
51
|
name: "list_platforms",
|
|
52
|
-
description: "list_platforms: List the platforms and permission scopes this agent has access to.\nInput: None.\nReturns: platforms
|
|
52
|
+
description: "list_platforms: List the platforms and permission scopes this agent has access to.\nInput: None.\nReturns: { platforms: [{ platformId, platformName, scopes, requireApproval }], version: \"<hex>\" }.\nVersion: a deterministic hash that only changes when the platform set or scopes change. Cache the value across calls in the same session — only refresh when you suspect platforms have changed (e.g. user mentions a new connection).\nAuth: Bearer JWT.",
|
|
53
53
|
inputSchema: { type: "object", properties: {} },
|
|
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: {
|
|
@@ -221,13 +221,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
221
221
|
isError: true,
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
|
+
const progressToken = request.params._meta?.progressToken;
|
|
224
225
|
return await handleUsePlatform({
|
|
225
226
|
platform: args.platform,
|
|
226
227
|
endpoint: args.endpoint,
|
|
227
228
|
method: args.method,
|
|
228
229
|
scope: args.scope,
|
|
229
230
|
data: args.data,
|
|
230
|
-
});
|
|
231
|
+
}, progressToken);
|
|
231
232
|
}
|
|
232
233
|
if (name === "agent_register") {
|
|
233
234
|
if (!args || typeof args.owner_id !== "string" || typeof args.agent_name !== "string" || !Array.isArray(args.requested_scopes)) {
|
|
@@ -304,7 +305,13 @@ async function handleListPlatforms() {
|
|
|
304
305
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
305
306
|
return { content: [{ type: "text", text: body }] };
|
|
306
307
|
}
|
|
307
|
-
|
|
308
|
+
// Long-poll budget — under Claude Desktop's hardcoded 60s tool timeout
|
|
309
|
+
// (with 10s of safety). After this we return a graceful "queued" message and
|
|
310
|
+
// the action lands in the user's pending-actions list (Layer 4).
|
|
311
|
+
const APPROVAL_POLL_BUDGET_MS = 50_000;
|
|
312
|
+
const APPROVAL_POLL_INTERVAL_MS = 2_000;
|
|
313
|
+
const APPROVAL_PROGRESS_INTERVAL_MS = 5_000;
|
|
314
|
+
async function handleUsePlatform(params, progressToken) {
|
|
308
315
|
if (AGENT_PRIVATE_KEY_RAW === null) {
|
|
309
316
|
await notifyBindSecret();
|
|
310
317
|
return pendingFirstCallResponse();
|
|
@@ -327,10 +334,112 @@ async function handleUsePlatform(params) {
|
|
|
327
334
|
return errorContent(`Network error: ${err instanceof Error ? err.message : err}`);
|
|
328
335
|
}
|
|
329
336
|
const body = await response.text();
|
|
337
|
+
// 202 + approval_id → enter long-poll. The proxy returns this when the
|
|
338
|
+
// owner needs to approve the call. We poll /v1/approvals/:id every ~2s
|
|
339
|
+
// for up to 50s; if the owner approves in time the proxy re-runs the
|
|
340
|
+
// upstream call and we return its result to the agent transparently.
|
|
341
|
+
if (response.status === 202) {
|
|
342
|
+
const parsed = safeJsonParse(body);
|
|
343
|
+
const approvalId = parsed?.approval_id;
|
|
344
|
+
if (approvalId) {
|
|
345
|
+
return await waitForApproval(approvalId, params, progressToken);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
330
348
|
if (!response.ok)
|
|
331
349
|
return errorContent(`Proxy error ${response.status}: ${body}`);
|
|
332
350
|
return { content: [{ type: "text", text: body }] };
|
|
333
351
|
}
|
|
352
|
+
async function waitForApproval(approvalId, originalCall, progressToken) {
|
|
353
|
+
const startedAt = Date.now();
|
|
354
|
+
let lastProgressAt = 0;
|
|
355
|
+
// Initial progress so the user sees activity immediately.
|
|
356
|
+
await sendProgress(progressToken, 0, APPROVAL_POLL_BUDGET_MS, `Owner approval required for ${originalCall.platform}:${originalCall.scope} — waiting…`);
|
|
357
|
+
while (Date.now() - startedAt < APPROVAL_POLL_BUDGET_MS) {
|
|
358
|
+
await sleep(APPROVAL_POLL_INTERVAL_MS);
|
|
359
|
+
const elapsed = Date.now() - startedAt;
|
|
360
|
+
let res;
|
|
361
|
+
try {
|
|
362
|
+
res = await fetchWithAuth(`${PROXY_URL}/v1/approvals/${approvalId}`, { method: "GET" });
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// Transient network error — try again next tick.
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (!res.ok) {
|
|
369
|
+
// 404/410 etc. — treat as terminal. Surface back to the agent.
|
|
370
|
+
const text = await res.text();
|
|
371
|
+
return errorContent(`Approval status error ${res.status}: ${text}`);
|
|
372
|
+
}
|
|
373
|
+
const status = (await res.json());
|
|
374
|
+
if (status.status === "approved" && status.executed_at && status.result) {
|
|
375
|
+
// Re-execution complete. Return the upstream response transparently —
|
|
376
|
+
// the agent sees this as if the original use_platform call just succeeded.
|
|
377
|
+
const r = status.result;
|
|
378
|
+
const summary = `[Owner approved after ${Math.round(elapsed / 1000)}s]\n` +
|
|
379
|
+
JSON.stringify(r.data ?? null);
|
|
380
|
+
// Mirror the non-2xx-from-upstream behaviour of the regular path.
|
|
381
|
+
if (r.status < 200 || r.status >= 300) {
|
|
382
|
+
return errorContent(`Upstream returned ${r.status}: ${JSON.stringify(r.data)}`);
|
|
383
|
+
}
|
|
384
|
+
return { content: [{ type: "text", text: summary }] };
|
|
385
|
+
}
|
|
386
|
+
if (status.status === "denied") {
|
|
387
|
+
return errorContent(`Owner denied this action${status.execution_error ? `: ${status.execution_error}` : "."}`);
|
|
388
|
+
}
|
|
389
|
+
if (status.status === "expired") {
|
|
390
|
+
return errorContent("Approval request expired before the owner responded.");
|
|
391
|
+
}
|
|
392
|
+
if (status.execution_error) {
|
|
393
|
+
// Approved but re-execution failed (network / upstream / agent revoked).
|
|
394
|
+
return errorContent(`Approved but re-execution failed: ${status.execution_error}`);
|
|
395
|
+
}
|
|
396
|
+
// Still pending — emit a progress update at most once per ~5s so the
|
|
397
|
+
// user can see the wait advancing in the Claude Desktop tool UI.
|
|
398
|
+
if (Date.now() - lastProgressAt >= APPROVAL_PROGRESS_INTERVAL_MS) {
|
|
399
|
+
await sendProgress(progressToken, elapsed, APPROVAL_POLL_BUDGET_MS, `Waiting for owner approval — ${Math.round(elapsed / 1000)}s elapsed`);
|
|
400
|
+
lastProgressAt = Date.now();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Timed out — fall through to async-recap path. The action is still
|
|
404
|
+
// queued and will run when the owner approves; the user is notified via
|
|
405
|
+
// push/email at that point. Layer 4's list_my_pending_actions surfaces it.
|
|
406
|
+
return {
|
|
407
|
+
content: [{
|
|
408
|
+
type: "text",
|
|
409
|
+
text: JSON.stringify({
|
|
410
|
+
status: "pending_approval",
|
|
411
|
+
approval_id: approvalId,
|
|
412
|
+
message: `Owner hasn't approved within ${APPROVAL_POLL_BUDGET_MS / 1000}s. ` +
|
|
413
|
+
`The action is queued — your owner will be notified, and you'll see it ` +
|
|
414
|
+
`complete next time we chat (or you can ask me to check pending actions).`,
|
|
415
|
+
}),
|
|
416
|
+
}],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function safeJsonParse(text) {
|
|
420
|
+
try {
|
|
421
|
+
return JSON.parse(text);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function sleep(ms) {
|
|
428
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
429
|
+
}
|
|
430
|
+
async function sendProgress(token, progress, total, message) {
|
|
431
|
+
if (token === undefined)
|
|
432
|
+
return;
|
|
433
|
+
try {
|
|
434
|
+
await server.notification({
|
|
435
|
+
method: "notifications/progress",
|
|
436
|
+
params: { progressToken: token, progress, total, message },
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// Best-effort — never let a failed notification break the call.
|
|
441
|
+
}
|
|
442
|
+
}
|
|
334
443
|
async function handleAgentRegister(args) {
|
|
335
444
|
let response;
|
|
336
445
|
try {
|
package/dist/instructions.js
CHANGED
|
@@ -11,7 +11,8 @@ 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
|
|