# Add tools with TypeScript

Speakeasy Functions are compact code units that represent LLM tools. Write them in TypeScript, deploy them to the platform, and expose them to language models through MCP servers. Once deployed, Functions behave like any other tool on the platform, so they can be hosted as MCP servers, combined with tools from other sources on a single server, or used to build multi-tool workflows. Deployed functions appear on the **Sources** tab of **MCP Gateway > MCP** in the dashboard.

Unlike OpenAPI-sourced tools, which wrap existing API endpoints, functions run custom code in isolated environments. A function can call multiple APIs, connect to remote databases over TCP or HTTP, and transform data with third-party libraries. Function-sourced tools suit:

- **Custom business logic:** Calculations, data processing, or decision-making that doesn't map to a single API endpoint, such as aggregating data from multiple sources
- **Data transformations:** Filtering, enriching, or summarizing data before returning it to the LLM
- **Multi-step workflows:** Orchestrating several API calls or operations in a single tool
- **Services without an OpenAPI document:** Integrating third-party services or databases that no OpenAPI document describes
- **Control flow:** Conditional logic, iteration, and other control flow that a declarative API specification can't express

```typescript filename="gram.ts"
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";

const gram = new Gram().tool({
  name: "greet",
  description: "Greet someone special",
  inputSchema: { name: z.string() },
  async execute(ctx, input) {
    return ctx.json({ message: `Hello, ${input.name}!` });
  },
});

export default gram;
```

The steps below also appear in the dashboard: go to **MCP Gateway > MCP**, click **Add new**, and choose **Write custom code** under **Advanced**.

## Access requirements

> Pushing functions with the `gram` CLI and opening the **Write custom code** page require the `project:write` scope. Building an MCP server from the deployed functions requires the `mcp:write` scope. The default [Admin role](/docs/ai-control-plane/org-admin/roles-and-permissions) includes both. The **Write custom code** option is shown only when functions are enabled for the organization; the CLI commands below work regardless.

## Prerequisites

This guide assumes:

- An account and project exist (see [Getting started](/docs/ai-control-plane/getting-started))
- Node.js version 22.18.0 or later is installed

## Step 1: Create a project

Scaffold a new project:

```bash
npm create @gram-ai/function@latest
```

The scaffolder prompts for:

- **Framework:** **Gram Functions** (the simplest path, batteries included) or the official **Model Context Protocol SDK** for advanced use cases
- Project name, directory, and git initialization
- Dependency installation
- **CLI installation:** The `gram` CLI is required to deploy. The scaffolder offers to install it and run `gram auth` to authenticate

To skip the framework prompt and scaffold directly onto the Functions Framework, pass a template (`gram` or `mcp`):

```bash
npm create @gram-ai/function@latest -- --template gram
```

The project contains `src/gram.ts` (the tools) and `src/server.ts` (a local MCP server for testing):

```bash
└── src
   ├── gram.ts   # Edit me!
   └── server.ts
└── package.json
└── README.md
```

## Step 2: Write tools

Open `src/gram.ts` and define tools, as many as needed:

```typescript filename="gram.ts"
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";

const gram = new Gram().tool({
  name: "greet",
  description: "Greet someone special",
  inputSchema: { name: z.string() },
  async execute(ctx, input) {
    return ctx.json({ message: `Hello, ${input.name}!` });
  },
});

export default gram;
```

Tools can be written with the [Functions Framework](/docs/ai-control-plane/mcp-gateway/building-servers/functions/functions-framework) or the official [MCP SDK](/docs/ai-control-plane/mcp-gateway/building-servers/functions/mcp-sdk). The Functions Framework is the more lightweight way to write tools, and MCP SDK support is available for teams that prefer the official SDK.

> **Tools, not servers**
> These are tools, not a finished MCP server. After deploying, tools can be
> sliced into multiple MCP servers or combined with tools from other sources.

Test locally before deploying. Running `npm run dev` starts the project under the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector), so tools can be called interactively from the browser.

## Step 3: Build and deploy

```bash
npm run build
npm run push
```

Build bundles the functions. Push stages the bundle and creates a deployment, running the `gram` CLI commands `gram stage function` and `gram push` under the hood. The CLI prints a link to the deployment page. Once the deployment completes, the functions source and its tools appear on the **Sources** tab of **MCP Gateway > MCP** in the dashboard. Deployment history and retries live on the [Deployments](/docs/ai-control-plane/mcp-gateway/building-servers/deployments) tab, and the [Build and deploy](/docs/ai-control-plane/mcp-gateway/building-servers/functions/build-deploy) guide covers the process in full.

## Step 4: Create an MCP server

Open the deployed function on the **Sources** tab and click **Build a server**, or go to **MCP Gateway > MCP**, click **Add new**, and choose **From an existing source** under **Advanced**. Pick the function, enter a **Server name**, and click **Create**. Every tool the function produced is added to the new server, and the dashboard opens the server's **Tools** tab, where tools from other sources can be added alongside them. The server's detail page links to a hosted installation page with per-client setup instructions. See [Authentication](/docs/ai-control-plane/mcp-gateway/access) for visibility, [Team access](/docs/ai-control-plane/mcp-gateway/access/team-access) for who can connect, and [Distribution](/docs/ai-control-plane/mcp-gateway#distribution) for publishing.

## Functions vs MCP servers

Functions provide a way to create individual tools for use in MCP servers. Creating tools is not the same as creating MCP servers.

The key distinction:

- **Functions:** Individual tools that represent specific capabilities or operations
- **MCP servers:** Collections of tools combined into a single server that LLM clients can access

Once a tool exists, whether it came from Functions or OpenAPI, combine it with other tools into one or many MCP servers. Functions can therefore be split across multiple projects, kept in a single file, or organized however a workflow requires.

[MCP servers](/docs/ai-control-plane/mcp-gateway#tools-on-a-server) are assembled from these tools later in the dashboard.

## Bundle structure

The scaffolded project produces a function bundle when it builds, and the [Functions Framework](/docs/ai-control-plane/mcp-gateway/building-servers/functions/functions-framework) generates everything in it. A bundle can also be written by hand. At its core, a function bundle is a zip file containing two files.

The `manifest.json` file describes the tools:

```json filename="manifest.json"
{
  "version": "0.0.0",
  "tools": [
    {
      "name": "add",
      "description": "Add two numbers",
      "inputSchema": {
        "type": "object",
        "properties": {
          "a": { "type": "number" },
          "b": { "type": "number" }
        },
        "required": ["a", "b"]
      }
    },
    {
      "name": "square_root",
      "description": "Calculate the square root of a number",
      "inputSchema": {
        "type": "object",
        "properties": {
          "a": { "type": "number" }
        },
        "required": ["a"]
      }
    }
  ]
}
```

The manifest includes each tool's name, description, and JSON Schema for input validation. It can also declare required environment variables, [tags](/docs/ai-control-plane/mcp-gateway/building-servers/functions/tool-tags), and MCP [tool annotations](/docs/ai-control-plane/mcp-gateway/building-servers/functions/tool-annotations) such as the read-only and destructive hints.

The `functions.js` file is bundled JavaScript that exports a `handleToolCall` function:

```javascript filename="functions.js"
export async function handleToolCall({ name, input }) {
  switch (name) {
    case "add":
      return json({ value: input.a + input.b });
    case "square_root":
      return json({ value: Math.sqrt(input.a) });
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}
```

When the zip file is uploaded, the platform uses the manifest to expose the tools and invokes `handleToolCall` to execute them. Functions run on a managed runtime. The supported runtimes are Node.js 22 (`nodejs:22`), Node.js 24 (`nodejs:24`), and Python 3.12 (`python:3.12`).

> **Note**
> The code entrypoint must be named `functions` with an extension matching the
> runtime: `functions.js`, `functions.mjs`, `functions.ts`, or `functions.mts`
> for Node.js, and `functions.py` for Python.

> **Bundle size limit**
> The zipped bundle (containing both the manifest and the code entrypoint) must
> not exceed **15MB**. Keep functions lean by avoiding large dependencies and
> using tree-shaking when bundling.

## What's next?

- [Use the Functions Framework](/docs/ai-control-plane/mcp-gateway/building-servers/functions/functions-framework)
- [Tag tools so clients can filter by tag](/docs/ai-control-plane/mcp-gateway/building-servers/functions/tool-tags)
- [Choose which tools a server exposes](/docs/ai-control-plane/mcp-gateway#tools-on-a-server)
- [Add OpenAPI-sourced tools](/docs/ai-control-plane/mcp-gateway/building-servers/openapi)
