Skip to content

Commit 448aba6

Browse files
Merge pull request #79 from asararatnakar/tools-with-descriptions
2 parents ea04590 + 2245fd4 commit 448aba6

File tree

6 files changed

+410
-3
lines changed

6 files changed

+410
-3
lines changed

executable/persisted/tests/Test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ const {
99

1010
testDescription = getTestDescription("snippets", __dirname);
1111

12-
const requestsFile = path.join(path.dirname(__dirname), "operations.graphql");
13-
const requests = fs.readFileSync(requestsFile, "utf8").toString();
14-
1512
describe(testDescription, function () {
1613
const tests = [
1714
{

executable/prescribed/README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Prescribed Tools
2+
3+
A **prescribed tool** is a tool that maps to a specific [GraphQL operation](https://spec.graphql.org/September2025/#sec-Language.Operations) in a [persisted document](https://spec.graphql.org/September2025/#sec-Persisted-Documents). The `@tool(prescribed:)` argument links the tool definition to an operation name in a persisted document. This approach provides a structured way to expose specific GraphQL operations as tools that can be called by AI models.
4+
5+
This sample demonstrates how to use the `@tool` directive to create prescribed tools for MCP (Model Context Protocol).
6+
7+
## Overview
8+
9+
The sample implements a mock weather service API with a single operation: weather forecast for a city.
10+
11+
The key feature demonstrated is how to create a prescribed tool that maps to a persisted GraphQL operation, allowing AI models to execute specific, well-defined queries.
12+
13+
## Schema Structure
14+
15+
The schema consists of:
16+
17+
1. A main schema file (`index.graphql`) that defines:
18+
- The GraphQL types for weather data
19+
- The `@tool` directive that creates a prescribed tool
20+
- The `@sdl` directive that includes the persisted operation
21+
22+
2. An operations file (`operations.graphql`) that contains:
23+
- A GraphQL operation that will be exposed as a tool
24+
- Descriptions for the operation and its variables (recommended best practice)
25+
26+
## How Prescribed Tools Work
27+
28+
A prescribed tool is defined using the `@tool` directive with the `prescribed` argument pointing to a specific operation in a persisted document:
29+
30+
```graphql
31+
schema
32+
# Load the persisted operations document that contains the WeatherForecast operation
33+
@sdl(
34+
files: []
35+
executables: [{ document: "operations.graphql", persist: true }]
36+
)
37+
# Define a prescribed tool that maps to the WeatherForecast operation in the persisted document
38+
@tool(name: "weather-lookup", prescribed: "WeatherForecast") {
39+
query: Query
40+
}
41+
```
42+
43+
The operation in the persisted document should include descriptions ([new to GraphQL September 2025](https://spec.graphql.org/September2025/#Description)) to help AI models understand how to use the tool:
44+
45+
```graphql
46+
"""
47+
Get detailed weather forecast for a specific city
48+
This operation provides a multi-day weather forecast including temperature, conditions, and other meteorological data
49+
"""
50+
query WeatherForecast(
51+
"""The name of the city to get weather forecast for"""
52+
$city: String!,
53+
"""Number of days to forecast (1-7), defaults to 3 days"""
54+
$days: Int = 3
55+
) {
56+
weatherForecast(city: $city, days: $days) {
57+
city {
58+
name
59+
country
60+
timezone
61+
}
62+
forecast {
63+
date
64+
conditions
65+
high {
66+
celsius
67+
fahrenheit
68+
}
69+
low {
70+
celsius
71+
fahrenheit
72+
}
73+
precipitation
74+
humidity
75+
windSpeed
76+
windDirection
77+
}
78+
}
79+
}
80+
```
81+
82+
## MCP Tool Description
83+
84+
When deployed, this schema will expose a tool through the MCP endpoint. The tool's description and parameter information are derived from the GraphQL operation's descriptions:
85+
86+
```json
87+
{
88+
"name": "weather-lookup",
89+
"description": "Get detailed weather forecast for a specific city. This operation provides a multi-day weather forecast including temperature, conditions, and other meteorological data",
90+
"inputSchema": {
91+
"type": "object",
92+
"properties": {
93+
"variables": {
94+
"properties": {
95+
"city": {
96+
"description": "The name of the city to get weather forecast for",
97+
"type": "string"
98+
},
99+
"days": {
100+
"description": "Number of days to forecast (1-7), defaults to 3 days",
101+
"type": "integer",
102+
"default": 3
103+
}
104+
},
105+
"required": ["city"],
106+
"type": "object"
107+
}
108+
},
109+
"required": ["variables"]
110+
}
111+
}
112+
```
113+
114+
## Using the Tool
115+
116+
An AI model can use this tool by:
117+
118+
1. Understanding the tool's purpose from the operation's description
119+
2. Providing the required variables (city name and optionally number of days)
120+
3. Receiving structured weather forecast data in response
121+
122+
## Benefits of Prescribed Tools
123+
124+
1. **Controlled Access**: Only specific, predefined operations are exposed as tools, providing fine-grained control over what AI models can execute.
125+
2. **Type Safety**: The GraphQL schema ensures that inputs are properly validated.
126+
3. **Clear Documentation**: Operation descriptions serve as both documentation for developers and instructions for AI models.
127+
4. **Versioning**: Operations can be versioned and updated independently of the tool definition.
128+
129+
## Testing as an MCP Tool
130+
131+
To test this as an MCP tool with AI models:
132+
133+
1. Deploy the schema to StepZen using the command `stepzen deploy`
134+
2. [Connect Claude Desktop](https://modelcontextprotocol.io/docs/develop/connect-local-servers) to your StepZen MCP endpoint
135+
3. The tool will appear as `weather-lookup` and can be called by the AI model
136+
137+
**Example**: Interaction between MCP and the Claude UI.
138+
139+
<img width="563" height="360" alt="Image" src="https://github.com/user-attachments/assets/7da0cd20-2469-45cd-8a17-0891a1a03dec" />
140+
141+
<img width="502" height="588" alt="Image" src="https://github.com/user-attachments/assets/cf499b7e-a3fe-433c-b837-bdde25441739" />
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
schema
2+
# Load the persisted operations document that contains the WeatherForecast operation
3+
@sdl(
4+
files: []
5+
executables: [{ document: "operations.graphql", persist: true }]
6+
)
7+
# Define a prescribed tool that maps to the WeatherForecast operation in the persisted document
8+
@tool(name: "weather-lookup", prescribed: "WeatherForecast") {
9+
query: Query
10+
}
11+
12+
type Query {
13+
# Get weather forecast for a specific city
14+
weatherForecast(city: String!, days: Int = 1): WeatherForecast
15+
@value(
16+
script: {
17+
src: """
18+
function weatherForecast() {
19+
const cities = {
20+
"new york": {
21+
name: "New York",
22+
country: "United States",
23+
latitude: 40.7128,
24+
longitude: -74.0060,
25+
timezone: "America/New_York",
26+
population: 8804190
27+
},
28+
"london": {
29+
name: "London",
30+
country: "United Kingdom",
31+
latitude: 51.5074,
32+
longitude: -0.1278,
33+
timezone: "Europe/London",
34+
population: 8982000
35+
},
36+
"tokyo": {
37+
name: "Tokyo",
38+
country: "Japan",
39+
latitude: 35.6762,
40+
longitude: 139.6503,
41+
timezone: "Asia/Tokyo",
42+
population: 13960000
43+
},
44+
"sydney": {
45+
name: "Sydney",
46+
country: "Australia",
47+
latitude: -33.8688,
48+
longitude: 151.2093,
49+
timezone: "Australia/Sydney",
50+
population: 5312000
51+
}
52+
};
53+
54+
// Default to 1 day if not specified or out of range
55+
const daysToForecast = days < 1 || days > 7 ? 1 : days;
56+
57+
const normalizedCity = city.toLowerCase();
58+
const cityData = cities[normalizedCity] || {
59+
name: city,
60+
country: "Unknown",
61+
latitude: 0,
62+
longitude: 0,
63+
timezone: "UTC",
64+
population: null
65+
};
66+
67+
// Fixed forecast patterns for each city
68+
const forecastPatterns = {
69+
"new york": [
70+
{ conditions: "Partly Cloudy", high: 22, low: 15, precipitation: 20, humidity: 65, windSpeed: 12, windDirection: "NW" },
71+
{ conditions: "Sunny", high: 24, low: 16, precipitation: 5, humidity: 55, windSpeed: 8, windDirection: "W" },
72+
{ conditions: "Cloudy", high: 20, low: 14, precipitation: 30, humidity: 70, windSpeed: 15, windDirection: "NE" },
73+
{ conditions: "Rain", high: 18, low: 12, precipitation: 75, humidity: 85, windSpeed: 18, windDirection: "E" },
74+
{ conditions: "Partly Cloudy", high: 23, low: 16, precipitation: 15, humidity: 60, windSpeed: 10, windDirection: "SW" },
75+
{ conditions: "Sunny", high: 25, low: 17, precipitation: 0, humidity: 50, windSpeed: 7, windDirection: "W" },
76+
{ conditions: "Thunderstorm", high: 21, low: 15, precipitation: 90, humidity: 90, windSpeed: 25, windDirection: "S" }
77+
],
78+
"london": [
79+
{ conditions: "Cloudy", high: 16, low: 10, precipitation: 40, humidity: 75, windSpeed: 14, windDirection: "SW" },
80+
{ conditions: "Rain", high: 15, low: 9, precipitation: 65, humidity: 80, windSpeed: 16, windDirection: "W" },
81+
{ conditions: "Partly Cloudy", high: 17, low: 11, precipitation: 25, humidity: 70, windSpeed: 12, windDirection: "NW" },
82+
{ conditions: "Cloudy", high: 16, low: 10, precipitation: 35, humidity: 75, windSpeed: 13, windDirection: "SW" },
83+
{ conditions: "Rain", high: 14, low: 8, precipitation: 70, humidity: 85, windSpeed: 18, windDirection: "W" },
84+
{ conditions: "Partly Cloudy", high: 18, low: 12, precipitation: 20, humidity: 65, windSpeed: 11, windDirection: "NW" },
85+
{ conditions: "Sunny", high: 19, low: 13, precipitation: 10, humidity: 60, windSpeed: 9, windDirection: "N" }
86+
],
87+
"tokyo": [
88+
{ conditions: "Sunny", high: 26, low: 19, precipitation: 5, humidity: 60, windSpeed: 10, windDirection: "E" },
89+
{ conditions: "Partly Cloudy", high: 25, low: 18, precipitation: 15, humidity: 65, windSpeed: 12, windDirection: "SE" },
90+
{ conditions: "Cloudy", high: 23, low: 17, precipitation: 30, humidity: 70, windSpeed: 14, windDirection: "S" },
91+
{ conditions: "Rain", high: 22, low: 16, precipitation: 60, humidity: 80, windSpeed: 16, windDirection: "SW" },
92+
{ conditions: "Partly Cloudy", high: 24, low: 18, precipitation: 20, humidity: 65, windSpeed: 11, windDirection: "E" },
93+
{ conditions: "Sunny", high: 27, low: 20, precipitation: 5, humidity: 55, windSpeed: 9, windDirection: "NE" },
94+
{ conditions: "Cloudy", high: 24, low: 18, precipitation: 25, humidity: 68, windSpeed: 13, windDirection: "SE" }
95+
],
96+
"sydney": [
97+
{ conditions: "Sunny", high: 28, low: 20, precipitation: 10, humidity: 60, windSpeed: 15, windDirection: "NE" },
98+
{ conditions: "Partly Cloudy", high: 27, low: 19, precipitation: 15, humidity: 65, windSpeed: 14, windDirection: "E" },
99+
{ conditions: "Sunny", high: 29, low: 21, precipitation: 5, humidity: 55, windSpeed: 13, windDirection: "NE" },
100+
{ conditions: "Partly Cloudy", high: 26, low: 19, precipitation: 20, humidity: 70, windSpeed: 16, windDirection: "SE" },
101+
{ conditions: "Cloudy", high: 24, low: 18, precipitation: 35, humidity: 75, windSpeed: 18, windDirection: "S" },
102+
{ conditions: "Rain", high: 22, low: 17, precipitation: 70, humidity: 85, windSpeed: 20, windDirection: "SW" },
103+
{ conditions: "Partly Cloudy", high: 25, low: 18, precipitation: 25, humidity: 70, windDirection: "W", windSpeed: 17 }
104+
]
105+
};
106+
107+
// Get pattern for city or use default
108+
const pattern = forecastPatterns[normalizedCity] || forecastPatterns["new york"];
109+
110+
// Generate forecast for the requested number of days
111+
const forecast = [];
112+
const today = new Date();
113+
114+
for (let i = 0; i < daysToForecast; i++) {
115+
const forecastDate = new Date();
116+
forecastDate.setDate(today.getDate() + i);
117+
118+
const dayPattern = pattern[i % pattern.length];
119+
120+
forecast.push({
121+
date: forecastDate.toISOString().split('T')[0],
122+
sunrise: "06:25 AM",
123+
sunset: "06:35 PM",
124+
high: {
125+
celsius: dayPattern.high,
126+
fahrenheit: Math.round(dayPattern.high * 9 / 5 + 32)
127+
},
128+
low: {
129+
celsius: dayPattern.low,
130+
fahrenheit: Math.round(dayPattern.low * 9 / 5 + 32)
131+
},
132+
conditions: dayPattern.conditions,
133+
precipitation: dayPattern.precipitation,
134+
humidity: dayPattern.humidity,
135+
windSpeed: dayPattern.windSpeed,
136+
windDirection: dayPattern.windDirection
137+
});
138+
}
139+
140+
return {
141+
city: cityData,
142+
forecast: forecast,
143+
};
144+
}
145+
weatherForecast()
146+
"""
147+
}
148+
)
149+
}
150+
151+
type WeatherForecast {
152+
city: City!
153+
forecast: [DailyForecast!]!
154+
lastUpdated: DateTime
155+
}
156+
157+
type City {
158+
name: String!
159+
country: String!
160+
latitude: Float!
161+
longitude: Float!
162+
timezone: String!
163+
population: Int
164+
}
165+
166+
type DailyForecast {
167+
date: Date!
168+
sunrise: String!
169+
sunset: String!
170+
high: Temperature!
171+
low: Temperature!
172+
conditions: String!
173+
precipitation: Float!
174+
humidity: Int!
175+
windSpeed: Float!
176+
windDirection: String!
177+
}
178+
179+
type Temperature {
180+
celsius: Float!
181+
fahrenheit: Float!
182+
}
183+
184+
scalar Date
185+
scalar DateTime
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Get detailed weather forecast for a specific city
3+
This operation provides a multi-day weather forecast including temperature, conditions, and other meteorological data
4+
"""
5+
query WeatherForecast(
6+
"""The name of the city to get weather forecast for"""
7+
$city: String!,
8+
"""Number of days to forecast (1-7), defaults to current day"""
9+
$days: Int = 1
10+
) {
11+
weatherForecast(city: $city, days: $days) {
12+
city {
13+
name
14+
country
15+
timezone
16+
}
17+
forecast {
18+
high {
19+
celsius
20+
fahrenheit
21+
}
22+
low {
23+
celsius
24+
fahrenheit
25+
}
26+
conditions
27+
precipitation
28+
humidity
29+
windSpeed
30+
windDirection
31+
}
32+
}
33+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"endpoint": "api/miscellaneous"
3+
}

0 commit comments

Comments
 (0)