Skip to content

Commit ecf2be6

Browse files
committed
improve the keys and values tool descriptions
1 parent 62afcd5 commit ecf2be6

File tree

4 files changed

+145
-60
lines changed

4 files changed

+145
-60
lines changed

apps/workers-observability/src/tools/observability.ts

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { queryWorkersObservability, handleWorkerLogsKeys, handleWorkerLogsValues } from '@repo/mcp-common/src/api/workers-observability'
2-
import { zKeysRequest, zQueryRunRequest, zValuesRequest } from '@repo/mcp-common/src/types/workers-logs-schemas'
1+
import {
2+
handleWorkerLogsKeys,
3+
handleWorkerLogsValues,
4+
queryWorkersObservability,
5+
} from '@repo/mcp-common/src/api/workers-observability'
6+
import {
7+
zKeysRequest,
8+
zQueryRunRequest,
9+
zValuesRequest,
10+
} from '@repo/mcp-common/src/types/workers-logs-schemas'
311

412
import type { ObservabilityMCP } from '../index'
513

@@ -88,49 +96,92 @@ Once you have ran this query you must IMMEDIATELY present the user with this inf
8896
}
8997
)
9098

91-
agent.server.tool('observability_keys', `
92-
Find keys in the workers observability Data. This tool should be used to ensure that the filters or calculations that you are adding to your query are valid.
93-
Filters can be added to this query but because it is faster to return lots of keys set a high limit and only add the filter $metadata.service to filter by worker name.
94-
`, { keysQuery: zKeysRequest }, async ({ keysQuery }) => {
95-
const accountId = await agent.getActiveAccountId()
96-
if (!accountId) {
97-
return {
98-
content: [
99-
{
100-
type: 'text',
101-
text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
102-
},
103-
],
104-
}
105-
}
106-
try {
107-
108-
const result = await handleWorkerLogsKeys(agent.props.accessToken, accountId, keysQuery)
109-
return {
110-
content: [
111-
{
112-
type: 'text',
113-
text: JSON.stringify(result),
114-
},
115-
],
99+
agent.server.tool(
100+
'observability_keys',
101+
`Find keys in the Workers Observability Data.
102+
103+
## When to Use This Tool
104+
- Before creating new queries to discover available data fields
105+
- When building complex filters to verify field names exist
106+
- To explore the schema of your Workers data
107+
- When troubleshooting "invalid field" errors in queries
108+
- To discover metrics fields available for calculations
109+
110+
## Core Capabilities
111+
This tool provides a comprehensive view of available data fields:
112+
1. **Discover Schema** - Explore what fields exist in your Workers data
113+
2. **Validate Fields** - Confirm field names before using them in filters
114+
3. **Understand Data Types** - Learn the type of each field for proper filtering
115+
116+
## Best Practices
117+
- Set a high limit (1000+) to ensure you see all available keys
118+
- Add the $metadata.service filter to narrow results to a specific Worker
119+
- Use this tool before a query with unfamiliar fields
120+
- Pay attention to field data types when crafting filters
121+
122+
## Common Key Categories
123+
- $metadata.* fields: Core Worker metadata including service name, level, etc.
124+
- $workers.* fields: Worker-specific metadata like request ID, trigger type, etc.
125+
- custom fields: Any fields added via console.log in your Worker code
126+
127+
## Troubleshooting
128+
- If expected fields are missing, verify the Worker is actively logging
129+
- For empty results, try broadening your time range
130+
`,
131+
{ keysQuery: zKeysRequest },
132+
async ({ keysQuery }) => {
133+
const accountId = await agent.getActiveAccountId()
134+
if (!accountId) {
135+
return {
136+
content: [
137+
{
138+
type: 'text',
139+
text: 'No currently active accountId. Try listing your accounts (accounts_list) and then setting an active account (set_active_account)',
140+
},
141+
],
142+
}
116143
}
117-
} catch (error) {
118-
return {
119-
content: [
120-
{
121-
type: 'text',
122-
text: JSON.stringify({
123-
error: `Error retrieving worker telemetry keys: ${error instanceof Error && error.message}`,
124-
}),
125-
},
126-
],
144+
try {
145+
const result = await handleWorkerLogsKeys(agent.props.accessToken, accountId, keysQuery)
146+
return {
147+
content: [
148+
{
149+
type: 'text',
150+
text: JSON.stringify(result),
151+
},
152+
],
153+
}
154+
} catch (error) {
155+
return {
156+
content: [
157+
{
158+
type: 'text',
159+
text: JSON.stringify({
160+
error: `Error retrieving worker telemetry keys: ${error instanceof Error && error.message}`,
161+
}),
162+
},
163+
],
164+
}
127165
}
128166
}
129-
})
167+
)
130168

131-
agent.server.tool('observability_values', `
132-
Find values in the workers observability Data. This tool should be used to ensure that the filters that you are adding to your query are valid.
133-
`,
169+
agent.server.tool(
170+
'observability_values',
171+
`Find values in the Workers Observability Data.
172+
173+
## When to Use This Tool
174+
- When building complex queries requiring exact value matches
175+
176+
## Best Practices
177+
- Always specify the correct data type (string, number, boolean)
178+
- Use needle parameter with matchCase:false for case-insensitive searches
179+
- Combine with filters to focus on specific Workers or time periods
180+
- When dealing with high-cardinality fields, use more specific filters
181+
182+
## Troubleshooting
183+
- For no results, verify the field exists using observability_keys first
184+
- If expected values are missing, try broadening your time range`,
134185
{ valuesQuery: zValuesRequest },
135186
async ({ valuesQuery }) => {
136187
const accountId = await agent.getActiveAccountId()
@@ -166,5 +217,6 @@ Find values in the workers observability Data. This tool should be used to ensur
166217
],
167218
}
168219
}
169-
})
220+
}
221+
)
170222
}

packages/mcp-common/src/tools/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from 'zod'
22

33
import { handleAccountsList } from '../api/account'
44
import { getCloudflareClient } from '../cloudflare-api'
5+
56
import type { CloudflareMcpAgent } from '../types/cloudflare-mcp-agent'
67

78
export function registerAccountTools(agent: CloudflareMcpAgent) {

packages/mcp-common/src/tools/worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from 'zod'
22

33
import { handleWorkerScriptDownload, handleWorkersList } from '../api/workers'
44
import { getCloudflareClient } from '../cloudflare-api'
5+
56
import type { CloudflareMcpAgent } from '../types/cloudflare-mcp-agent'
67

78
/**

packages/mcp-common/src/types/workers-logs-schemas.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,7 @@ export const zQueryRunCalculationsV2 = z.array(
138138
series: z.array(
139139
z.object({
140140
time: z.string(),
141-
data: z.array(
142-
zAggregateResult
143-
),
141+
data: z.array(zAggregateResult),
144142
})
145143
),
146144
})
@@ -153,12 +151,12 @@ export const zStatistics = z.object({
153151
})
154152

155153
export const zTimeframe = z
156-
.object({
157-
to: z.string(),
158-
from: z.string(),
159-
})
160-
.describe(
161-
`Timeframe for your query (ISO-8601 format).
154+
.object({
155+
to: z.string(),
156+
from: z.string(),
157+
})
158+
.describe(
159+
`Timeframe for your query (ISO-8601 format).
162160
163161
• Current server time: ${new Date()}
164162
• Default: Last hour from current time
@@ -171,7 +169,7 @@ export const zTimeframe = z
171169
172170
Note: Narrower timeframes provide faster responses and more specific results.
173171
Omit this parameter entirely to use the default (last hour).`
174-
)
172+
)
175173

176174
const zCloudflareMiniEventDetailsRequest = z.object({
177175
url: z.string().optional(),
@@ -307,7 +305,10 @@ export const zQueryRunRequest = z.object({
307305
// TODO: Fix these types
308306
queryId: z.string(),
309307
parameters: z.object({
310-
datasets: z.array(z.string()).optional().describe('Leave this empty to use the default datasets'),
308+
datasets: z
309+
.array(z.string())
310+
.optional()
311+
.describe('Leave this empty to use the default datasets'),
311312
filters: z.array(zQueryFilter).optional(),
312313
filterCombination: zFilterCombination.optional(),
313314
calculations: z.array(zQueryCalculation).optional(),
@@ -318,12 +319,27 @@ export const zQueryRunRequest = z.object({
318319
order: z.enum(['asc', 'desc']).optional(),
319320
})
320321
.optional(),
321-
limit: z.number().int().nonnegative().max(100).optional().describe('Use this limit when view is calculation and a group by is present. 10 is a sensible default'),
322+
limit: z
323+
.number()
324+
.int()
325+
.nonnegative()
326+
.max(100)
327+
.optional()
328+
.describe(
329+
'Use this limit when view is calculation and a group by is present. 10 is a sensible default'
330+
),
322331
needle: zSearchNeedle.optional(),
323332
}),
324333
timeframe: zTimeframe,
325334
granularity: z.number().optional(),
326-
limit: z.number().max(100).optional().default(5).describe('Use this limit to limit the number of events returned when the view is events. 5 is a sensible default'),
335+
limit: z
336+
.number()
337+
.max(100)
338+
.optional()
339+
.default(5)
340+
.describe(
341+
'Use this limit to limit the number of events returned when the view is events. 5 is a sensible default'
342+
),
327343
view: zViews.optional().default('calculations').describe(`## Examples by View Type
328344
### Events View
329345
- "Show me all errors for the worker api-proxy in the last 30 minutes"
@@ -341,9 +357,17 @@ export const zQueryRunRequest = z.object({
341357
TRACES AND PATTERNS ARE NOT CURRENTLY SUPPORTED
342358
`),
343359
dry: z.boolean().optional().default(true),
344-
offset: z.string().optional().describe('The offset to use for pagination. Use the $metadata.id field to get the next offset.'),
360+
offset: z
361+
.string()
362+
.optional()
363+
.describe(
364+
'The offset to use for pagination. Use the $metadata.id field to get the next offset.'
365+
),
345366
offsetBy: z.number().optional(),
346-
offsetDirection: z.string().optional().describe('The direction to use for pagination. Use "next" or "prev".'),
367+
offsetDirection: z
368+
.string()
369+
.optional()
370+
.describe('The direction to use for pagination. Use "next" or "prev".'),
347371
})
348372

349373
/**
@@ -364,11 +388,15 @@ export const zReturnedQueryRunResult = z.object({
364388
*/
365389
export const zKeysRequest = z.object({
366390
timeframe: zTimeframe,
367-
datasets: z.array(z.string()).default([]).describe('Leave this empty to use the default datasets'),
391+
datasets: z
392+
.array(z.string())
393+
.default([])
394+
.describe('Leave this empty to use the default datasets'),
368395
filters: z.array(zQueryFilter).default([]),
369396
limit: z.number().optional(),
370397
needle: zSearchNeedle.optional(),
371-
keyNeedle: zSearchNeedle.optional().describe(`If the user makes a suggestion for a key, use this to narrow down the list of keys returned.
398+
keyNeedle: zSearchNeedle.optional()
399+
.describe(`If the user makes a suggestion for a key, use this to narrow down the list of keys returned.
372400
Make sure match case is fals to avoid case sensitivity issues.`),
373401
})
374402

@@ -391,7 +419,10 @@ export const zValuesRequest = z.object({
391419
timeframe: zTimeframe,
392420
key: z.string(),
393421
type: z.enum(['string', 'boolean', 'number']),
394-
datasets: z.array(z.string()).default([]).describe('Leave this empty to use the default datasets'),
422+
datasets: z
423+
.array(z.string())
424+
.default([])
425+
.describe('Leave this empty to use the default datasets'),
395426
filters: z.array(zQueryFilter).default([]),
396427
limit: z.number().default(50),
397428
needle: zSearchNeedle.optional(),

0 commit comments

Comments
 (0)