Skip to main content
Wildo.ai Coming soon

Agent protocols

Let assistants attach files without carrying the bytes

An assistant can request a short-lived upload link for a file field, then attach the uploaded file to a record. The document’s bytes travel through the upload path rather than through tool-call arguments.

A file is uploaded through a scoped grant before the application operation uses it.

Let assistants attach files without carrying the bytes

An assistant can request a short-lived upload link for a file field, then attach the uploaded file to a record. The document’s bytes travel through the upload path rather than through tool-call arguments.

The same flow supports an assistant that can upload a local file and one that needs the person to choose it in a browser.

Example — Attach a receipt to an expense

The assistant requests the receipt upload link. After the person uploads it, the assistant uses the returned file identifier when creating the expense.

For engineers

The engine derives an upload-grant tool for each user-uploadable file field on an exposed create or update operation. Generated-document fields do not acquire a user upload tool. No second hand-authored MCP operation is needed.

Discover the derived name and its schema in tools/list. Supply the existing record ID for an update, and optionally the declared name, MIME type, size and requested lifetime. These file facts let the engine reject an impossible upload before issuing a link.

Request, transfer, then save

StageWhat the integration usesResult
Request permission to uploadDiscovered upload-grant tool and argumentsuploadCommand, uploadPageUrl and statusUrl
Transfer the actual fileReturned command, or browser upload pageAn uploaded file identifier
Save the business recordParent operation with the field’s fileId or fileIdsThe attachment becomes part of the record

Reuse the discovered contracts

In an initialized, authenticated MCP client, first call tools/list and select the derived grant tool for the intended field and its parent UPDATE tool. Use the actual names and record-ID property from those descriptors. This illustrative client function takes those selections as arguments; it does not assume a universal tool name or an id field.

async function requestFileHandoff({ client, grantTool, grantArguments }) {
  const decode = (response) => {
    if (response.isError) throw new Error('MCP tool refused the request');
    if (response.structuredContent !== undefined) return response.structuredContent;
    const text = response.content.find((item) => item.type === 'text')?.text;
    if (!text) throw new Error('MCP tool returned no JSON result');
    return JSON.parse(text);
  };
  const grant = decode(await client.callTool({
    name: grantTool.name,
    arguments: {
      ...grantArguments,
      expiresInMinutes: 10,
    },
  }));

  return {
    uploadPageUrl: grant.uploadPageUrl,
    uploadCommand: grant.uploadCommand,
    async finish({ parentTool, parentArguments, fieldName, multiple }) {
      const response = await fetch(grant.statusUrl);
      if (!response.ok) throw new Error(`Status HTTP ${response.status}`);
      const status = await response.json();
      if (status.state !== 'redeemed' || !status.file?.fileId) {
        throw new Error('Wait for the file upload before saving');
      }
      const value = {
        ...(multiple ? { fileIds: [status.file.fileId] } : { fileId: status.file.fileId }),
        updatedAt: new Date().toISOString(),
      };
      return decode(await client.callTool({
        name: parentTool.name,
        arguments: { ...parentArguments, [fieldName]: value },
      }));
    },
  };
}

For an UPDATE, both grantArguments and parentArguments must identify the same record using the property advertised by their tool schemas. fieldName and multiple come from the declared file field and parent input schema. Supply any other required parent arguments. The example replaces that field with one file; preserve existing IDs for an append workflow.

Show the returned page to the person, or follow the returned upload command locally. Call finish only after the upload; a pending result is a reason to wait, not to save an invented ID. Scanning and the ordinary parent write can still refuse attachment. The shared upload recipe shows the actual multipart transfer, status response and final record read-back. Keep token-bearing handoff/status URLs out of logs.

The public upload origin comes from backend configuration. Configure that public URL correctly so the returned links can be reached outside the server. The grant authorizes one upload to the selected field; it does not replace the parent operation’s permission check. If it expires, request a new grant rather than fabricating a file ID.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.