MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / dispatchCallTool

Function dispatchCallTool

server/src/training-agent/task-handlers.ts:3587–3893  ·  view source on GitHub ↗
(
    request: { params: { name: string; arguments?: unknown; task?: { ttl?: number } } },
    _extra: unknown,
  )

Source from the content-addressed store, hash-verified

3585 });
3586
3587 async function dispatchCallTool(
3588 request: { params: { name: string; arguments?: unknown; task?: { ttl?: number } } },
3589 _extra: unknown,
3590 ): Promise<{ result: object; flushable: boolean }> {
3591 const { name, arguments: args } = request.params;
3592
3593 // Extract and strip context before passing args to handlers (AdCP requirement:
3594 // echo caller's context object back unchanged in every response).
3595 const rawArgs = (args as Record<string, unknown> | undefined) ?? {};
3596 const { context: callerContext, ...handlerArgs } = rawArgs;
3597
3598 const handler = HANDLER_MAP[name];
3599
3600 if (!handler) {
3601 // Pre-handler validation failures don't touch session state, so flushing
3602 // is a no-op; leaving flushable=true keeps behaviour consistent for
3603 // requests whose handlers DO legitimately mutate before failing.
3604 return { result: adcpError('INVALID_REQUEST', { message: `Unknown tool: ${name}` }, callerContext), flushable: true };
3605 }
3606
3607 const requestedVersion = (handlerArgs as { adcp_major_version?: unknown }).adcp_major_version;
3608 if (
3609 requestedVersion !== undefined
3610 && !(SUPPORTED_MAJOR_VERSIONS as readonly number[]).includes(requestedVersion as number)
3611 ) {
3612 return {
3613 result: adcpError('VERSION_UNSUPPORTED', {
3614 message: `AdCP major version ${String(requestedVersion)} is not supported`,
3615 details: { supported_major_versions: SUPPORTED_MAJOR_VERSIONS },
3616 field: 'adcp_major_version',
3617 }, callerContext),
3618 flushable: true,
3619 };
3620 }
3621
3622 // Check for task-augmented request (explicit `task` field in params).
3623 // Dry-run requests always return synchronously — there's no reason to
3624 // async a dry-run operation, and clients expect immediate results.
3625 const taskField = (request.params as { task?: { ttl?: number } }).task;
3626 const isDryRun = rawArgs.dry_run === true;
3627 const isTaskRequest = taskField !== undefined && !isDryRun;
3628 if (isTaskRequest && !toolSupportsTask(name)) {
3629 throw new Error(`Tool "${name}" does not support task augmentation`);
3630 }
3631
3632 // Idempotency enforcement for mutating tools (#2315, #2346).
3633 // Key presence + format are schema-level requirements; we check them
3634 // before the handler so a malformed key never touches the cache
3635 // (prevents key-format-accepting cache misses from leaking timing).
3636 const authPrincipal = ctx.principal ?? 'anonymous';
3637 // Partition the idempotency cache by caller-stated account scope so the
3638 // shared public sandbox token doesn't pool every buyer into one oracle
3639 // (security.mdx §"three-state response"). An attacker on the same auth
3640 // principal using a different account ref can still cross-probe, but
3641 // callers can already enumerate their own account's keys — so the
3642 // scoping adds no useful probing surface while closing the cross-caller
3643 // leak.
3644 const accountScope = deriveAccountScope(handlerArgs);

Callers 1

Calls 12

adcpErrorFunction · 0.85
toolSupportsTaskFunction · 0.85
scopedPrincipalFunction · 0.85
isMutatingToolFunction · 0.85
validateKeyFormatFunction · 0.85
getIdempotencyStoreFunction · 0.85
handlerFunction · 0.85
hasMethod · 0.80
saveMethod · 0.80
deriveAccountScopeFunction · 0.70
checkMethod · 0.45

Tested by

no test coverage detected