> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Standalone MCP Tools in Anvil JS via server/tools/

> Add tool files to server/tools/ to expose MCP-callable functions that aren't tied to any HTTP route — served by the same anvil mcp command.

Standalone tools let you expose logic to MCP clients that doesn't belong on any HTTP route. Drop a file into `server/tools/` and `anvil mcp` picks it up automatically — no registration, no config change needed.

## File shape

Every file in `server/tools/` must default-export a function. Alongside the default export, provide an `inputSchema` and a `description`:

```ts theme={null}
// server/tools/wordCount.ts
import { z } from 'zod';

export const description = 'Count the words in a piece of text';
export const inputSchema = z.object({ text: z.string() });

export default async function wordCount(args: { text: string }) {
  const words = args.text.trim().split(/\s+/).filter(Boolean);
  return { words: words.length, characters: args.text.length };
}
```

<ResponseField name="default" type="function" required>
  The tool implementation. Receives the validated arguments as its first argument and may return any serializable value. Async functions are supported.
</ResponseField>

<ResponseField name="inputSchema" type="z.ZodObject">
  Describes the arguments the tool accepts. Pass a Zod object and Anvil converts it to JSON Schema automatically. When omitted (or when the export is not a Zod schema), Anvil falls back to an empty schema — so always export a Zod object to get input validation and accurate schema advertisement.
</ResponseField>

<ResponseField name="description" type="string">
  A plain-English description shown to MCP clients and AI agents. You can also export `meta.description` if you prefer to group metadata together.
</ResponseField>

<ResponseField name="name" type="string">
  An explicit tool name. When omitted, Anvil uses the filename without its extension (e.g. `wordCount.ts` → `wordCount`). Must match `[a-zA-Z0-9_-]{1,64}`.
</ResponseField>

## Multi-parameter tools

Tools can accept any number of inputs. Define them all in the Zod object:

```ts theme={null}
// server/tools/add.ts
import { z } from 'zod';

export const description = 'Add two numbers';
export const inputSchema = z.object({ a: z.number(), b: z.number() });

export default async function add(args: { a: number; b: number }) {
  return { sum: args.a + args.b };
}
```

When an MCP client calls this tool, Anvil validates the incoming arguments against the Zod schema before your function runs — any constraint violations are returned as a graceful tool error, not a protocol crash.

## Generating the input schema dynamically

If you need constructs that Zod can't express losslessly — or you're generating tool definitions programmatically — build the Zod schema at module load time rather than inlining it:

```ts theme={null}
// server/tools/search.ts
import { z } from 'zod';

export const description = 'Full-text search over the product catalog';

const filtersSchema = z.object({
  category: z.string().optional(),
  inStock: z.boolean().optional(),
});

export const inputSchema = z.object({
  query: z.string().min(1),
  limit: z.number().int().min(1).max(100).default(10),
  filters: filtersSchema.optional(),
});

export default async function search(args: {
  query: string;
  limit?: number;
  filters?: { category?: string; inStock?: boolean };
}) {
  // ... implementation
  return { results: [] };
}
```

<Note>
  Always export a Zod object as `inputSchema`. Anvil uses the Zod schema both to advertise the tool's shape to MCP clients (via JSON Schema conversion) and to validate arguments at call time. If `inputSchema` is absent or not a Zod schema, Anvil falls back to an empty schema and skips validation.
</Note>

<Warning>
  Files whose names start with `_` are ignored by the tool scanner. Use this convention for shared helpers you want to co-locate in `server/tools/` without exposing them as tools.
</Warning>

## How tools are served

When `anvil mcp` starts, it scans `server/tools/` for all `.ts`, `.mts`, `.js`, and `.mjs` files (recursive, sorted alphabetically, excluding `_`-prefixed files and `.d.ts` declaration files). Each valid file becomes one MCP tool.

Standalone tools are merged with route-derived tools into a single toolset. Tool names must be unique across both sources — a collision between a file in `server/tools/` and a route with the same derived name is a hard startup error.

```bash theme={null}
anvil mcp
# [anvil] mcp (Streamable HTTP) on http://localhost:3100/mcp
# [anvil] 3 tool(s): get_users_by_id, wordCount, add
```

<Tip>
  Keep `server/tools/` files focused: one exported function per file, co-located with its schema. This makes each tool easy to test in isolation and keeps the MCP surface easy to audit.
</Tip>
