Getting started
Tools for agents
Fifteen minutes from nothing to an agent calling a capability you own. You end with a project running on your laptop, one tool in it, and Claude, Cursor or Copilot connected to it.
1. Scaffold a project
You need Bun 1.1 or later; the craft CLI runs on it. Installation has the other package managers and the manual route.
bunx create-routecraft my-tools
cd my-tools
The project has one capability folder, capabilities/hello-world, with two routes in it: greet, which looks a user up over HTTP and returns a greeting, and hello-world, which calls greet once at start so you can see the project run.
2. Run it
bun run start
The log ends with Hello, Leanne Graham!. That was hello-world calling greet through the in-process door, direct(), with the user id validated against the capability's input schema before any code ran. Stop it with Ctrl-C.
bun run test
The capability's own test mocks fetch, runs the same two routes, and asserts the greeting. It is the shape to copy for every capability you write.
3. Connect your client
greet has a second door: mcp(). The project's craft.config.ts serves every capability with that source as an MCP tool over stdio, so a client spawns the project and talks to it on its standard streams. No port, no credential. Run the client from the project folder.
Claude Code, from the project folder:
claude mcp add my-tools -- bunx craft start --log-file craft.log
The log goes to a file because standard output is the protocol. Cursor, Claude Desktop and VS Code take the same command in JSON, with absolute paths because they do not start the server from the project folder: Expose to an agent has each client's file.
4. Call it
Ask the client: greet user 1. It reads the tool's title, description and input schema, which are the .title(), .description() and .input() on the route, decides to call greet with { "userId": 1 }, and gets Hello, Leanne Graham! back. Ask for user "one" and the schema refuses it before your code runs; the client is told which field was wrong.
That is the whole mechanism. The agent never held a credential for the user service and never saw a URL. It pressed a tool you built.
5. Make it yours
Copy capabilities/hello-world, rename it, and change what it does. The one rule: route.ts is the capability's public surface and the only file another capability may import.
// capabilities/find-overdue-invoices/route.ts
import { craft, direct, log } from "@routecraft/routecraft";
import { mcp } from "@routecraft/ai";
import { z } from "zod";
export default craft()
.id("find-overdue-invoices")
.title("Find overdue invoices")
.description("List invoices more than the given number of days overdue")
.input({ body: z.object({ days: z.number().int().min(1) }) })
.from(direct(), mcp())
.transform(({ days }) => ({ days, invoices: [] }))
.to(log());
craft start discovers the new folder on its own. Give the route an http() source and it is also an endpoint; a cron() source and it also runs on a schedule. Same capability, same schema, every door.
6. What comes next
- Share it with your team. Run the project always on, serve MCP over HTTP behind authentication, on service credentials no person holds. Local harness, team harness is the step; Expose to an agent has the HTTP transport and every client.
- Decide who may call it. Credentials and identity is the model;
authorizeis the operation. - Reach it the other ways. Doors and triggers covers the CLI, HTTP and what starts a capability when nobody asks.
An agent of your own
The other first project: a harness you talk to from your editor.
Capabilities
Everything a route can be.
MCP tool
A copyable capability exposed as an MCP tool, and one that calls a remote MCP server.