Skip to content

Commit 8c98747

Browse files
committed
Sync open source content 🐝 (from a88518025c569c9bef6af99659d7c2cd1a943a66)
1 parent 0dad5d6 commit 8c98747

File tree

5 files changed

+316
-5
lines changed

5 files changed

+316
-5
lines changed

β€Ž_meta.global.tsxβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ const meta = {
233233
"creating-taskmaster-mcp-server": {
234234
title: "Create a Taskmaster MCP server",
235235
},
236+
"consuming-external-apis": {
237+
title: "Connect to External APIs",
238+
},
236239
},
237240
},
238241
},
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
---
2+
title: Connecting to External APIs with Gram Functions
3+
description: "Learn how to build Gram Functions that consume external APIs and manage secrets securely"
4+
---
5+
6+
import { Callout } from "@/mdx/components";
7+
8+
When building AI agents, you often need to integrate with external APIs to provide real-world functionality.
9+
This guide shows you how to build Gram Functions that consume external APIs while managing API keys and secrets
10+
using Gram's environment system.
11+
12+
We'll build a weather service that demonstrates key patterns for external API integration.
13+
14+
## What we'll build
15+
16+
In this guide, we'll:
17+
18+
- Create Gram Functions that call external APIs
19+
- Configure environment variables for API keys
20+
21+
## Setting up the project
22+
23+
First, create a new Gram Functions project. Follow the instructions in the [Getting Started](/docs/gram/getting-started) guide for more details.
24+
25+
```bash
26+
npm create gram-functions@latest
27+
```
28+
29+
## Building a tool that uses the OpenWeatherMap API
30+
31+
Let's create a tool that uses the OpenWeatherMap API. Edit `src/gram.ts`:
32+
33+
```typescript filename=="src/gram.ts"
34+
import { Gram } from "@gram-ai/functions";
35+
import { z } from "zod";
36+
37+
const gram = new Gram({
38+
envSchema: {
39+
OPENWEATHER_API_KEY: z.string(),
40+
},
41+
}).tool({
42+
name: "get_current_weather",
43+
description: "Get current weather conditions for a specific city",
44+
inputSchema: {
45+
city: z.string().describe("The city name (e.g., 'London', 'New York')"),
46+
country: z
47+
.string()
48+
.optional()
49+
.describe("Optional 2-letter country code (e.g., 'US', 'GB')"),
50+
},
51+
async execute(ctx, input) {
52+
const query =
53+
input.country != null ? `${input.city},${input.country}` : input.city;
54+
55+
const url = new URL("https://api.openweathermap.org/data/2.5/weather");
56+
url.searchParams.append("q", query);
57+
url.searchParams.append("appid", ctx.env.OPENWEATHER_API_KEY);
58+
59+
// Gram Functions handle Response objects natively, so no need to process the response at all
60+
return await fetch(url.toString());
61+
},
62+
});
63+
64+
export default gram;
65+
```
66+
67+
## Breaking down the implementation
68+
69+
### Environment schema definition
70+
71+
```typescript
72+
const gram = new Gram({
73+
envSchema: {
74+
OPENWEATHER_API_KEY: z.string(),
75+
},
76+
});
77+
```
78+
79+
The `envSchema` defines what environment variables your function expects.
80+
Gram makes them available via `ctx.env` in your execute functions. The Gram dashboard will help you manage these variables.
81+
82+
### Tool input schemas
83+
84+
```typescript
85+
inputSchema: {
86+
city: z.string().describe("The city name (e.g., 'London', 'New York')"),
87+
country: z.string().optional().describe("Optional 2-letter country code"),
88+
units: z.enum(["metric", "imperial", "standard"]).default("metric"),
89+
}
90+
```
91+
92+
Input schemas define what parameters the AI agent can provide. Using `.describe()` helps the AI understand when and how to use each parameter.
93+
94+
### Accessing environment variables
95+
96+
```typescript
97+
url.searchParams.append("appid", ctx.env.OPENWEATHER_API_KEY);
98+
```
99+
100+
Environment variables are accessed through `ctx.env`, which is type-safe based on your `envSchema`.
101+
102+
Environment variables are also available through the standard `process.env` object, but will not be type-safe. This will be populated with the "raw" environment variable values.
103+
104+
### Returning the response
105+
106+
```typescript
107+
return await fetch(url.toString());
108+
```
109+
110+
Gram Functions handle Response objects natively, so no need to process the response at all. Alternatively,
111+
you can `await` the reponse and extract the data you need.
112+
113+
```typescript
114+
const response = await fetch(url.toString());
115+
const data = await response.json();
116+
return ctx.json({ temperature: data.main.temp });
117+
```
118+
119+
## Deploying your functions
120+
121+
Build and push your functions to Gram.
122+
123+
```bash
124+
gram build
125+
gram push
126+
```
127+
128+
You should now see your functions as a "source" in your Gram project. When creating a new toolset (or updating an existing one) you'll see the tools you've defined as options.
129+
130+
## Setting up the environment variable
131+
132+
Gram Functions provide a type-safe way to manage environment variables using Zod schemas. This ensures your functions have access to required secrets at runtime while keeping them secure.
133+
134+
<Callout title="Providing environment variables" type="info">
135+
You'll need to provide the environment variable in the Gram dashboard. After
136+
adding the `get_current_weather` tool to a toolset, you'll see the
137+
OPENWEATHER_API_KEY environment variable show up in the `Auth` tab. Set the
138+
value and save.
139+
</Callout>
140+
141+
![Gram dashboard with the OPENWEATHER_API_KEY environment variable](/assets/docs/gram/img/functions/auth_tab.png)
142+
143+
## Testing your functions
144+
145+
Once deployed, you can test your functions in the Gram playground or in your Gram MCP server.
146+
Simply add the tool to any toolset and ask about the weather in a city.
147+
148+
## Chaining API calls
149+
150+
Gram Functions put the power of TypeScript at your fingertips. You can chain API calls together to create more complex tools.
151+
152+
```typescript filename=="src/gram.ts"
153+
.tool({
154+
name: "compare_weather_between_cities",
155+
description:
156+
"Compare weather conditions between multiple cities and provide analysis",
157+
inputSchema: {
158+
cities: z
159+
.array(z.string())
160+
.min(2)
161+
.max(5)
162+
.describe(
163+
"Array of city names to compare (between 2 and 5 cities, e.g., ['London', 'Paris', 'Berlin'])"
164+
),
165+
units: z
166+
.enum(["metric", "imperial", "standard"])
167+
.default("metric")
168+
.describe("Units of measurement for all cities"),
169+
},
170+
async execute(ctx, input) {
171+
// Fetch weather for all cities in parallel
172+
const weatherPromises = input.cities.map(async (city) => {
173+
const url = new URL("https://api.openweathermap.org/data/2.5/weather");
174+
url.searchParams.append("q", city);
175+
url.searchParams.append("appid", ctx.env.OPENWEATHER_API_KEY);
176+
url.searchParams.append("units", input.units);
177+
178+
try {
179+
const response = await fetch(url.toString());
180+
if (!response.ok) {
181+
return { city, error: "City not found or API error" };
182+
}
183+
const data: any = await response.json();
184+
return {
185+
city: data.name,
186+
country: data.sys.country,
187+
temperature: data.main.temp,
188+
feels_like: data.main.feels_like,
189+
humidity: data.main.humidity,
190+
description: data.weather[0].description,
191+
wind_speed: data.wind.speed,
192+
};
193+
} catch (error) {
194+
return { city, error: "Failed to fetch weather" };
195+
}
196+
});
197+
198+
const results = await Promise.all(weatherPromises);
199+
200+
// Filter out errors
201+
const validResults = results.filter((r) => !("error" in r));
202+
const errors = results.filter((r) => "error" in r);
203+
204+
if (validResults.length === 0) {
205+
return ctx.json({
206+
error: "Could not fetch weather for any cities",
207+
errors,
208+
});
209+
}
210+
211+
// Calculate comparison statistics
212+
const temperatures = validResults.map((r) => r.temperature);
213+
const warmest = validResults.reduce((prev, current) =>
214+
prev.temperature > current.temperature ? prev : current
215+
);
216+
const coldest = validResults.reduce((prev, current) =>
217+
prev.temperature < current.temperature ? prev : current
218+
);
219+
const avgTemp =
220+
temperatures.reduce((sum, temp) => sum + temp, 0) / temperatures.length;
221+
222+
// Find cities with similar conditions
223+
const conditionGroups = validResults.reduce((groups, result) => {
224+
const desc = result.description;
225+
if (!groups[desc]) groups[desc] = [];
226+
groups[desc].push(result.city);
227+
return groups;
228+
}, {} as Record<string, string[]>);
229+
230+
return ctx.json({
231+
comparison: validResults,
232+
analysis: {
233+
warmest_city: {
234+
city: warmest.city,
235+
temperature: warmest.temperature,
236+
},
237+
coldest_city: {
238+
city: coldest.city,
239+
temperature: coldest.temperature,
240+
},
241+
temperature_range: warmest.temperature - coldest.temperature,
242+
average_temperature: Math.round(avgTemp * 10) / 10,
243+
condition_groups: conditionGroups,
244+
},
245+
errors: errors.length > 0 ? errors : undefined,
246+
units: input.units,
247+
});
248+
},
249+
});
250+
```
251+
252+
## Best practices for external API integration
253+
254+
### 1. Use descriptive tool names
255+
256+
Choose names that clearly indicate what the tool does:
257+
258+
- βœ… `get_current_weather`
259+
- ❌ `weather` or `fetch`
260+
261+
### 2. Provide detailed descriptions
262+
263+
Help the AI agent understand when to use each tool:
264+
265+
```typescript
266+
description: "Get current weather conditions for a specific city";
267+
```
268+
269+
### 3. Transform API responses
270+
271+
Return only the data the AI agent needs:
272+
273+
```typescript
274+
// Good: Clean, focused response
275+
return ctx.json({
276+
city: data.name,
277+
temperature: data.main.temp,
278+
description: data.weather[0].description,
279+
});
280+
281+
// Avoid: Raw API response with unnecessary fields
282+
return ctx.json(data);
283+
```
284+
285+
### 4. Use environment variables for all secrets
286+
287+
Never hardcode API keys or credentials:
288+
289+
```typescript
290+
// βœ… Good
291+
ctx.env.OPENWEATHER_API_KEY;
292+
293+
// ❌ Bad
294+
const API_KEY = "abc123...";
295+
```
296+
297+
## Next steps
298+
299+
Now that you understand how to consume external APIs with Gram Functions, explore these related guides:
300+
301+
- [Build MCP servers with external OAuth](/docs/gram/examples/oauth-external-server)
302+
- [Creating an MCP server for Taskmaster](/docs/gram/examples/creating-taskmaster-mcp-server)
303+
- [Deploy from GitHub Actions](/docs/gram/examples/deploy-from-github-actions)
304+
305+
## Additional resources
306+
307+
- [OpenWeatherMap API Documentation](https://openweathermap.org/api)
308+
- [Gram Functions API Reference](/docs/gram/gram-functions/introduction)
309+
- [Zod Schema Validation](https://zod.dev)

β€Ždocs/gram/getting-started/typescript.mdxβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ This will build your functions and deploy them to Gram. In the [Gram Dashboard](
101101
You can now add your tools to an existing MCP server or create a new one.
102102

103103
To create a new MCP server, go to the Toolsets page and click **Create A Toolset** (or **+ Add Toolset**). Name it whatever you want. A toolset is a group of tools that can be exposed as an MCP server.
104-
For more information on toolsets, see the [Toolsets](/docs/gram/build-mcp) documentation.
104+
For more information on toolsets, see the [Toolsets](/docs/gram/build-mcp/create-default-toolset) documentation.
105105

106106
![Gram Dashboard, showing the create toolset dialog](/assets/docs/gram/img/functions/onboarding_create-toolset.png)
107107

108108
Now, click **Add Tools** to add your tools to the toolset. You'll see the tools you defined in your `src/gram.ts` file listed here.
109109

110110
![Gram Dashboard, showing the add tools dialog](/assets/docs/gram/img/functions/onboarding_add-tools.png)
111111

112-
Finally, head to the **MCP** tab on the toolset page and click **Enable**. Check out the **Install Page** linked therein for help installing the MCP server in your favorite client. For more information on MCP servers, see the [MCP Servers](/docs/gram/host-mcp) documentation.
112+
Finally, head to the **MCP** tab on the toolset page and click **Enable**. Check out the **Install Page** linked therein for help installing the MCP server in your favorite client. For more information on MCP servers, see the [MCP Servers](/docs/gram/host-mcp/deploy-mcp-server) documentation.

β€Ždocs/gram/introduction.mdβ€Ž

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,9 @@ In addition, any page can be copied into markdown or opened into popular LLM cha
6868

6969
## Further Reading
7070

71-
- [Building tools in TypeScript using Gram Functions](/docs/gram/typescript)
72-
- [Getting started with Gram using an API spec](/docs/gram/openapi)
71+
- [Building tools in TypeScript using Gram Functions](/docs/gram/getting-started/typescript)
72+
- [Getting started with Gram using an API spec](/docs/gram/getting-started/openapi)
7373
- [Bring an existing MCP server to Gram](/docs/gram/gram-functions/mcp-sdk)
74-
- [Advanced tool curation](/docs/advanced/art-of-tool-curation)
7574
- [For resources on MCP see our MCP hub](/mcp)
7675
- [Adding OAuth to your MCP server](/docs/gram/host-mcp/adding-oauth)
7776
- [CLI reference](/docs/gram/command-line/installation)
337 KB
Loading

0 commit comments

Comments
Β (0)