
Research
/Security News
9 Malicious NuGet Packages Deliver Time-Delayed Destructive Payloads
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.
@gram-ai/functions
Advanced tools
Gram Functions are small pieces of code that represent LLM tools. They are deployed to [Gram](https://getgram.ai) and are then exposed to LLMs via MCP servers.
Gram Functions are small pieces of code that represent LLM tools. They are deployed to Gram and are then exposed to LLMs via MCP servers.
This library provides a small framework for authoring Gram Functions in TypeScript. The "Hello, World!" example is:
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;
You can use one of the following command to scaffold a new Gram Function project quickly:
pnpm create @gram-ai/function@latest --template gram
## Or one of the following:
# bun create @gram-ai/function@latest --template gram
# npm create @gram-ai/function@latest -- --template gram
Use one of the following commands to add the package to your project:
pnpm add @gram-ai/functions
## Or one of the following:
# bun add @gram-ai/functions
# npm add @gram-ai/functions
The Gram class is the main entry point for defining tools. You create an instance and chain .tool() calls to register multiple tools:
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";
const gram = new Gram()
.tool({
name: "add",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
async execute(ctx, input) {
return ctx.json({ sum: input.a + input.b });
},
})
.tool({
name: "multiply",
description: "Multiply two numbers",
inputSchema: { a: z.number(), b: z.number() },
async execute(ctx, input) {
return ctx.json({ product: input.a * input.b });
},
});
export default gram;
Each tool requires:
The execute function receives a ctx (context) object with helper methods:
ctx.json(data)Returns a JSON response:
async execute(ctx, input) {
return ctx.json({ result: "success", value: 42 });
}
ctx.text(data)Returns a plain text response:
async execute(ctx, input) {
return ctx.text("Operation completed successfully");
}
ctx.html(data)Returns an HTML response:
async execute(ctx, input) {
return ctx.html("<h1>Hello, World!</h1>");
}
ctx.fail(data, options?)Throws an error response (never returns):
async execute(ctx, input) {
if (!input.value) {
ctx.fail({ error: "value is required" }, { status: 400 });
}
// ...
}
ctx.signalAn AbortSignal for handling cancellation:
async execute(ctx, input) {
const response = await fetch(input.url, { signal: ctx.signal });
return ctx.json(await response.json());
}
ctx.envAccess to parsed environment variables defined by the Gram instance:
const gram = new Gram({
envSchema: {
BASE_URL: z.string().transform((url) => new URL(url)),
},
}).tool({
name: "api_call",
inputSchema: { endpoint: z.string() },
async execute(ctx, input) {
const baseURL = ctx.env.BASE_URL;
// Use baseURL...
},
});
Input schemas are defined using Zod:
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";
const gram = new Gram().tool({
name: "create_user",
inputSchema: {
email: z.string().check(z.email()),
age: z.number().check(z.min(18)),
name: z.optional(z.string()),
},
async execute(ctx, input) {
// input is fully typed based on the schema
return ctx.json({ userId: "123" });
},
});
By default, the framework strictly validates input. You can enable lax mode to allow unvalidated input to pass through:
const gram = new Gram({ lax: true });
Environment variables that are used by tools must be defined when instantiating
the Gram class. This is done using a Zod v4 object schema:
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";
const gram = new Gram({
envSchema: {
API_KEY: z.string().describe("API key for external service"),
BASE_URL: z.string().check(z.url()).describe("Base URL for API requests"),
},
});
Whenever a tool wants to access a new environment variable, a definition must be
added to the envSchema if one does not exist. When this Gram Function is
deployed, end users will then be able to provide values for these variables when
installing the corresponding MCP servers.
Environment variables are read from process.env by default, but you can
override them when creating the Gram instance. This can be useful for testing
or local development. Example:
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";
const gram = new Gram({
env: {
API_KEY: "secret-key",
BASE_URL: "https://api.example.com",
},
envSchema: {
API_KEY: z.string().describe("API key for external service"),
BASE_URL: z.string().check(z.url()).describe("Base URL for API requests"),
},
});
If not provided, the framework falls back to process.env.
The framework supports multiple response types. All response methods return Web API Response objects.
return ctx.json({
status: "success",
data: { id: 123, name: "Example" },
});
return ctx.text("Plain text response");
return ctx.html(`
<!DOCTYPE html>
<html>
<body><h1>Hello</h1></body>
</html>
`);
You can also return a plain Response object:
return new Response(data, {
status: 200,
headers: {
"Content-Type": "application/xml",
"X-Custom-Header": "value",
},
});
ctx.fail()Use ctx.fail() to throw error responses:
async execute(ctx, input) {
if (!input.userId) {
ctx.fail(
{ error: "userId is required" },
{ status: 400 }
);
}
const user = await fetchUser(input.userId);
if (!user) {
ctx.fail(
{ error: "User not found" },
{ status: 404 }
);
}
return ctx.json({ user });
}
Errors automatically include a stack trace in the response.
assert()The assert function provides a convenient way to validate conditions and throw error responses:
import { assert } from "@gram-ai/functions";
async execute(ctx, input) {
assert(input.userId, { error: "userId is required" }, { status: 400 });
const user = await fetchUser(input.userId);
assert(user, { error: "User not found" }, { status: 404 });
return ctx.json({ user });
}
The assert function throws a Response object when the condition is false. The framework catches all thrown values, and if any happen to be a Response instance, they will be returned to the client.
Key points about assert:
error field)Generate a manifest of all registered tools:
import { Gram } from "@gram-ai/functions";
const gram = new Gram()
.tool({
/* ... */
})
.tool({
/* ... */
});
const manifest = g.manifest();
// {
// version: "0.0.0",
// tools: [
// {
// name: "tool1",
// description: "...",
// inputSchema: "...", // JSON Schema string
// variables: { ... }
// },
// ...
// ]
// }
Exporting the Gram instance from your module as the default export will allow Gram to handle tool calls automatically when deployed:
import { Gram } from "@gram-ai/functions";
const gram = new Gram()
.tool({
/* ... */
})
.tool({
/* ... */
});
export default gram;
You can also call tools programmatically:
const response = await gram.handleToolCall({
name: "add",
input: { a: 5, b: 3 },
});
const data = await response.json();
console.log(data); // { sum: 8 }
With abort signal support:
const signal = AbortSignal.timeout(5000);
const response = await gram.handleToolCall(
{ name: "longRunning", input: {} },
{ signal },
);
The framework provides full TypeScript type inference:
import { Gram } from "@gram-ai/functions";
import * as z from "zod/mini";
const gram = new Gram().tool({
name: "greet",
inputSchema: { name: z.string() },
async execute(ctx, input) {
// input.name is typed as string
return ctx.json({ message: `Hello, ${input.name}` });
},
});
// Type-safe tool calls
const response = await g.handleToolCall({
name: "greet", // Only "greet" is valid
input: { name: "World" }, // input is typed correctly
});
// Response type is inferred
const data = await response.json(); // { message: string }
FAQs
Gram Functions are small pieces of code that represent LLM tools. They are deployed to [Gram](https://getgram.ai) and are then exposed to LLMs via MCP servers.
We found that @gram-ai/functions demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.

Security News
Socket CTO Ahmad Nassri discusses why supply chain attacks now target developer machines and what AI means for the future of enterprise security.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.