@gokiteam/goki-dev 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ export function registerFirestoreTools (server, apiClient) {
5
5
  'firestore_list_projects',
6
6
  'List configured Firestore emulator projects',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/firestore/projects/list')
@@ -21,6 +22,7 @@ export function registerFirestoreTools (server, apiClient) {
21
22
  {
22
23
  projectId: z.string().optional().describe('Firestore project ID')
23
24
  },
25
+ { readOnlyHint: true },
24
26
  async ({ projectId }) => {
25
27
  try {
26
28
  const data = await apiClient.post('/v1/firestore/collections/list', { projectId })
@@ -39,6 +41,7 @@ export function registerFirestoreTools (server, apiClient) {
39
41
  collectionPath: z.string().describe('Collection path'),
40
42
  page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Pagination options with limit and offset')
41
43
  },
44
+ { readOnlyHint: true },
42
45
  async ({ projectId, collectionPath, page }) => {
43
46
  try {
44
47
  const data = await apiClient.post('/v1/firestore/documents/list', { projectId, collectionPath, page })
@@ -57,6 +60,7 @@ export function registerFirestoreTools (server, apiClient) {
57
60
  collectionPath: z.string().describe('Collection path'),
58
61
  documentId: z.string().describe('Document ID')
59
62
  },
63
+ { readOnlyHint: true },
60
64
  async ({ projectId, collectionPath, documentId }) => {
61
65
  try {
62
66
  const data = await apiClient.post('/v1/firestore/documents/get', { projectId, collectionPath, documentId })
@@ -107,12 +111,13 @@ export function registerFirestoreTools (server, apiClient) {
107
111
 
108
112
  server.tool(
109
113
  'firestore_delete_document',
110
- 'Delete a Firestore document',
114
+ '[DESTRUCTIVE] Delete a Firestore document permanently. Ask the user for confirmation before calling this tool.',
111
115
  {
112
116
  projectId: z.string().optional().describe('Firestore project ID'),
113
117
  collectionPath: z.string().describe('Collection path'),
114
118
  documentId: z.string().describe('Document ID')
115
119
  },
120
+ { destructiveHint: true },
116
121
  async ({ projectId, collectionPath, documentId }) => {
117
122
  try {
118
123
  const data = await apiClient.post('/v1/firestore/documents/delete', { projectId, collectionPath, documentId })
@@ -123,6 +128,46 @@ export function registerFirestoreTools (server, apiClient) {
123
128
  }
124
129
  )
125
130
 
131
+ server.tool(
132
+ 'firestore_set_document',
133
+ 'Set (create or replace) a Firestore document by ID',
134
+ {
135
+ projectId: z.string().optional().describe('Firestore project ID'),
136
+ collectionPath: z.string().describe('Collection path'),
137
+ documentId: z.string().describe('Document ID'),
138
+ fields: z.record(z.any()).describe('Document fields in Firestore format')
139
+ },
140
+ async ({ projectId, collectionPath, documentId, fields }) => {
141
+ try {
142
+ const data = await apiClient.post('/v1/firestore/documents/set', { projectId, collectionPath, documentId, fields })
143
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
144
+ } catch (error) {
145
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
146
+ }
147
+ }
148
+ )
149
+
150
+ server.tool(
151
+ 'firestore_create_batch',
152
+ 'Create multiple Firestore documents in a collection at once',
153
+ {
154
+ projectId: z.string().optional().describe('Firestore project ID'),
155
+ collectionPath: z.string().describe('Collection path'),
156
+ documents: z.array(z.object({
157
+ documentId: z.string().optional().describe('Document ID (auto-generated if omitted)'),
158
+ fields: z.record(z.any()).describe('Document fields in Firestore format')
159
+ })).describe('Array of documents to create')
160
+ },
161
+ async ({ projectId, collectionPath, documents }) => {
162
+ try {
163
+ const data = await apiClient.post('/v1/firestore/documents/create-batch', { projectId, collectionPath, documents })
164
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
165
+ } catch (error) {
166
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
167
+ }
168
+ }
169
+ )
170
+
126
171
  server.tool(
127
172
  'firestore_query',
128
173
  'Execute a query against a Firestore collection with optional where/orderBy/limit',
@@ -133,6 +178,7 @@ export function registerFirestoreTools (server, apiClient) {
133
178
  orderBy: z.array(z.any()).optional().describe('Array of orderBy clauses'),
134
179
  limit: z.number().optional().describe('Maximum number of results')
135
180
  },
181
+ { readOnlyHint: true },
136
182
  async ({ projectId, collectionPath, where, orderBy, limit }) => {
137
183
  try {
138
184
  const data = await apiClient.post('/v1/firestore/query/execute', { projectId, collectionPath, where, orderBy, limit })
@@ -145,11 +191,12 @@ export function registerFirestoreTools (server, apiClient) {
145
191
 
146
192
  server.tool(
147
193
  'firestore_clear_collection',
148
- 'Delete all documents in a Firestore collection',
194
+ '[DESTRUCTIVE] Delete ALL documents in a Firestore collection. This cannot be undone. Ask the user for confirmation before calling this tool.',
149
195
  {
150
196
  projectId: z.string().optional().describe('Firestore project ID'),
151
197
  collectionPath: z.string().describe('Collection path')
152
198
  },
199
+ { destructiveHint: true },
153
200
  async ({ projectId, collectionPath }) => {
154
201
  try {
155
202
  const data = await apiClient.post('/v1/firestore/collection/clear', { projectId, collectionPath })
@@ -45,6 +45,7 @@ export function registerFunctionsTools (server, apiClient) {
45
45
  limit: z.number().optional().describe('Maximum number of results'),
46
46
  offset: z.number().optional().describe('Offset for pagination')
47
47
  },
48
+ { readOnlyHint: true },
48
49
  async ({ triggerType, status, limit, offset }) => {
49
50
  try {
50
51
  const body = {}
@@ -69,6 +70,7 @@ export function registerFunctionsTools (server, apiClient) {
69
70
  id: z.string().optional().describe('Function ID'),
70
71
  name: z.string().optional().describe('Function name')
71
72
  },
73
+ { readOnlyHint: true },
72
74
  async ({ id, name }) => {
73
75
  try {
74
76
  const body = {}
@@ -118,11 +120,12 @@ export function registerFunctionsTools (server, apiClient) {
118
120
 
119
121
  server.tool(
120
122
  'functions_delete',
121
- 'Delete a Cloud Function from the dev-tools emulator',
123
+ '[DESTRUCTIVE] Delete a Cloud Function from the dev-tools emulator. Ask the user for confirmation before calling this tool.',
122
124
  {
123
125
  id: z.string().optional().describe('Function ID'),
124
126
  name: z.string().optional().describe('Function name')
125
127
  },
128
+ { destructiveHint: true },
126
129
  async ({ id, name }) => {
127
130
  try {
128
131
  const body = {}
@@ -158,11 +161,12 @@ export function registerFunctionsTools (server, apiClient) {
158
161
 
159
162
  server.tool(
160
163
  'functions_stop',
161
- 'Stop a running Cloud Function',
164
+ '[DESTRUCTIVE] Stop a running Cloud Function. Ask the user for confirmation before calling this tool.',
162
165
  {
163
166
  id: z.string().optional().describe('Function ID'),
164
167
  name: z.string().optional().describe('Function name')
165
168
  },
169
+ { destructiveHint: true },
166
170
  async ({ id, name }) => {
167
171
  try {
168
172
  const body = {}
@@ -178,11 +182,12 @@ export function registerFunctionsTools (server, apiClient) {
178
182
 
179
183
  server.tool(
180
184
  'functions_restart',
181
- 'Restart a Cloud Function (stop then start)',
185
+ '[DESTRUCTIVE] Restart a Cloud Function (stop then start). Ask the user for confirmation before calling this tool.',
182
186
  {
183
187
  id: z.string().optional().describe('Function ID'),
184
188
  name: z.string().optional().describe('Function name')
185
189
  },
190
+ { destructiveHint: true },
186
191
  async ({ id, name }) => {
187
192
  try {
188
193
  const body = {}
@@ -228,6 +233,7 @@ export function registerFunctionsTools (server, apiClient) {
228
233
  limit: z.number().optional().describe('Maximum number of results (default: 50)'),
229
234
  offset: z.number().optional().describe('Offset for pagination')
230
235
  },
236
+ { readOnlyHint: true },
231
237
  async ({ functionId, functionName, triggerType, limit, offset }) => {
232
238
  try {
233
239
  const body = {}
@@ -248,6 +254,7 @@ export function registerFunctionsTools (server, apiClient) {
248
254
  'functions_stats',
249
255
  'Get aggregate statistics for Cloud Functions (counts by trigger type, status, invocations)',
250
256
  {},
257
+ { readOnlyHint: true },
251
258
  async () => {
252
259
  try {
253
260
  const data = await apiClient.post('/v1/functions/stats')
@@ -264,6 +271,7 @@ export function registerFunctionsTools (server, apiClient) {
264
271
  {
265
272
  name: z.string().describe('Function name')
266
273
  },
274
+ { readOnlyHint: true },
267
275
  async ({ name }) => {
268
276
  try {
269
277
  const data = await apiClient.post('/v1/functions/dependencies/read', { name })
@@ -301,6 +309,7 @@ export function registerFunctionsTools (server, apiClient) {
301
309
  id: z.string().optional().describe('Function ID'),
302
310
  name: z.string().optional().describe('Function name')
303
311
  },
312
+ { readOnlyHint: true },
304
313
  async ({ id, name }) => {
305
314
  try {
306
315
  const body = {}
@@ -322,6 +331,7 @@ export function registerFunctionsTools (server, apiClient) {
322
331
  name: z.string().optional().describe('Function name'),
323
332
  filePath: z.string().describe('Relative file path within the function source directory')
324
333
  },
334
+ { readOnlyHint: true },
325
335
  async ({ id, name, filePath }) => {
326
336
  try {
327
337
  const body = { filePath }
@@ -359,12 +369,13 @@ export function registerFunctionsTools (server, apiClient) {
359
369
 
360
370
  server.tool(
361
371
  'functions_files_delete',
362
- 'Delete a file from a Cloud Function source directory',
372
+ '[DESTRUCTIVE] Delete a file from a Cloud Function source directory. Ask the user for confirmation before calling this tool.',
363
373
  {
364
374
  id: z.string().optional().describe('Function ID'),
365
375
  name: z.string().optional().describe('Function name'),
366
376
  filePath: z.string().describe('Relative file path within the function source directory')
367
377
  },
378
+ { destructiveHint: true },
368
379
  async ({ id, name, filePath }) => {
369
380
  try {
370
381
  const body = { filePath }
@@ -9,6 +9,7 @@ export function registerHttpTrafficTools (server, apiClient) {
9
9
  limit: z.number().optional().describe('Maximum number of records to return'),
10
10
  offset: z.number().optional().describe('Number of records to skip for pagination')
11
11
  },
12
+ { readOnlyHint: true },
12
13
  async ({ filter, limit, offset }) => {
13
14
  try {
14
15
  const body = {}
@@ -29,6 +30,7 @@ export function registerHttpTrafficTools (server, apiClient) {
29
30
  {
30
31
  id: z.string().describe('ID of the HTTP traffic entry to retrieve')
31
32
  },
33
+ { readOnlyHint: true },
32
34
  async ({ id }) => {
33
35
  try {
34
36
  const data = await apiClient.post('/v1/http-traffic/details', { id })
@@ -46,6 +48,7 @@ export function registerHttpTrafficTools (server, apiClient) {
46
48
  filter: z.record(z.any()).describe('Required filter criteria (method, targetHost, sourceService, statusCode, traceId, pathContains)'),
47
49
  timeout: z.number().optional().describe('Timeout in ms (default: 5000, max: 30000)')
48
50
  },
51
+ { readOnlyHint: true },
49
52
  async ({ filter, timeout }) => {
50
53
  try {
51
54
  const body = { filter }
@@ -60,8 +63,9 @@ export function registerHttpTrafficTools (server, apiClient) {
60
63
 
61
64
  server.tool(
62
65
  'http_traffic_clear',
63
- 'Clear all recorded HTTP traffic',
66
+ '[DESTRUCTIVE] Clear all recorded HTTP traffic. Ask the user for confirmation before calling this tool.',
64
67
  {},
68
+ { destructiveHint: true },
65
69
  async () => {
66
70
  try {
67
71
  const data = await apiClient.post('/v1/http-traffic/clear')
@@ -76,6 +80,7 @@ export function registerHttpTrafficTools (server, apiClient) {
76
80
  'http_traffic_stats',
77
81
  'Get HTTP traffic statistics grouped by host, method, and error rates',
78
82
  {},
83
+ { readOnlyHint: true },
79
84
  async () => {
80
85
  try {
81
86
  const data = await apiClient.post('/v1/http-traffic/stats')
@@ -5,6 +5,7 @@ export function registerLoggingTools (server, apiClient) {
5
5
  'logging_list_services',
6
6
  'List all services that have sent log entries to the dev-tools logging emulator',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/logging/services/list')
@@ -29,6 +30,7 @@ export function registerLoggingTools (server, apiClient) {
29
30
  limit: z.number().optional().describe('Max entries to return (default: 100, max: 1000)'),
30
31
  offset: z.number().optional().describe('Skip this many entries for pagination (default: 0)')
31
32
  },
33
+ { readOnlyHint: true },
32
34
  async ({ serviceName, severity, textContains, traceId, sinceTimestamp, untilTimestamp, compact, limit, offset }) => {
33
35
  try {
34
36
  const filter = {}
@@ -61,6 +63,7 @@ export function registerLoggingTools (server, apiClient) {
61
63
  {
62
64
  ids: z.array(z.string()).describe('Array of log entry IDs to retrieve (from the _id field in compact list results)')
63
65
  },
66
+ { readOnlyHint: true },
64
67
  async ({ ids }) => {
65
68
  try {
66
69
  const data = await apiClient.post('/v1/logging/entries/get', { ids })
@@ -73,8 +76,9 @@ export function registerLoggingTools (server, apiClient) {
73
76
 
74
77
  server.tool(
75
78
  'logging_clear',
76
- 'Clear all log entries from the dev-tools logging emulator',
79
+ '[DESTRUCTIVE] Clear all log entries from the dev-tools logging emulator. Ask the user for confirmation before calling this tool.',
77
80
  {},
81
+ { destructiveHint: true },
78
82
  async () => {
79
83
  try {
80
84
  const data = await apiClient.post('/v1/logging/entries/clear')
@@ -91,6 +95,7 @@ export function registerLoggingTools (server, apiClient) {
91
95
  {
92
96
  filter: z.record(z.any()).optional().describe('Optional filter criteria to scope the export')
93
97
  },
98
+ { readOnlyHint: true },
94
99
  async ({ filter }) => {
95
100
  try {
96
101
  const body = {}
@@ -109,6 +114,7 @@ export function registerLoggingTools (server, apiClient) {
109
114
  {
110
115
  traceId: z.string().describe('The trace ID to look up')
111
116
  },
117
+ { readOnlyHint: true },
112
118
  async ({ traceId }) => {
113
119
  try {
114
120
  const data = await apiClient.post(`/v1/logging/trace/${traceId}`)
@@ -126,6 +132,7 @@ export function registerLoggingTools (server, apiClient) {
126
132
  filter: z.record(z.any()).describe('Filter criteria the log entry must match'),
127
133
  timeout: z.number().optional().describe('Maximum seconds to wait before timing out')
128
134
  },
135
+ { readOnlyHint: true },
129
136
  async ({ filter, timeout }) => {
130
137
  try {
131
138
  const body = { filter }
@@ -145,6 +152,7 @@ export function registerLoggingTools (server, apiClient) {
145
152
  traceId: z.string().optional().describe('Optional trace ID to scope the assertion to'),
146
153
  sinceTimestamp: z.string().optional().describe('Optional ISO timestamp — only check logs after this time')
147
154
  },
155
+ { readOnlyHint: true },
148
156
  async ({ traceId, sinceTimestamp }) => {
149
157
  try {
150
158
  const body = {}
@@ -162,6 +170,7 @@ export function registerLoggingTools (server, apiClient) {
162
170
  'logging_get_queue_stats',
163
171
  'Get Bull queue statistics including job counts and health status for the blackbox logs consumer',
164
172
  {},
173
+ { readOnlyHint: true },
165
174
  async () => {
166
175
  try {
167
176
  const data = await apiClient.post('/v1/logging/queue-stats', {})
@@ -5,6 +5,7 @@ export function registerMqttTools (server, apiClient) {
5
5
  'mqtt_list_clients',
6
6
  'List all connected MQTT clients with their connection info',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/mqtt/clients/list')
@@ -22,6 +23,7 @@ export function registerMqttTools (server, apiClient) {
22
23
  filter: z.record(z.any()).optional().describe('Optional filter criteria for MQTT messages'),
23
24
  page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Optional pagination with limit and offset')
24
25
  },
26
+ { readOnlyHint: true },
25
27
  async ({ filter, page }) => {
26
28
  try {
27
29
  const body = {}
@@ -5,6 +5,7 @@ export function registerPostgresTools (server, apiClient) {
5
5
  'postgres_test_connection',
6
6
  'Test the PostgreSQL database connection',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/postgres/connection/test')
@@ -19,6 +20,7 @@ export function registerPostgresTools (server, apiClient) {
19
20
  'postgres_list_databases',
20
21
  'List all PostgreSQL databases',
21
22
  {},
23
+ { readOnlyHint: true },
22
24
  async () => {
23
25
  try {
24
26
  const data = await apiClient.post('/v1/postgres/databases/list')
@@ -33,6 +35,7 @@ export function registerPostgresTools (server, apiClient) {
33
35
  'postgres_list_schemas',
34
36
  'List all schemas in the current database',
35
37
  {},
38
+ { readOnlyHint: true },
36
39
  async () => {
37
40
  try {
38
41
  const data = await apiClient.post('/v1/postgres/schemas/list')
@@ -49,6 +52,7 @@ export function registerPostgresTools (server, apiClient) {
49
52
  {
50
53
  schema: z.string().optional().describe('Schema name (defaults to public)')
51
54
  },
55
+ { readOnlyHint: true },
52
56
  async ({ schema }) => {
53
57
  try {
54
58
  const data = await apiClient.post('/v1/postgres/tables/list', { schema })
@@ -66,6 +70,7 @@ export function registerPostgresTools (server, apiClient) {
66
70
  table: z.string().describe('Table name'),
67
71
  schema: z.string().optional().describe('Schema name (defaults to public)')
68
72
  },
73
+ { readOnlyHint: true },
69
74
  async ({ table, schema }) => {
70
75
  try {
71
76
  const data = await apiClient.post('/v1/postgres/columns/list', { table, schema })
@@ -83,6 +88,7 @@ export function registerPostgresTools (server, apiClient) {
83
88
  table: z.string().describe('Table name'),
84
89
  page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Pagination options with limit and offset')
85
90
  },
91
+ { readOnlyHint: true },
86
92
  async ({ table, page }) => {
87
93
  try {
88
94
  const data = await apiClient.post('/v1/postgres/rows/list', { table, page })
@@ -95,11 +101,12 @@ export function registerPostgresTools (server, apiClient) {
95
101
 
96
102
  server.tool(
97
103
  'postgres_execute_query',
98
- 'Execute a raw SQL query against PostgreSQL',
104
+ '[DESTRUCTIVE] Execute a raw SQL query against PostgreSQL. Ask the user for confirmation before calling this tool.',
99
105
  {
100
106
  query: z.string().describe('SQL query to execute'),
101
107
  params: z.array(z.any()).optional().describe('Query parameters for parameterized queries')
102
108
  },
109
+ { destructiveHint: true },
103
110
  async ({ query, params }) => {
104
111
  try {
105
112
  const data = await apiClient.post('/v1/postgres/query/execute', { query, params })
@@ -118,6 +125,7 @@ export function registerPostgresTools (server, apiClient) {
118
125
  condition: z.record(z.any()).optional().describe('Condition to match against query results'),
119
126
  timeout: z.number().optional().describe('Timeout in milliseconds')
120
127
  },
128
+ { readOnlyHint: true },
121
129
  async ({ query, condition, timeout }) => {
122
130
  try {
123
131
  const data = await apiClient.post('/v1/postgres/rows/wait-for', { query, condition, timeout })
@@ -5,6 +5,7 @@ export function registerPubsubTools (server, apiClient) {
5
5
  'pubsub_list_topics',
6
6
  'List all Pub/Sub topics in the dev-tools emulator',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/pubsub/topics/list')
@@ -33,10 +34,11 @@ export function registerPubsubTools (server, apiClient) {
33
34
 
34
35
  server.tool(
35
36
  'pubsub_delete_topic',
36
- 'Delete a Pub/Sub topic from the dev-tools emulator',
37
+ '[DESTRUCTIVE] Delete a Pub/Sub topic from the dev-tools emulator. Ask the user for confirmation before calling this tool.',
37
38
  {
38
39
  topicName: z.string().describe('Name of the topic to delete')
39
40
  },
41
+ { destructiveHint: true },
40
42
  async ({ topicName }) => {
41
43
  try {
42
44
  const data = await apiClient.post('/v1/pubsub/topics/delete', { topicName })
@@ -54,6 +56,7 @@ export function registerPubsubTools (server, apiClient) {
54
56
  query: z.string().describe('Search query to match against topic names'),
55
57
  limit: z.number().optional().describe('Maximum number of suggestions to return')
56
58
  },
59
+ { readOnlyHint: true },
57
60
  async ({ query, limit }) => {
58
61
  try {
59
62
  const body = { query }
@@ -70,6 +73,7 @@ export function registerPubsubTools (server, apiClient) {
70
73
  'pubsub_list_subscriptions',
71
74
  'List all Pub/Sub subscriptions in the dev-tools emulator',
72
75
  {},
76
+ { readOnlyHint: true },
73
77
  async () => {
74
78
  try {
75
79
  const data = await apiClient.post('/v1/pubsub/subscriptions/list')
@@ -99,10 +103,11 @@ export function registerPubsubTools (server, apiClient) {
99
103
 
100
104
  server.tool(
101
105
  'pubsub_delete_subscription',
102
- 'Delete a Pub/Sub subscription from the dev-tools emulator',
106
+ '[DESTRUCTIVE] Delete a Pub/Sub subscription from the dev-tools emulator. Ask the user for confirmation before calling this tool.',
103
107
  {
104
108
  subscriptionName: z.string().describe('Name of the subscription to delete')
105
109
  },
110
+ { destructiveHint: true },
106
111
  async ({ subscriptionName }) => {
107
112
  try {
108
113
  const data = await apiClient.post('/v1/pubsub/subscriptions/delete', { subscriptionName })
@@ -140,6 +145,7 @@ export function registerPubsubTools (server, apiClient) {
140
145
  subscriptionName: z.string().describe('Subscription to pull messages from'),
141
146
  maxMessages: z.number().optional().describe('Maximum number of messages to pull')
142
147
  },
148
+ { readOnlyHint: true },
143
149
  async ({ subscriptionName, maxMessages }) => {
144
150
  try {
145
151
  const body = { subscriptionName }
@@ -160,6 +166,7 @@ export function registerPubsubTools (server, apiClient) {
160
166
  searchIn: z.array(z.string()).optional().describe('Optional list of fields to search in (e.g. ["data", "attributes", "topic"])'),
161
167
  filter: z.record(z.any()).optional().describe('Optional additional filter criteria')
162
168
  },
169
+ { readOnlyHint: true },
163
170
  async ({ query, searchIn, filter }) => {
164
171
  try {
165
172
  const body = { query }
@@ -180,6 +187,7 @@ export function registerPubsubTools (server, apiClient) {
180
187
  filter: z.record(z.any()).optional().describe('Optional filter criteria (e.g. by topic, time range)'),
181
188
  page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Optional pagination with limit and offset')
182
189
  },
190
+ { readOnlyHint: true },
183
191
  async ({ filter, page }) => {
184
192
  try {
185
193
  const body = {}
@@ -195,11 +203,12 @@ export function registerPubsubTools (server, apiClient) {
195
203
 
196
204
  server.tool(
197
205
  'pubsub_clear_history',
198
- 'Clear Pub/Sub message history. Optionally keep a number of recent messages or clear only a specific topic.',
206
+ '[DESTRUCTIVE] Clear Pub/Sub message history. Optionally keep a number of recent messages or clear only a specific topic. Ask the user for confirmation before calling this tool.',
199
207
  {
200
208
  keepCount: z.number().optional().describe('Number of recent messages to keep'),
201
209
  topic: z.string().optional().describe('Only clear history for this topic')
202
210
  },
211
+ { destructiveHint: true },
203
212
  async ({ keepCount, topic }) => {
204
213
  try {
205
214
  const body = {}
@@ -217,6 +226,7 @@ export function registerPubsubTools (server, apiClient) {
217
226
  'pubsub_history_stats',
218
227
  'Get statistics about Pub/Sub message history including counts per topic and total volume',
219
228
  {},
229
+ { readOnlyHint: true },
220
230
  async () => {
221
231
  try {
222
232
  const data = await apiClient.post('/v1/pubsub/history/stats')
@@ -234,6 +244,7 @@ export function registerPubsubTools (server, apiClient) {
234
244
  filter: z.record(z.any()).describe('Filter criteria with jsonPath and/or predicate to match incoming messages'),
235
245
  timeout: z.number().optional().describe('Maximum seconds to wait before timing out')
236
246
  },
247
+ { readOnlyHint: true },
237
248
  async ({ filter, timeout }) => {
238
249
  try {
239
250
  const body = { filter }
@@ -252,6 +263,7 @@ export function registerPubsubTools (server, apiClient) {
252
263
  {
253
264
  filter: z.record(z.any()).describe('Filter criteria: { topic (string), since (ISO timestamp), dataContains (string), attributesMatch (object) }')
254
265
  },
266
+ { readOnlyHint: true },
255
267
  async ({ filter }) => {
256
268
  try {
257
269
  const data = await apiClient.post('/v1/pubsub/messages/assert-published', { filter })
@@ -285,6 +297,7 @@ export function registerPubsubTools (server, apiClient) {
285
297
  {
286
298
  projectName: z.string().optional().describe('Optional project name to filter by')
287
299
  },
300
+ { readOnlyHint: true },
288
301
  async ({ projectName }) => {
289
302
  try {
290
303
  const body = {}
@@ -299,11 +312,12 @@ export function registerPubsubTools (server, apiClient) {
299
312
 
300
313
  server.tool(
301
314
  'pubsub_registry_unregister',
302
- 'Unregister topics for a project from the persistent Pub/Sub registry',
315
+ '[DESTRUCTIVE] Unregister topics for a project from the persistent Pub/Sub registry. Ask the user for confirmation before calling this tool.',
303
316
  {
304
317
  projectName: z.string().describe('Project name to unregister topics from'),
305
318
  topicNames: z.array(z.string()).describe('Array of topic name strings to unregister')
306
319
  },
320
+ { destructiveHint: true },
307
321
  async ({ projectName, topicNames }) => {
308
322
  try {
309
323
  const data = await apiClient.post('/v1/pubsub/registry/unregister', { projectName, topicNames })
@@ -5,6 +5,7 @@ export function registerRedisTools (server, apiClient) {
5
5
  'redis_test_connection',
6
6
  'Test the Redis connection',
7
7
  {},
8
+ { readOnlyHint: true },
8
9
  async () => {
9
10
  try {
10
11
  const data = await apiClient.post('/v1/redis/connection/test')
@@ -19,6 +20,7 @@ export function registerRedisTools (server, apiClient) {
19
20
  'redis_info',
20
21
  'Get Redis server info including memory usage and db size',
21
22
  {},
23
+ { readOnlyHint: true },
22
24
  async () => {
23
25
  try {
24
26
  const data = await apiClient.post('/v1/redis/info')
@@ -37,6 +39,7 @@ export function registerRedisTools (server, apiClient) {
37
39
  cursor: z.string().optional().describe('Cursor for pagination'),
38
40
  count: z.number().optional().describe('Approximate number of keys to return per scan')
39
41
  },
42
+ { readOnlyHint: true },
40
43
  async ({ pattern, cursor, count }) => {
41
44
  try {
42
45
  const data = await apiClient.post('/v1/redis/keys/scan', { pattern, cursor, count })
@@ -53,6 +56,7 @@ export function registerRedisTools (server, apiClient) {
53
56
  {
54
57
  key: z.string().describe('Redis key name')
55
58
  },
59
+ { readOnlyHint: true },
56
60
  async ({ key }) => {
57
61
  try {
58
62
  const data = await apiClient.post('/v1/redis/keys/get', { key })
@@ -69,6 +73,7 @@ export function registerRedisTools (server, apiClient) {
69
73
  {
70
74
  key: z.string().describe('Redis key name')
71
75
  },
76
+ { readOnlyHint: true },
72
77
  async ({ key }) => {
73
78
  try {
74
79
  const data = await apiClient.post('/v1/redis/keys/ttl', { key })
@@ -81,10 +86,11 @@ export function registerRedisTools (server, apiClient) {
81
86
 
82
87
  server.tool(
83
88
  'redis_delete_key',
84
- 'Delete a specific Redis key',
89
+ '[DESTRUCTIVE] Delete a specific Redis key. Ask the user for confirmation before calling this tool.',
85
90
  {
86
91
  key: z.string().describe('Redis key name')
87
92
  },
93
+ { destructiveHint: true },
88
94
  async ({ key }) => {
89
95
  try {
90
96
  const data = await apiClient.post('/v1/redis/keys/delete', { key })
@@ -115,8 +121,9 @@ export function registerRedisTools (server, apiClient) {
115
121
 
116
122
  server.tool(
117
123
  'redis_delete_all',
118
- 'Delete all Redis keys (FLUSHDB)',
124
+ '[DESTRUCTIVE] Delete all Redis keys (FLUSHDB). Ask the user for confirmation before calling this tool.',
119
125
  {},
126
+ { destructiveHint: true },
120
127
  async () => {
121
128
  try {
122
129
  const data = await apiClient.post('/v1/redis/keys/delete-all')
@@ -134,6 +141,7 @@ export function registerRedisTools (server, apiClient) {
134
141
  condition: z.record(z.any()).describe('Condition to match against the key value'),
135
142
  timeout: z.number().optional().describe('Timeout in milliseconds')
136
143
  },
144
+ { readOnlyHint: true },
137
145
  async ({ condition, timeout }) => {
138
146
  try {
139
147
  const data = await apiClient.post('/v1/redis/keys/wait-for', { condition, timeout })