@gokiteam/goki-dev 0.2.0

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.
Files changed (205) hide show
  1. package/README.md +478 -0
  2. package/bin/goki-dev.js +452 -0
  3. package/bin/mcp-server.js +16 -0
  4. package/bin/secrets-cli.js +302 -0
  5. package/cli/ComposeOverrideGenerator.js +226 -0
  6. package/cli/ComposeParser.js +73 -0
  7. package/cli/ConfigGenerator.js +304 -0
  8. package/cli/ConfigManager.js +46 -0
  9. package/cli/DatabaseManager.js +94 -0
  10. package/cli/DevToolsChecker.js +21 -0
  11. package/cli/DevToolsDir.js +66 -0
  12. package/cli/DevToolsManager.js +451 -0
  13. package/cli/DockerManager.js +138 -0
  14. package/cli/FunctionManager.js +95 -0
  15. package/cli/HttpProxyRewriter.js +91 -0
  16. package/cli/Logger.js +10 -0
  17. package/cli/McpConfigManager.js +123 -0
  18. package/cli/NgrokManager.js +431 -0
  19. package/cli/ProjectCLI.js +2322 -0
  20. package/cli/PubSubManager.js +129 -0
  21. package/cli/SnapshotManager.js +88 -0
  22. package/cli/UiFormatter.js +292 -0
  23. package/cli/WebhookUrlRewriter.js +32 -0
  24. package/cli/secrets/BiometricAuth.js +125 -0
  25. package/cli/secrets/SecretInjector.js +47 -0
  26. package/cli/secrets/SecretsConfig.js +141 -0
  27. package/cli/secrets/SecretsDoctor.js +384 -0
  28. package/cli/secrets/SecretsManager.js +255 -0
  29. package/client/dist/client.d.ts +332 -0
  30. package/client/dist/client.js +507 -0
  31. package/client/dist/helpers.d.ts +62 -0
  32. package/client/dist/helpers.js +122 -0
  33. package/client/dist/index.d.ts +59 -0
  34. package/client/dist/index.js +78 -0
  35. package/client/dist/package.json +1 -0
  36. package/client/dist/types.d.ts +280 -0
  37. package/client/dist/types.js +7 -0
  38. package/config.development +46 -0
  39. package/config.test +18 -0
  40. package/guidelines/CodingStyleGuideline.md +148 -0
  41. package/guidelines/CommentingGuideline.md +10 -0
  42. package/guidelines/HttpApiImplementationGuideline.md +137 -0
  43. package/guidelines/NamingGuideline.md +182 -0
  44. package/package.json +138 -0
  45. package/patterns/api/[collectionName]/Controllers.md +62 -0
  46. package/patterns/api/[collectionName]/Logic.md +154 -0
  47. package/patterns/api/[collectionName]/Permissions.md +81 -0
  48. package/patterns/api/[collectionName]/Router.md +83 -0
  49. package/patterns/api/[collectionName]/Schemas.md +197 -0
  50. package/patterns/configs/Patterns.md +7 -0
  51. package/patterns/enums/Patterns.md +24 -0
  52. package/patterns/errorHandling/Patterns.md +185 -0
  53. package/patterns/testing/Patterns.md +232 -0
  54. package/src/Server.js +238 -0
  55. package/src/api/dashboard/Controllers.js +9 -0
  56. package/src/api/dashboard/Logic.js +76 -0
  57. package/src/api/dashboard/Router.js +11 -0
  58. package/src/api/dashboard/Schemas.js +47 -0
  59. package/src/api/data/Controllers.js +26 -0
  60. package/src/api/data/Logic.js +188 -0
  61. package/src/api/data/Router.js +16 -0
  62. package/src/api/docker/Controllers.js +33 -0
  63. package/src/api/docker/Logic.js +268 -0
  64. package/src/api/docker/Router.js +15 -0
  65. package/src/api/docker/Schemas.js +80 -0
  66. package/src/api/docs/Controllers.js +15 -0
  67. package/src/api/docs/Logic.js +85 -0
  68. package/src/api/docs/Router.js +12 -0
  69. package/src/api/export/Controllers.js +30 -0
  70. package/src/api/export/Logic.js +143 -0
  71. package/src/api/export/Router.js +18 -0
  72. package/src/api/export/Schemas.js +104 -0
  73. package/src/api/firestore/Controllers.js +152 -0
  74. package/src/api/firestore/Logic.js +474 -0
  75. package/src/api/firestore/Router.js +23 -0
  76. package/src/api/functions/Controllers.js +261 -0
  77. package/src/api/functions/Logic.js +710 -0
  78. package/src/api/functions/Router.js +50 -0
  79. package/src/api/functions/Schemas.js +193 -0
  80. package/src/api/gateway/Controllers.js +72 -0
  81. package/src/api/gateway/Logic.js +74 -0
  82. package/src/api/gateway/Router.js +10 -0
  83. package/src/api/gateway/Schemas.js +19 -0
  84. package/src/api/health/Controllers.js +14 -0
  85. package/src/api/health/Logic.js +24 -0
  86. package/src/api/health/Router.js +12 -0
  87. package/src/api/httpTraffic/Controllers.js +29 -0
  88. package/src/api/httpTraffic/Logic.js +33 -0
  89. package/src/api/httpTraffic/Router.js +9 -0
  90. package/src/api/httpTraffic/Schemas.js +23 -0
  91. package/src/api/logging/Controllers.js +80 -0
  92. package/src/api/logging/Logic.js +461 -0
  93. package/src/api/logging/Router.js +24 -0
  94. package/src/api/logging/Schemas.js +43 -0
  95. package/src/api/mqtt/Controllers.js +17 -0
  96. package/src/api/mqtt/Logic.js +66 -0
  97. package/src/api/mqtt/Router.js +12 -0
  98. package/src/api/postgres/Controllers.js +97 -0
  99. package/src/api/postgres/Logic.js +221 -0
  100. package/src/api/postgres/Router.js +21 -0
  101. package/src/api/pubsub/Controllers.js +236 -0
  102. package/src/api/pubsub/Logic.js +732 -0
  103. package/src/api/pubsub/Router.js +41 -0
  104. package/src/api/pubsub/Schemas.js +355 -0
  105. package/src/api/redis/Controllers.js +63 -0
  106. package/src/api/redis/Logic.js +239 -0
  107. package/src/api/redis/Router.js +21 -0
  108. package/src/api/scheduler/Controllers.js +27 -0
  109. package/src/api/scheduler/Logic.js +49 -0
  110. package/src/api/scheduler/Router.js +16 -0
  111. package/src/api/services/Controllers.js +26 -0
  112. package/src/api/services/Logic.js +205 -0
  113. package/src/api/services/Router.js +14 -0
  114. package/src/api/services/Schemas.js +66 -0
  115. package/src/api/snapshots/Controllers.js +37 -0
  116. package/src/api/snapshots/Logic.js +797 -0
  117. package/src/api/snapshots/Router.js +15 -0
  118. package/src/api/snapshots/Schemas.js +23 -0
  119. package/src/api/webhooks/Controllers.js +49 -0
  120. package/src/api/webhooks/Logic.js +137 -0
  121. package/src/api/webhooks/Router.js +12 -0
  122. package/src/api/webhooks/Schemas.js +31 -0
  123. package/src/configs/Application.js +147 -0
  124. package/src/configs/Default.js +13 -0
  125. package/src/consumers/BlackboxLogsConsumer.js +235 -0
  126. package/src/consumers/DockerLogsConsumer.js +687 -0
  127. package/src/db/Tables.js +66 -0
  128. package/src/db/schemas/firestore.js +18 -0
  129. package/src/db/schemas/functions.js +65 -0
  130. package/src/db/schemas/httpTraffic.js +43 -0
  131. package/src/db/schemas/logging.js +74 -0
  132. package/src/db/schemas/migrations.js +64 -0
  133. package/src/db/schemas/mqtt.js +56 -0
  134. package/src/db/schemas/pubsub.js +90 -0
  135. package/src/db/schemas/pubsubRegistry.js +22 -0
  136. package/src/db/schemas/webhooks.js +28 -0
  137. package/src/emulation/awsiot/Controllers.js +91 -0
  138. package/src/emulation/awsiot/Logic.js +70 -0
  139. package/src/emulation/awsiot/Router.js +19 -0
  140. package/src/emulation/awsiot/Server.js +100 -0
  141. package/src/emulation/firestore/Server.js +136 -0
  142. package/src/emulation/logging/Controllers.js +212 -0
  143. package/src/emulation/logging/Logic.js +416 -0
  144. package/src/emulation/logging/Router.js +36 -0
  145. package/src/emulation/logging/Schemas.js +82 -0
  146. package/src/emulation/logging/Server.js +108 -0
  147. package/src/emulation/pubsub/Controllers.js +279 -0
  148. package/src/emulation/pubsub/DefaultTopics.js +162 -0
  149. package/src/emulation/pubsub/Logic.js +427 -0
  150. package/src/emulation/pubsub/README.md +309 -0
  151. package/src/emulation/pubsub/Router.js +33 -0
  152. package/src/emulation/pubsub/Server.js +104 -0
  153. package/src/emulation/pubsub/ShadowPoller.js +276 -0
  154. package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
  155. package/src/enums/ContainerNames.js +106 -0
  156. package/src/enums/ErrorReason.js +28 -0
  157. package/src/enums/FunctionStatuses.js +15 -0
  158. package/src/enums/FunctionTriggerTypes.js +15 -0
  159. package/src/enums/GatewayState.js +7 -0
  160. package/src/enums/ServiceNames.js +68 -0
  161. package/src/jobs/DatabaseMaintenance.js +184 -0
  162. package/src/jobs/MessageHistoryCleanup.js +152 -0
  163. package/src/mcp/ApiClient.js +25 -0
  164. package/src/mcp/Server.js +52 -0
  165. package/src/mcp/prompts/debugging.js +104 -0
  166. package/src/mcp/resources/platform.js +118 -0
  167. package/src/mcp/tools/data.js +84 -0
  168. package/src/mcp/tools/docker.js +166 -0
  169. package/src/mcp/tools/firestore.js +162 -0
  170. package/src/mcp/tools/functions.js +380 -0
  171. package/src/mcp/tools/httpTraffic.js +69 -0
  172. package/src/mcp/tools/logging.js +174 -0
  173. package/src/mcp/tools/mqtt.js +37 -0
  174. package/src/mcp/tools/postgres.js +130 -0
  175. package/src/mcp/tools/pubsub.js +316 -0
  176. package/src/mcp/tools/redis.js +146 -0
  177. package/src/mcp/tools/services.js +169 -0
  178. package/src/mcp/tools/snapshots.js +88 -0
  179. package/src/mcp/tools/webhooks.js +115 -0
  180. package/src/middleware/DevProxy.js +67 -0
  181. package/src/middleware/ErrorCatcher.js +35 -0
  182. package/src/middleware/HttpProxy.js +215 -0
  183. package/src/middleware/Reply.js +24 -0
  184. package/src/middleware/TraceId.js +9 -0
  185. package/src/middleware/WebhookProxy.js +234 -0
  186. package/src/protocols/mqtt/Broker.js +92 -0
  187. package/src/protocols/mqtt/Handlers.js +175 -0
  188. package/src/protocols/mqtt/PubSubBridge.js +162 -0
  189. package/src/protocols/mqtt/Server.js +116 -0
  190. package/src/runtime/FunctionRunner.js +179 -0
  191. package/src/services/AppGatewayService.js +582 -0
  192. package/src/singletons/FirestoreBroadcaster.js +367 -0
  193. package/src/singletons/FunctionTriggerDispatcher.js +456 -0
  194. package/src/singletons/FunctionsService.js +418 -0
  195. package/src/singletons/HttpProxy.js +224 -0
  196. package/src/singletons/LogBroadcaster.js +159 -0
  197. package/src/singletons/Logger.js +49 -0
  198. package/src/singletons/MemoryJsonStore.js +175 -0
  199. package/src/singletons/MessageBroadcaster.js +190 -0
  200. package/src/singletons/PostgresBroadcaster.js +367 -0
  201. package/src/singletons/PostgresClient.js +180 -0
  202. package/src/singletons/RedisClient.js +184 -0
  203. package/src/singletons/SqliteStore.js +480 -0
  204. package/src/singletons/TickService.js +151 -0
  205. package/src/singletons/WebhookProxy.js +223 -0
@@ -0,0 +1,380 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerFunctionsTools (server, apiClient) {
4
+ server.tool(
5
+ 'functions_create',
6
+ 'Register and deploy a Cloud Function in the dev-tools emulator',
7
+ {
8
+ name: z.string().describe('Unique function name (alphanumeric, hyphens, underscores)'),
9
+ source: z.string().optional().describe('Inline function source code'),
10
+ sourcePath: z.string().optional().describe('Absolute path to the function source directory'),
11
+ entryPoint: z.string().describe('Name of the exported function to invoke'),
12
+ triggerType: z.enum(['http', 'pubsub', 'firestore', 'scheduler']).describe('How the function is triggered'),
13
+ triggerConfig: z.record(z.any()).optional().describe('Trigger-specific config (e.g. { topic: "myTopic" } for pubsub)'),
14
+ signatureType: z.enum(['cloudevent', 'http']).optional().describe('Function signature type'),
15
+ runtime: z.string().optional().describe('Runtime identifier (default: nodejs20)'),
16
+ environmentVariables: z.record(z.string()).optional().describe('Environment variables for the function process'),
17
+ timeoutSeconds: z.number().optional().describe('Maximum execution time in seconds (1-540)'),
18
+ description: z.string().optional().describe('Human-readable description')
19
+ },
20
+ async ({ name, source, sourcePath, entryPoint, triggerType, triggerConfig, signatureType, runtime, environmentVariables, timeoutSeconds, description }) => {
21
+ try {
22
+ const body = { name, entryPoint, triggerType }
23
+ if (source !== undefined) body.source = source
24
+ if (sourcePath !== undefined) body.sourcePath = sourcePath
25
+ if (triggerConfig !== undefined) body.triggerConfig = triggerConfig
26
+ if (signatureType !== undefined) body.signatureType = signatureType
27
+ if (runtime !== undefined) body.runtime = runtime
28
+ if (environmentVariables !== undefined) body.environmentVariables = environmentVariables
29
+ if (timeoutSeconds !== undefined) body.timeoutSeconds = timeoutSeconds
30
+ if (description !== undefined) body.description = description
31
+ const data = await apiClient.post('/v1/functions/create', body)
32
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
33
+ } catch (error) {
34
+ return { content: [{ type: 'text', text: `Failed to create function: ${error.message}` }], isError: true }
35
+ }
36
+ }
37
+ )
38
+
39
+ server.tool(
40
+ 'functions_list',
41
+ 'List all registered Cloud Functions in the dev-tools emulator',
42
+ {
43
+ triggerType: z.enum(['http', 'pubsub', 'firestore', 'scheduler']).optional().describe('Filter by trigger type'),
44
+ status: z.enum(['stopped', 'starting', 'running', 'error']).optional().describe('Filter by status'),
45
+ limit: z.number().optional().describe('Maximum number of results'),
46
+ offset: z.number().optional().describe('Offset for pagination')
47
+ },
48
+ async ({ triggerType, status, limit, offset }) => {
49
+ try {
50
+ const body = {}
51
+ const filter = {}
52
+ if (triggerType !== undefined) filter.triggerType = triggerType
53
+ if (status !== undefined) filter.status = status
54
+ if (Object.keys(filter).length > 0) body.filter = filter
55
+ if (limit !== undefined) body.limit = limit
56
+ if (offset !== undefined) body.offset = offset
57
+ const data = await apiClient.post('/v1/functions/list', body)
58
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
59
+ } catch (error) {
60
+ return { content: [{ type: 'text', text: `Failed to list functions: ${error.message}` }], isError: true }
61
+ }
62
+ }
63
+ )
64
+
65
+ server.tool(
66
+ 'functions_details',
67
+ 'Get details of a specific Cloud Function by id or name',
68
+ {
69
+ id: z.string().optional().describe('Function ID'),
70
+ name: z.string().optional().describe('Function name')
71
+ },
72
+ async ({ id, name }) => {
73
+ try {
74
+ const body = {}
75
+ if (id !== undefined) body.id = id
76
+ if (name !== undefined) body.name = name
77
+ const data = await apiClient.post('/v1/functions/details', body)
78
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
79
+ } catch (error) {
80
+ return { content: [{ type: 'text', text: `Failed to get function details: ${error.message}` }], isError: true }
81
+ }
82
+ }
83
+ )
84
+
85
+ server.tool(
86
+ 'functions_update',
87
+ 'Update configuration of a Cloud Function',
88
+ {
89
+ id: z.string().optional().describe('Function ID'),
90
+ name: z.string().optional().describe('Function name'),
91
+ description: z.string().optional().describe('Updated description'),
92
+ triggerConfig: z.record(z.any()).optional().describe('Updated trigger configuration'),
93
+ enabled: z.boolean().optional().describe('Enable or disable the function'),
94
+ environmentVariables: z.record(z.string()).optional().describe('Updated environment variables'),
95
+ timeoutSeconds: z.number().optional().describe('Updated timeout in seconds'),
96
+ entryPoint: z.string().optional().describe('Updated entry point'),
97
+ signatureType: z.enum(['cloudevent', 'http']).optional().describe('Updated signature type')
98
+ },
99
+ async ({ id, name, description, triggerConfig, enabled, environmentVariables, timeoutSeconds, entryPoint, signatureType }) => {
100
+ try {
101
+ const body = {}
102
+ if (id !== undefined) body.id = id
103
+ if (name !== undefined) body.name = name
104
+ if (description !== undefined) body.description = description
105
+ if (triggerConfig !== undefined) body.triggerConfig = triggerConfig
106
+ if (enabled !== undefined) body.enabled = enabled
107
+ if (environmentVariables !== undefined) body.environmentVariables = environmentVariables
108
+ if (timeoutSeconds !== undefined) body.timeoutSeconds = timeoutSeconds
109
+ if (entryPoint !== undefined) body.entryPoint = entryPoint
110
+ if (signatureType !== undefined) body.signatureType = signatureType
111
+ const data = await apiClient.post('/v1/functions/update', body)
112
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
113
+ } catch (error) {
114
+ return { content: [{ type: 'text', text: `Failed to update function: ${error.message}` }], isError: true }
115
+ }
116
+ }
117
+ )
118
+
119
+ server.tool(
120
+ 'functions_delete',
121
+ 'Delete a Cloud Function from the dev-tools emulator',
122
+ {
123
+ id: z.string().optional().describe('Function ID'),
124
+ name: z.string().optional().describe('Function name')
125
+ },
126
+ async ({ id, name }) => {
127
+ try {
128
+ const body = {}
129
+ if (id !== undefined) body.id = id
130
+ if (name !== undefined) body.name = name
131
+ const data = await apiClient.post('/v1/functions/delete', body)
132
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
133
+ } catch (error) {
134
+ return { content: [{ type: 'text', text: `Failed to delete function: ${error.message}` }], isError: true }
135
+ }
136
+ }
137
+ )
138
+
139
+ server.tool(
140
+ 'functions_start',
141
+ 'Start a stopped Cloud Function',
142
+ {
143
+ id: z.string().optional().describe('Function ID'),
144
+ name: z.string().optional().describe('Function name')
145
+ },
146
+ async ({ id, name }) => {
147
+ try {
148
+ const body = {}
149
+ if (id !== undefined) body.id = id
150
+ if (name !== undefined) body.name = name
151
+ const data = await apiClient.post('/v1/functions/start', body)
152
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
153
+ } catch (error) {
154
+ return { content: [{ type: 'text', text: `Failed to start function: ${error.message}` }], isError: true }
155
+ }
156
+ }
157
+ )
158
+
159
+ server.tool(
160
+ 'functions_stop',
161
+ 'Stop a running Cloud Function',
162
+ {
163
+ id: z.string().optional().describe('Function ID'),
164
+ name: z.string().optional().describe('Function name')
165
+ },
166
+ async ({ id, name }) => {
167
+ try {
168
+ const body = {}
169
+ if (id !== undefined) body.id = id
170
+ if (name !== undefined) body.name = name
171
+ const data = await apiClient.post('/v1/functions/stop', body)
172
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
173
+ } catch (error) {
174
+ return { content: [{ type: 'text', text: `Failed to stop function: ${error.message}` }], isError: true }
175
+ }
176
+ }
177
+ )
178
+
179
+ server.tool(
180
+ 'functions_restart',
181
+ 'Restart a Cloud Function (stop then start)',
182
+ {
183
+ id: z.string().optional().describe('Function ID'),
184
+ name: z.string().optional().describe('Function name')
185
+ },
186
+ async ({ id, name }) => {
187
+ try {
188
+ const body = {}
189
+ if (id !== undefined) body.id = id
190
+ if (name !== undefined) body.name = name
191
+ const data = await apiClient.post('/v1/functions/restart', body)
192
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
193
+ } catch (error) {
194
+ return { content: [{ type: 'text', text: `Failed to restart function: ${error.message}` }], isError: true }
195
+ }
196
+ }
197
+ )
198
+
199
+ server.tool(
200
+ 'functions_invoke',
201
+ 'Manually invoke a Cloud Function with a custom payload',
202
+ {
203
+ id: z.string().optional().describe('Function ID'),
204
+ name: z.string().optional().describe('Function name'),
205
+ payload: z.any().optional().describe('Payload to pass as CloudEvent data')
206
+ },
207
+ async ({ id, name, payload }) => {
208
+ try {
209
+ const body = {}
210
+ if (id !== undefined) body.id = id
211
+ if (name !== undefined) body.name = name
212
+ if (payload !== undefined) body.payload = payload
213
+ const data = await apiClient.post('/v1/functions/invoke', body)
214
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
215
+ } catch (error) {
216
+ return { content: [{ type: 'text', text: `Failed to invoke function: ${error.message}` }], isError: true }
217
+ }
218
+ }
219
+ )
220
+
221
+ server.tool(
222
+ 'functions_invocations_list',
223
+ 'List invocation history for Cloud Functions',
224
+ {
225
+ functionId: z.string().optional().describe('Filter by function ID'),
226
+ functionName: z.string().optional().describe('Filter by function name'),
227
+ triggerType: z.string().optional().describe('Filter by trigger type'),
228
+ limit: z.number().optional().describe('Maximum number of results (default: 50)'),
229
+ offset: z.number().optional().describe('Offset for pagination')
230
+ },
231
+ async ({ functionId, functionName, triggerType, limit, offset }) => {
232
+ try {
233
+ const body = {}
234
+ if (functionId !== undefined) body.functionId = functionId
235
+ if (functionName !== undefined) body.functionName = functionName
236
+ if (triggerType !== undefined) body.triggerType = triggerType
237
+ if (limit !== undefined) body.limit = limit
238
+ if (offset !== undefined) body.offset = offset
239
+ const data = await apiClient.post('/v1/functions/invocations/list', body)
240
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
241
+ } catch (error) {
242
+ return { content: [{ type: 'text', text: `Failed to list invocations: ${error.message}` }], isError: true }
243
+ }
244
+ }
245
+ )
246
+
247
+ server.tool(
248
+ 'functions_stats',
249
+ 'Get aggregate statistics for Cloud Functions (counts by trigger type, status, invocations)',
250
+ {},
251
+ async () => {
252
+ try {
253
+ const data = await apiClient.post('/v1/functions/stats')
254
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
255
+ } catch (error) {
256
+ return { content: [{ type: 'text', text: `Failed to get functions stats: ${error.message}` }], isError: true }
257
+ }
258
+ }
259
+ )
260
+
261
+ server.tool(
262
+ 'functions_dependencies_read',
263
+ 'Read the package.json for a Cloud Function to see its npm dependencies',
264
+ {
265
+ name: z.string().describe('Function name')
266
+ },
267
+ async ({ name }) => {
268
+ try {
269
+ const data = await apiClient.post('/v1/functions/dependencies/read', { name })
270
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
271
+ } catch (error) {
272
+ return { content: [{ type: 'text', text: `Failed to read dependencies: ${error.message}` }], isError: true }
273
+ }
274
+ }
275
+ )
276
+
277
+ server.tool(
278
+ 'functions_dependencies_update',
279
+ 'Update the package.json for a Cloud Function and optionally run npm install',
280
+ {
281
+ name: z.string().describe('Function name'),
282
+ packageJson: z.string().describe('Full package.json content as a JSON string'),
283
+ install: z.boolean().optional().describe('Whether to run npm install after writing (default: true)')
284
+ },
285
+ async ({ name, packageJson, install }) => {
286
+ try {
287
+ const body = { name, packageJson }
288
+ if (install !== undefined) body.install = install
289
+ const data = await apiClient.post('/v1/functions/dependencies/update', body)
290
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
291
+ } catch (error) {
292
+ return { content: [{ type: 'text', text: `Failed to update dependencies: ${error.message}` }], isError: true }
293
+ }
294
+ }
295
+ )
296
+
297
+ server.tool(
298
+ 'functions_files_list',
299
+ 'List all files in a Cloud Function source directory (excludes node_modules)',
300
+ {
301
+ id: z.string().optional().describe('Function ID'),
302
+ name: z.string().optional().describe('Function name')
303
+ },
304
+ async ({ id, name }) => {
305
+ try {
306
+ const body = {}
307
+ if (id !== undefined) body.id = id
308
+ if (name !== undefined) body.name = name
309
+ const data = await apiClient.post('/v1/functions/files/list', body)
310
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
311
+ } catch (error) {
312
+ return { content: [{ type: 'text', text: `Failed to list files: ${error.message}` }], isError: true }
313
+ }
314
+ }
315
+ )
316
+
317
+ server.tool(
318
+ 'functions_files_read',
319
+ 'Read a file from a Cloud Function source directory',
320
+ {
321
+ id: z.string().optional().describe('Function ID'),
322
+ name: z.string().optional().describe('Function name'),
323
+ filePath: z.string().describe('Relative file path within the function source directory')
324
+ },
325
+ async ({ id, name, filePath }) => {
326
+ try {
327
+ const body = { filePath }
328
+ if (id !== undefined) body.id = id
329
+ if (name !== undefined) body.name = name
330
+ const data = await apiClient.post('/v1/functions/files/read', body)
331
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
332
+ } catch (error) {
333
+ return { content: [{ type: 'text', text: `Failed to read file: ${error.message}` }], isError: true }
334
+ }
335
+ }
336
+ )
337
+
338
+ server.tool(
339
+ 'functions_files_write',
340
+ 'Write a file in a Cloud Function source directory',
341
+ {
342
+ id: z.string().optional().describe('Function ID'),
343
+ name: z.string().optional().describe('Function name'),
344
+ filePath: z.string().describe('Relative file path within the function source directory'),
345
+ content: z.string().describe('File content to write')
346
+ },
347
+ async ({ id, name, filePath, content }) => {
348
+ try {
349
+ const body = { filePath, content }
350
+ if (id !== undefined) body.id = id
351
+ if (name !== undefined) body.name = name
352
+ const data = await apiClient.post('/v1/functions/files/write', body)
353
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
354
+ } catch (error) {
355
+ return { content: [{ type: 'text', text: `Failed to write file: ${error.message}` }], isError: true }
356
+ }
357
+ }
358
+ )
359
+
360
+ server.tool(
361
+ 'functions_files_delete',
362
+ 'Delete a file from a Cloud Function source directory',
363
+ {
364
+ id: z.string().optional().describe('Function ID'),
365
+ name: z.string().optional().describe('Function name'),
366
+ filePath: z.string().describe('Relative file path within the function source directory')
367
+ },
368
+ async ({ id, name, filePath }) => {
369
+ try {
370
+ const body = { filePath }
371
+ if (id !== undefined) body.id = id
372
+ if (name !== undefined) body.name = name
373
+ const data = await apiClient.post('/v1/functions/files/delete', body)
374
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
375
+ } catch (error) {
376
+ return { content: [{ type: 'text', text: `Failed to delete file: ${error.message}` }], isError: true }
377
+ }
378
+ }
379
+ )
380
+ }
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerHttpTrafficTools (server, apiClient) {
4
+ server.tool(
5
+ 'http_traffic_list',
6
+ 'List recorded HTTP traffic between microservices (captured by the dev-tools proxy)',
7
+ {
8
+ filter: z.record(z.any()).optional().describe('Optional filter criteria (method, targetHost, sourceService, statusCode, traceId)'),
9
+ limit: z.number().optional().describe('Maximum number of records to return'),
10
+ offset: z.number().optional().describe('Number of records to skip for pagination')
11
+ },
12
+ async ({ filter, limit, offset }) => {
13
+ try {
14
+ const body = {}
15
+ if (filter !== undefined) body.filter = filter
16
+ if (limit !== undefined) body.limit = limit
17
+ if (offset !== undefined) body.offset = offset
18
+ const data = await apiClient.post('/v1/http-traffic/list', body)
19
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
20
+ } catch (error) {
21
+ return { content: [{ type: 'text', text: `Failed to list HTTP traffic: ${error.message}` }], isError: true }
22
+ }
23
+ }
24
+ )
25
+
26
+ server.tool(
27
+ 'http_traffic_details',
28
+ 'Get full request/response details of an HTTP traffic entry including headers and body',
29
+ {
30
+ id: z.string().describe('ID of the HTTP traffic entry to retrieve')
31
+ },
32
+ async ({ id }) => {
33
+ try {
34
+ const data = await apiClient.post('/v1/http-traffic/details', { id })
35
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
36
+ } catch (error) {
37
+ return { content: [{ type: 'text', text: `Failed to get HTTP traffic details: ${error.message}` }], isError: true }
38
+ }
39
+ }
40
+ )
41
+
42
+ server.tool(
43
+ 'http_traffic_clear',
44
+ 'Clear all recorded HTTP traffic',
45
+ {},
46
+ async () => {
47
+ try {
48
+ const data = await apiClient.post('/v1/http-traffic/clear')
49
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
50
+ } catch (error) {
51
+ return { content: [{ type: 'text', text: `Failed to clear HTTP traffic: ${error.message}` }], isError: true }
52
+ }
53
+ }
54
+ )
55
+
56
+ server.tool(
57
+ 'http_traffic_stats',
58
+ 'Get HTTP traffic statistics grouped by host, method, and error rates',
59
+ {},
60
+ async () => {
61
+ try {
62
+ const data = await apiClient.post('/v1/http-traffic/stats')
63
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
64
+ } catch (error) {
65
+ return { content: [{ type: 'text', text: `Failed to get HTTP traffic stats: ${error.message}` }], isError: true }
66
+ }
67
+ }
68
+ )
69
+ }
@@ -0,0 +1,174 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerLoggingTools (server, apiClient) {
4
+ server.tool(
5
+ 'logging_list_services',
6
+ 'List all services that have sent log entries to the dev-tools logging emulator',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/logging/services/list')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Failed to list services: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'logging_list_entries',
20
+ 'List log entries with optional filters for service name, severity level, and search text. Supports pagination. Use compact=true (default) for an overview with just id, timestamp, severity, service, message, and error — then call logging_get_entries with specific IDs to get full details.',
21
+ {
22
+ serviceName: z.string().optional().describe('Filter by service name (e.g., "core-pms", "integration-apaleo")'),
23
+ severity: z.string().optional().describe('Filter by severity level: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY'),
24
+ textContains: z.string().optional().describe('Search for text in log messages (case-sensitive substring match)'),
25
+ traceId: z.string().optional().describe('Filter by trace ID to see all logs for a specific request'),
26
+ sinceTimestamp: z.string().optional().describe('ISO timestamp - only show logs after this time (e.g., "2024-01-01T00:00:00Z")'),
27
+ untilTimestamp: z.string().optional().describe('ISO timestamp - only show logs before this time'),
28
+ compact: z.boolean().optional().describe('Return compact entries with only key fields. Default: true. Set to false for full entries.'),
29
+ limit: z.number().optional().describe('Max entries to return (default: 100, max: 1000)'),
30
+ offset: z.number().optional().describe('Skip this many entries for pagination (default: 0)')
31
+ },
32
+ async ({ serviceName, severity, textContains, traceId, sinceTimestamp, untilTimestamp, compact, limit, offset }) => {
33
+ try {
34
+ const filter = {}
35
+ if (serviceName) filter.serviceName = serviceName
36
+ if (severity) filter.severity = severity
37
+ if (textContains) filter.textPayload = { $regex: textContains }
38
+ if (traceId) filter.trace = traceId
39
+ if (sinceTimestamp || untilTimestamp) {
40
+ filter.timestamp = {}
41
+ if (sinceTimestamp) filter.timestamp.$gte = sinceTimestamp
42
+ if (untilTimestamp) filter.timestamp.$lte = untilTimestamp
43
+ }
44
+ const page = {}
45
+ if (limit !== undefined) page.limit = Math.min(limit, 1000)
46
+ if (offset !== undefined) page.offset = offset
47
+ const body = { compact: compact !== false }
48
+ if (Object.keys(filter).length > 0) body.filter = filter
49
+ if (Object.keys(page).length > 0) body.page = page
50
+ const data = await apiClient.post('/v1/logging/entries/list', body)
51
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
52
+ } catch (error) {
53
+ return { content: [{ type: 'text', text: `Failed to list log entries: ${error.message}` }], isError: true }
54
+ }
55
+ }
56
+ )
57
+
58
+ server.tool(
59
+ 'logging_get_entries',
60
+ 'Get full details of one or more log entries by their IDs. Use this after logging_list_entries (compact mode) to fetch complete details for specific entries you want to inspect.',
61
+ {
62
+ ids: z.array(z.string()).describe('Array of log entry IDs to retrieve (from the _id field in compact list results)')
63
+ },
64
+ async ({ ids }) => {
65
+ try {
66
+ const data = await apiClient.post('/v1/logging/entries/get', { ids })
67
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
68
+ } catch (error) {
69
+ return { content: [{ type: 'text', text: `Failed to get log entries: ${error.message}` }], isError: true }
70
+ }
71
+ }
72
+ )
73
+
74
+ server.tool(
75
+ 'logging_clear',
76
+ 'Clear all log entries from the dev-tools logging emulator',
77
+ {},
78
+ async () => {
79
+ try {
80
+ const data = await apiClient.post('/v1/logging/entries/clear')
81
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
82
+ } catch (error) {
83
+ return { content: [{ type: 'text', text: `Failed to clear log entries: ${error.message}` }], isError: true }
84
+ }
85
+ }
86
+ )
87
+
88
+ server.tool(
89
+ 'logging_export',
90
+ 'Export log entries from the dev-tools logging emulator, optionally filtered by criteria',
91
+ {
92
+ filter: z.record(z.any()).optional().describe('Optional filter criteria to scope the export')
93
+ },
94
+ async ({ filter }) => {
95
+ try {
96
+ const body = {}
97
+ if (filter !== undefined) body.filter = filter
98
+ const data = await apiClient.post('/v1/logging/entries/export', body)
99
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
100
+ } catch (error) {
101
+ return { content: [{ type: 'text', text: `Failed to export log entries: ${error.message}` }], isError: true }
102
+ }
103
+ }
104
+ )
105
+
106
+ server.tool(
107
+ 'logging_get_trace',
108
+ 'Get all log entries for a specific trace ID, useful for following a request across services',
109
+ {
110
+ traceId: z.string().describe('The trace ID to look up')
111
+ },
112
+ async ({ traceId }) => {
113
+ try {
114
+ const data = await apiClient.post(`/v1/logging/trace/${traceId}`)
115
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
116
+ } catch (error) {
117
+ return { content: [{ type: 'text', text: `Failed to get trace: ${error.message}` }], isError: true }
118
+ }
119
+ }
120
+ )
121
+
122
+ server.tool(
123
+ 'logging_wait_for',
124
+ 'Wait for a log entry matching the filter criteria to appear. Blocks up to timeout seconds. Useful for verifying async operations completed.',
125
+ {
126
+ filter: z.record(z.any()).describe('Filter criteria the log entry must match'),
127
+ timeout: z.number().optional().describe('Maximum seconds to wait before timing out')
128
+ },
129
+ async ({ filter, timeout }) => {
130
+ try {
131
+ const body = { filter }
132
+ if (timeout !== undefined) body.timeout = timeout
133
+ const data = await apiClient.post('/v1/logging/entries/wait-for', body)
134
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
135
+ } catch (error) {
136
+ return { content: [{ type: 'text', text: `Failed to wait for log entry: ${error.message}` }], isError: true }
137
+ }
138
+ }
139
+ )
140
+
141
+ server.tool(
142
+ 'logging_assert_no_errors',
143
+ 'Assert that no error-level log entries exist. Optionally scope to a specific traceId or time window. Returns errors if any are found.',
144
+ {
145
+ traceId: z.string().optional().describe('Optional trace ID to scope the assertion to'),
146
+ sinceTimestamp: z.string().optional().describe('Optional ISO timestamp — only check logs after this time')
147
+ },
148
+ async ({ traceId, sinceTimestamp }) => {
149
+ try {
150
+ const body = {}
151
+ if (traceId !== undefined) body.traceId = traceId
152
+ if (sinceTimestamp !== undefined) body.sinceTimestamp = sinceTimestamp
153
+ const data = await apiClient.post('/v1/logging/entries/assert-no-errors', body)
154
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
155
+ } catch (error) {
156
+ return { content: [{ type: 'text', text: `Failed to assert no errors: ${error.message}` }], isError: true }
157
+ }
158
+ }
159
+ )
160
+
161
+ server.tool(
162
+ 'logging_get_queue_stats',
163
+ 'Get Bull queue statistics including job counts and health status for the blackbox logs consumer',
164
+ {},
165
+ async () => {
166
+ try {
167
+ const data = await apiClient.post('/v1/logging/queue-stats', {})
168
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
169
+ } catch (error) {
170
+ return { content: [{ type: 'text', text: `Failed to get queue stats: ${error.message}` }], isError: true }
171
+ }
172
+ }
173
+ )
174
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerMqttTools (server, apiClient) {
4
+ server.tool(
5
+ 'mqtt_list_clients',
6
+ 'List all connected MQTT clients with their connection info',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/mqtt/clients/list')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Failed to list MQTT clients: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'mqtt_list_messages',
20
+ 'List recent MQTT messages with optional filtering',
21
+ {
22
+ filter: z.record(z.any()).optional().describe('Optional filter criteria for MQTT messages'),
23
+ page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Optional pagination with limit and offset')
24
+ },
25
+ async ({ filter, page }) => {
26
+ try {
27
+ const body = {}
28
+ if (filter !== undefined) body.filter = filter
29
+ if (page !== undefined) body.page = page
30
+ const data = await apiClient.post('/v1/mqtt/messages/list', body)
31
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
32
+ } catch (error) {
33
+ return { content: [{ type: 'text', text: `Failed to list MQTT messages: ${error.message}` }], isError: true }
34
+ }
35
+ }
36
+ )
37
+ }