AI tools

Tool factories for OpenAI, Vercel AI SDK, and Claude Agent SDK. Same eight operations, Zod-validated, approval-gated by default. Each is tree-shakeable with its own peer deps.

OpenAI

The files-sdk/openai subpath ships two factories targeting OpenAI directly - one for the native Responses API and one for the OpenAI Agents SDK (@openai/agents). Both wrap the same eight file operations as the Vercel subpath, with the same approval-gating defaults.

openai and @openai/agents are optional peer dependencies - install only the one(s) you use. The subpath requires Zod 4: @openai/agents peer-requires it, and Zod 4's built-in toJSONSchema powers the Responses tool definitions.

Responses API

createResponsesFileTools returns { definitions, execute, needsApproval }. Pass definitions straight to openai.responses.create({ tools }), then call execute(call) on each function_call item in the response output to get a function_call_output ready to push into the next turn's input.

npm install openai zod
import OpenAI from "openai";import { Files } from "files-sdk";import { s3 } from "files-sdk/s3";import { createResponsesFileTools } from "files-sdk/openai";const client = new OpenAI();const files = new Files({ adapter: s3({ bucket: "uploads" }) });const ft = createResponsesFileTools({ files });const input: any[] = [{ role: "user", content: "List my files." }];while (true) {  const res = await client.responses.create({    model: "gpt-4.1",    input,    tools: ft.definitions,  });  const calls = res.output.filter((o) => o.type === "function_call");  if (calls.length === 0) {    console.log(res.output_text);    break;  }  for (const call of calls) {    if (ft.needsApproval(call.name)) {      // surface approval UX, then continue or break    }    input.push(call, await ft.execute(call));  }}

execute returns JSON parse failures and Zod validation errors as the tool's output, so the model can self-correct on the next turn. FilesError from the underlying SDK is rethrown - you decide how to surface it. needsApproval(name) is informational; checking it is the caller's responsibility.

Agents SDK

createAgentsFileTools returns a record of tool() outputs keyed by tool name - spread Object.values() into new Agent({ tools }). Write tools default to needsApproval: true; the Agents SDK runner surfaces an interruption that your program resolves by approving or rejecting the call.

npm install @openai/agents zod
import { Agent, run } from "@openai/agents";import { Files } from "files-sdk";import { s3 } from "files-sdk/s3";import { createAgentsFileTools } from "files-sdk/openai";const files = new Files({ adapter: s3({ bucket: "uploads" }) });const tools = createAgentsFileTools({ files });const agent = new Agent({  instructions: "Help the user manage their files.",  name: "Files agent",  tools: Object.values(tools),});const result = await run(agent, "List my files.");

Errors thrown from execute() are wrapped by the Agents SDK's default errorFunction into a model-visible string - the model sees the message and can self-correct on the next turn. This is the standard Agents-SDK pattern, and differs from the Responses flow where FilesError rethrows.

Approval, read-only, overrides

Both factories accept the same options shape as the Vercel createFileTools: requireApproval (boolean or per-tool record), readOnly (strips writes entirely), and overrides (description, plus strict for Responses or needsApproval for Agents).

// Same shape across both factories.createResponsesFileTools({ files });                       // all writes gated (default)createResponsesFileTools({ files, requireApproval: false }); // disabledcreateResponsesFileTools({  files,  requireApproval: { deleteFile: true, uploadFile: false },});createAgentsFileTools({ files, readOnly: true });// → only listFiles, getFileMetadata, downloadFile, getFileUrl

Vercel AI SDK

The files-sdk/ai-sdk subpath exposes a configured Files instance to the Vercel AI SDK as a set of ready-to-use tools - drop them into generateText, streamText, or any agent and the model can browse, read, and mutate your bucket through the same unified surface as your application code.

Write tools (uploadFile, deleteFile, copyFile, signUploadUrl) require user approval by default - designed for human-in-the-loop agents. Read tools (listFiles, getFileMetadata, downloadFile, getFileUrl) never require approval.

Installation

ai and zod are optional peer dependencies - only install them if you're consuming the files-sdk/ai-sdk subpath.

npm install ai zod

Quick start

Construct a Files instance the same way you would anywhere else, then pass it to createFileTools. The returned object plugs straight into the AI SDK's tools field.

import { Files } from "files-sdk";import { s3 } from "files-sdk/s3";import { createFileTools } from "files-sdk/ai-sdk";import { generateText } from "ai";const files = new Files({  adapter: s3({ bucket: "uploads", region: "us-east-1" }),});const result = await generateText({  model: yourModel,  tools: createFileTools({ files }),  prompt: "Find every CSV under reports/ and summarize the latest one.",});

Approval control

requireApproval accepts a boolean for the all-or-nothing case, or an object keyed by write tool name for fine-grained control. Unspecified entries in the object form default to true, so it's safe to opt-in only the cases you trust.

// All writes require approval (default).createFileTools({ files });// Drop the approval gate entirely.createFileTools({ files, requireApproval: false });// Granular: only the destructive operations need approval.createFileTools({  files,  requireApproval: {    deleteFile: true,    signUploadUrl: true,    uploadFile: false,    copyFile: false,  },});

Read-only mode

Pass readOnly: true to drop every write tool. The model cannot mutate the bucket regardless of how requireApproval is configured - useful for retrieval-style agents that only need to browse, summarize, or hand the user a download URL.

// Strip every write tool. The model can browse but cannot mutate// the bucket regardless of approval configuration.createFileTools({ files, readOnly: true });// → { listFiles, getFileMetadata, downloadFile, getFileUrl }

Tool surface

Eight tools are returned by default - four read, four write. Each one is a thin wrapper around a Files method, so they share the SDK's key validation, normalized errors, and adapter portability.

Overrides

Patch any safe tool() field on a per-tool basis without touching the underlying implementation. Useful for tightening descriptions to your domain, flipping an individual needsApproval, or adding provider-specific providerOptions. execute, inputSchema, and outputSchema are intentionally not overridable.

createFileTools({  files,  overrides: {    listFiles: { description: "List files in the current tenant's bucket" },    deleteFile: { needsApproval: false, title: "Remove file" },  },});

Cherry-picking tools

Each tool factory is also exported individually for fully custom setups - useful when you want to mix AI SDK tools across multiple domains and need full control over the returned object's shape.

import { Files } from "files-sdk";import { listFiles, downloadFile, uploadFile } from "files-sdk/ai-sdk";const files = new Files({ adapter });const tools = {  listFiles: listFiles(files),  downloadFile: downloadFile(files),  uploadFile: uploadFile(files),};

Claude Agent SDK

The files-sdk/claude subpath exposes a configured Files instance to the Claude Agent SDK (@anthropic-ai/claude-agent-sdk, the renamed Claude Code SDK). The Agent SDK consumes tools as an in-process MCP server plus an allowedTools allow-list and a canUseTool approval callback, so createClaudeFileTools returns a bundle of all three - pass them straight into query().

@anthropic-ai/claude-agent-sdk and zod are optional peer dependencies - only install them if you're consuming this subpath.

Installation

npm install @anthropic-ai/claude-agent-sdk zod

Quick start

createClaudeFileTools returns { mcpServers, allowedTools, canUseTool, needsApproval, server, serverName }. The first three slot directly into query()'s options; the rest are escape hatches for callers that want to compose with existing MCP servers or wire their own approval UX.

import { query } from "@anthropic-ai/claude-agent-sdk";import { Files } from "files-sdk";import { s3 } from "files-sdk/s3";import { createClaudeFileTools } from "files-sdk/claude";const files = new Files({ adapter: s3({ bucket: "uploads" }) });const tools = createClaudeFileTools({ files });for await (const message of query({  prompt: "Find every CSV under reports/ and summarize the latest one.",  options: {    mcpServers: tools.mcpServers,    allowedTools: tools.allowedTools,    canUseTool: tools.canUseTool,  },})) {  // handle messages}

Approval control

The bundled canUseTool denies any tool whose needsApproval resolves to true with a "requires approval" message, and allows everything else. requireApproval accepts a boolean for the all-or-nothing case, or an object keyed by write tool name for fine-grained control.

// All writes require approval (default) - denied by the bundled canUseTool.createClaudeFileTools({ files });// Disable the approval gate entirely.createClaudeFileTools({ files, requireApproval: false });// Granular: only destructive operations need approval.createClaudeFileTools({  files,  requireApproval: {    deleteFile: true,    signUploadUrl: true,    uploadFile: false,    copyFile: false,  },});

For real human-in-the-loop UX, compose your own canUseTool on top of tools.needsApproval(). The helper accepts both bare names ("uploadFile") and the MCP-prefixed form ("mcp__files__uploadFile") that the SDK passes in, so the callback is symmetric whichever shape you receive.

import type { CanUseTool } from "@anthropic-ai/claude-agent-sdk";const tools = createClaudeFileTools({ files });// Compose your own canUseTool - needsApproval accepts both bare// names ("uploadFile") and the mcp-prefixed form passed in by the SDK.const canUseTool: CanUseTool = async (name, input) => {  if (tools.needsApproval(name)) {    const approved = await askUser(name, input);    return approved      ? { behavior: "allow", updatedInput: input }      : { behavior: "deny", message: "User rejected the call." };  }  return { behavior: "allow", updatedInput: input };};

Read-only mode

Pass readOnly: true to drop every write tool from the bundled MCP server. The model cannot mutate the bucket regardless of how requireApproval is configured.

// Strip every write tool. The model can browse but cannot mutate// the bucket regardless of approval configuration.createClaudeFileTools({ files, readOnly: true });// allowedTools → ["mcp__files__listFiles", "mcp__files__getFileMetadata",//                 "mcp__files__downloadFile", "mcp__files__getFileUrl"]

Server name

Claude addresses each MCP tool as mcp__<server-name>__<tool-name>. The default server name is "files"; override it via serverName when you need to namespace alongside another MCP server or just prefer a different label in transcripts.

// Override the MCP server name - affects the mcp__<server>__<tool>// prefix the model sees, and the mcpServers map key.const tools = createClaudeFileTools({ files, serverName: "storage" });// tools.allowedTools → ["mcp__storage__listFiles", ...]// tools.mcpServers   → { storage: <McpSdkServerConfigWithInstance> }

Cherry-picking tools

Each tool factory is exported individually as a SdkMcpToolDefinition - bundle them into your own createSdkMcpServer call when you want full control over the MCP server shape or want to mix files-sdk tools with your own.

import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";import { Files } from "files-sdk";import {  claudeDownloadFile,  claudeListFiles,  claudeUploadFile,} from "files-sdk/claude";const files = new Files({ adapter });// Compose your own MCP server with just the tools you want.const server = createSdkMcpServer({  name: "files",  version: "1.0.0",  tools: [claudeListFiles(files), claudeDownloadFile(files), claudeUploadFile(files)],});