@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,130 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerPostgresTools (server, apiClient) {
4
+ server.tool(
5
+ 'postgres_test_connection',
6
+ 'Test the PostgreSQL database connection',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/postgres/connection/test')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'postgres_list_databases',
20
+ 'List all PostgreSQL databases',
21
+ {},
22
+ async () => {
23
+ try {
24
+ const data = await apiClient.post('/v1/postgres/databases/list')
25
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
26
+ } catch (error) {
27
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
28
+ }
29
+ }
30
+ )
31
+
32
+ server.tool(
33
+ 'postgres_list_schemas',
34
+ 'List all schemas in the current database',
35
+ {},
36
+ async () => {
37
+ try {
38
+ const data = await apiClient.post('/v1/postgres/schemas/list')
39
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
40
+ } catch (error) {
41
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
42
+ }
43
+ }
44
+ )
45
+
46
+ server.tool(
47
+ 'postgres_list_tables',
48
+ 'List tables in a PostgreSQL schema (defaults to public)',
49
+ {
50
+ schema: z.string().optional().describe('Schema name (defaults to public)')
51
+ },
52
+ async ({ schema }) => {
53
+ try {
54
+ const data = await apiClient.post('/v1/postgres/tables/list', { schema })
55
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
56
+ } catch (error) {
57
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
58
+ }
59
+ }
60
+ )
61
+
62
+ server.tool(
63
+ 'postgres_list_columns',
64
+ 'List columns of a PostgreSQL table with types and constraints',
65
+ {
66
+ table: z.string().describe('Table name'),
67
+ schema: z.string().optional().describe('Schema name (defaults to public)')
68
+ },
69
+ async ({ table, schema }) => {
70
+ try {
71
+ const data = await apiClient.post('/v1/postgres/columns/list', { table, schema })
72
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
73
+ } catch (error) {
74
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
75
+ }
76
+ }
77
+ )
78
+
79
+ server.tool(
80
+ 'postgres_list_rows',
81
+ 'List rows from a PostgreSQL table with pagination',
82
+ {
83
+ table: z.string().describe('Table name'),
84
+ page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Pagination options with limit and offset')
85
+ },
86
+ async ({ table, page }) => {
87
+ try {
88
+ const data = await apiClient.post('/v1/postgres/rows/list', { table, page })
89
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
90
+ } catch (error) {
91
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
92
+ }
93
+ }
94
+ )
95
+
96
+ server.tool(
97
+ 'postgres_execute_query',
98
+ 'Execute a raw SQL query against PostgreSQL',
99
+ {
100
+ query: z.string().describe('SQL query to execute'),
101
+ params: z.array(z.any()).optional().describe('Query parameters for parameterized queries')
102
+ },
103
+ async ({ query, params }) => {
104
+ try {
105
+ const data = await apiClient.post('/v1/postgres/query/execute', { query, params })
106
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
107
+ } catch (error) {
108
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
109
+ }
110
+ }
111
+ )
112
+
113
+ server.tool(
114
+ 'postgres_wait_for_row',
115
+ 'Wait for a row matching the condition (blocks up to timeout)',
116
+ {
117
+ query: z.string().describe('SQL query to poll'),
118
+ condition: z.record(z.any()).optional().describe('Condition to match against query results'),
119
+ timeout: z.number().optional().describe('Timeout in milliseconds')
120
+ },
121
+ async ({ query, condition, timeout }) => {
122
+ try {
123
+ const data = await apiClient.post('/v1/postgres/rows/wait-for', { query, condition, timeout })
124
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
125
+ } catch (error) {
126
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
127
+ }
128
+ }
129
+ )
130
+ }
@@ -0,0 +1,316 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerPubsubTools (server, apiClient) {
4
+ server.tool(
5
+ 'pubsub_list_topics',
6
+ 'List all Pub/Sub topics in the dev-tools emulator',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/pubsub/topics/list')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Failed to list topics: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'pubsub_create_topic',
20
+ 'Create a new Pub/Sub topic in the dev-tools emulator',
21
+ {
22
+ topicName: z.string().describe('Name of the topic to create')
23
+ },
24
+ async ({ topicName }) => {
25
+ try {
26
+ const data = await apiClient.post('/v1/pubsub/topics/create', { topicName })
27
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
28
+ } catch (error) {
29
+ return { content: [{ type: 'text', text: `Failed to create topic: ${error.message}` }], isError: true }
30
+ }
31
+ }
32
+ )
33
+
34
+ server.tool(
35
+ 'pubsub_delete_topic',
36
+ 'Delete a Pub/Sub topic from the dev-tools emulator',
37
+ {
38
+ topicName: z.string().describe('Name of the topic to delete')
39
+ },
40
+ async ({ topicName }) => {
41
+ try {
42
+ const data = await apiClient.post('/v1/pubsub/topics/delete', { topicName })
43
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
44
+ } catch (error) {
45
+ return { content: [{ type: 'text', text: `Failed to delete topic: ${error.message}` }], isError: true }
46
+ }
47
+ }
48
+ )
49
+
50
+ server.tool(
51
+ 'pubsub_suggest_topics',
52
+ 'Get topic name suggestions based on a query string, useful for autocomplete',
53
+ {
54
+ query: z.string().describe('Search query to match against topic names'),
55
+ limit: z.number().optional().describe('Maximum number of suggestions to return')
56
+ },
57
+ async ({ query, limit }) => {
58
+ try {
59
+ const body = { query }
60
+ if (limit !== undefined) body.limit = limit
61
+ const data = await apiClient.post('/v1/pubsub/topics/suggest', body)
62
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
63
+ } catch (error) {
64
+ return { content: [{ type: 'text', text: `Failed to suggest topics: ${error.message}` }], isError: true }
65
+ }
66
+ }
67
+ )
68
+
69
+ server.tool(
70
+ 'pubsub_list_subscriptions',
71
+ 'List all Pub/Sub subscriptions in the dev-tools emulator',
72
+ {},
73
+ async () => {
74
+ try {
75
+ const data = await apiClient.post('/v1/pubsub/subscriptions/list')
76
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
77
+ } catch (error) {
78
+ return { content: [{ type: 'text', text: `Failed to list subscriptions: ${error.message}` }], isError: true }
79
+ }
80
+ }
81
+ )
82
+
83
+ server.tool(
84
+ 'pubsub_create_subscription',
85
+ 'Create a new Pub/Sub subscription attached to a topic',
86
+ {
87
+ topicName: z.string().describe('Name of the topic to subscribe to'),
88
+ subscriptionName: z.string().describe('Name for the new subscription')
89
+ },
90
+ async ({ topicName, subscriptionName }) => {
91
+ try {
92
+ const data = await apiClient.post('/v1/pubsub/subscriptions/create', { topicName, subscriptionName })
93
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
94
+ } catch (error) {
95
+ return { content: [{ type: 'text', text: `Failed to create subscription: ${error.message}` }], isError: true }
96
+ }
97
+ }
98
+ )
99
+
100
+ server.tool(
101
+ 'pubsub_delete_subscription',
102
+ 'Delete a Pub/Sub subscription from the dev-tools emulator',
103
+ {
104
+ subscriptionName: z.string().describe('Name of the subscription to delete')
105
+ },
106
+ async ({ subscriptionName }) => {
107
+ try {
108
+ const data = await apiClient.post('/v1/pubsub/subscriptions/delete', { subscriptionName })
109
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
110
+ } catch (error) {
111
+ return { content: [{ type: 'text', text: `Failed to delete subscription: ${error.message}` }], isError: true }
112
+ }
113
+ }
114
+ )
115
+
116
+ server.tool(
117
+ 'pubsub_publish_message',
118
+ 'Publish a message to a Pub/Sub topic. The message can be a string or a JSON object. Optionally include key-value attributes.',
119
+ {
120
+ topicName: z.string().describe('Topic to publish the message to'),
121
+ message: z.any().describe('Message payload — can be a string or a JSON object'),
122
+ attributes: z.record(z.string()).optional().describe('Optional key-value attributes to attach to the message')
123
+ },
124
+ async ({ topicName, message, attributes }) => {
125
+ try {
126
+ const body = { topicName, message }
127
+ if (attributes !== undefined) body.attributes = attributes
128
+ const data = await apiClient.post('/v1/pubsub/messages/publish', body)
129
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
130
+ } catch (error) {
131
+ return { content: [{ type: 'text', text: `Failed to publish message: ${error.message}` }], isError: true }
132
+ }
133
+ }
134
+ )
135
+
136
+ server.tool(
137
+ 'pubsub_pull_messages',
138
+ 'Pull pending messages from a Pub/Sub subscription',
139
+ {
140
+ subscriptionName: z.string().describe('Subscription to pull messages from'),
141
+ maxMessages: z.number().optional().describe('Maximum number of messages to pull')
142
+ },
143
+ async ({ subscriptionName, maxMessages }) => {
144
+ try {
145
+ const body = { subscriptionName }
146
+ if (maxMessages !== undefined) body.maxMessages = maxMessages
147
+ const data = await apiClient.post('/v1/pubsub/messages/pull', body)
148
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
149
+ } catch (error) {
150
+ return { content: [{ type: 'text', text: `Failed to pull messages: ${error.message}` }], isError: true }
151
+ }
152
+ }
153
+ )
154
+
155
+ server.tool(
156
+ 'pubsub_search_messages',
157
+ 'Search through Pub/Sub message history by query string. Can search in specific fields and apply additional filters.',
158
+ {
159
+ query: z.string().describe('Search query to match against message content'),
160
+ searchIn: z.array(z.string()).optional().describe('Optional list of fields to search in (e.g. ["data", "attributes", "topic"])'),
161
+ filter: z.record(z.any()).optional().describe('Optional additional filter criteria')
162
+ },
163
+ async ({ query, searchIn, filter }) => {
164
+ try {
165
+ const body = { query }
166
+ if (searchIn !== undefined) body.searchIn = searchIn
167
+ if (filter !== undefined) body.filter = filter
168
+ const data = await apiClient.post('/v1/pubsub/messages/search', body)
169
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
170
+ } catch (error) {
171
+ return { content: [{ type: 'text', text: `Failed to search messages: ${error.message}` }], isError: true }
172
+ }
173
+ }
174
+ )
175
+
176
+ server.tool(
177
+ 'pubsub_get_history',
178
+ 'Retrieve Pub/Sub message history with optional filtering and pagination',
179
+ {
180
+ filter: z.record(z.any()).optional().describe('Optional filter criteria (e.g. by topic, time range)'),
181
+ page: z.object({ limit: z.number().optional(), offset: z.number().optional() }).optional().describe('Optional pagination with limit and offset')
182
+ },
183
+ async ({ filter, page }) => {
184
+ try {
185
+ const body = {}
186
+ if (filter !== undefined) body.filter = filter
187
+ if (page !== undefined) body.page = page
188
+ const data = await apiClient.post('/v1/pubsub/messages/history', body)
189
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
190
+ } catch (error) {
191
+ return { content: [{ type: 'text', text: `Failed to get message history: ${error.message}` }], isError: true }
192
+ }
193
+ }
194
+ )
195
+
196
+ server.tool(
197
+ 'pubsub_clear_history',
198
+ 'Clear Pub/Sub message history. Optionally keep a number of recent messages or clear only a specific topic.',
199
+ {
200
+ keepCount: z.number().optional().describe('Number of recent messages to keep'),
201
+ topic: z.string().optional().describe('Only clear history for this topic')
202
+ },
203
+ async ({ keepCount, topic }) => {
204
+ try {
205
+ const body = {}
206
+ if (keepCount !== undefined) body.keepCount = keepCount
207
+ if (topic !== undefined) body.topic = topic
208
+ const data = await apiClient.post('/v1/pubsub/history/clear', body)
209
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
210
+ } catch (error) {
211
+ return { content: [{ type: 'text', text: `Failed to clear history: ${error.message}` }], isError: true }
212
+ }
213
+ }
214
+ )
215
+
216
+ server.tool(
217
+ 'pubsub_history_stats',
218
+ 'Get statistics about Pub/Sub message history including counts per topic and total volume',
219
+ {},
220
+ async () => {
221
+ try {
222
+ const data = await apiClient.post('/v1/pubsub/history/stats')
223
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
224
+ } catch (error) {
225
+ return { content: [{ type: 'text', text: `Failed to get history stats: ${error.message}` }], isError: true }
226
+ }
227
+ }
228
+ )
229
+
230
+ server.tool(
231
+ 'pubsub_wait_for_message',
232
+ 'Block until a Pub/Sub message matching the filter criteria arrives, or timeout. Useful for waiting on async responses after publishing a command.',
233
+ {
234
+ filter: z.record(z.any()).describe('Filter criteria with jsonPath and/or predicate to match incoming messages'),
235
+ timeout: z.number().optional().describe('Maximum seconds to wait before timing out')
236
+ },
237
+ async ({ filter, timeout }) => {
238
+ try {
239
+ const body = { filter }
240
+ if (timeout !== undefined) body.timeout = timeout
241
+ const data = await apiClient.post('/v1/pubsub/messages/wait-for', body)
242
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
243
+ } catch (error) {
244
+ return { content: [{ type: 'text', text: `Failed to wait for message: ${error.message}` }], isError: true }
245
+ }
246
+ }
247
+ )
248
+
249
+ server.tool(
250
+ 'pubsub_assert_message_published',
251
+ 'Assert that a message was published to a topic matching filter criteria. Returns whether a match was found, the count, and the first matching message.',
252
+ {
253
+ filter: z.record(z.any()).describe('Filter criteria: { topic (string), since (ISO timestamp), dataContains (string), attributesMatch (object) }')
254
+ },
255
+ async ({ filter }) => {
256
+ try {
257
+ const data = await apiClient.post('/v1/pubsub/messages/assert-published', { filter })
258
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
259
+ } catch (error) {
260
+ return { content: [{ type: 'text', text: `Failed to assert message published: ${error.message}` }], isError: true }
261
+ }
262
+ }
263
+ )
264
+
265
+ server.tool(
266
+ 'pubsub_registry_register',
267
+ 'Register topics in the persistent Pub/Sub registry for a project. Topics persist across restarts.',
268
+ {
269
+ projectName: z.string().describe('Project name to register topics for'),
270
+ topics: z.array(z.any()).describe('Array of topic names (strings) or objects with { name, subscription }')
271
+ },
272
+ async ({ projectName, topics }) => {
273
+ try {
274
+ const data = await apiClient.post('/v1/pubsub/registry/register', { projectName, topics })
275
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
276
+ } catch (error) {
277
+ return { content: [{ type: 'text', text: `Failed to register topics: ${error.message}` }], isError: true }
278
+ }
279
+ }
280
+ )
281
+
282
+ server.tool(
283
+ 'pubsub_registry_list',
284
+ 'List registered topics from the persistent Pub/Sub registry. Optionally filter by project name.',
285
+ {
286
+ projectName: z.string().optional().describe('Optional project name to filter by')
287
+ },
288
+ async ({ projectName }) => {
289
+ try {
290
+ const body = {}
291
+ if (projectName !== undefined) body.projectName = projectName
292
+ const data = await apiClient.post('/v1/pubsub/registry/list', body)
293
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
294
+ } catch (error) {
295
+ return { content: [{ type: 'text', text: `Failed to list registry: ${error.message}` }], isError: true }
296
+ }
297
+ }
298
+ )
299
+
300
+ server.tool(
301
+ 'pubsub_registry_unregister',
302
+ 'Unregister topics for a project from the persistent Pub/Sub registry',
303
+ {
304
+ projectName: z.string().describe('Project name to unregister topics from'),
305
+ topicNames: z.array(z.string()).describe('Array of topic name strings to unregister')
306
+ },
307
+ async ({ projectName, topicNames }) => {
308
+ try {
309
+ const data = await apiClient.post('/v1/pubsub/registry/unregister', { projectName, topicNames })
310
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
311
+ } catch (error) {
312
+ return { content: [{ type: 'text', text: `Failed to unregister topics: ${error.message}` }], isError: true }
313
+ }
314
+ }
315
+ )
316
+ }
@@ -0,0 +1,146 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerRedisTools (server, apiClient) {
4
+ server.tool(
5
+ 'redis_test_connection',
6
+ 'Test the Redis connection',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/redis/connection/test')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'redis_info',
20
+ 'Get Redis server info including memory usage and db size',
21
+ {},
22
+ async () => {
23
+ try {
24
+ const data = await apiClient.post('/v1/redis/info')
25
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
26
+ } catch (error) {
27
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
28
+ }
29
+ }
30
+ )
31
+
32
+ server.tool(
33
+ 'redis_scan_keys',
34
+ 'Scan Redis keys matching a pattern (default: *)',
35
+ {
36
+ pattern: z.string().optional().describe('Key pattern to match (default: *)'),
37
+ cursor: z.string().optional().describe('Cursor for pagination'),
38
+ count: z.number().optional().describe('Approximate number of keys to return per scan')
39
+ },
40
+ async ({ pattern, cursor, count }) => {
41
+ try {
42
+ const data = await apiClient.post('/v1/redis/keys/scan', { pattern, cursor, count })
43
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
44
+ } catch (error) {
45
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
46
+ }
47
+ }
48
+ )
49
+
50
+ server.tool(
51
+ 'redis_get_key',
52
+ 'Get the value of a Redis key (auto-detects type: string, hash, list, set)',
53
+ {
54
+ key: z.string().describe('Redis key name')
55
+ },
56
+ async ({ key }) => {
57
+ try {
58
+ const data = await apiClient.post('/v1/redis/keys/get', { key })
59
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
60
+ } catch (error) {
61
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
62
+ }
63
+ }
64
+ )
65
+
66
+ server.tool(
67
+ 'redis_get_ttl',
68
+ 'Get the TTL (time-to-live) of a Redis key in seconds',
69
+ {
70
+ key: z.string().describe('Redis key name')
71
+ },
72
+ async ({ key }) => {
73
+ try {
74
+ const data = await apiClient.post('/v1/redis/keys/ttl', { key })
75
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
76
+ } catch (error) {
77
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
78
+ }
79
+ }
80
+ )
81
+
82
+ server.tool(
83
+ 'redis_delete_key',
84
+ 'Delete a specific Redis key',
85
+ {
86
+ key: z.string().describe('Redis key name')
87
+ },
88
+ async ({ key }) => {
89
+ try {
90
+ const data = await apiClient.post('/v1/redis/keys/delete', { key })
91
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
92
+ } catch (error) {
93
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
94
+ }
95
+ }
96
+ )
97
+
98
+ server.tool(
99
+ 'redis_update_key',
100
+ 'Set or update a Redis key value with optional TTL',
101
+ {
102
+ key: z.string().describe('Redis key name'),
103
+ value: z.string().describe('Value to set'),
104
+ ttl: z.number().optional().describe('Time-to-live in seconds')
105
+ },
106
+ async ({ key, value, ttl }) => {
107
+ try {
108
+ const data = await apiClient.post('/v1/redis/keys/update', { key, value, ttl })
109
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
110
+ } catch (error) {
111
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
112
+ }
113
+ }
114
+ )
115
+
116
+ server.tool(
117
+ 'redis_delete_all',
118
+ 'Delete all Redis keys (FLUSHDB)',
119
+ {},
120
+ async () => {
121
+ try {
122
+ const data = await apiClient.post('/v1/redis/keys/delete-all')
123
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
124
+ } catch (error) {
125
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
126
+ }
127
+ }
128
+ )
129
+
130
+ server.tool(
131
+ 'redis_wait_for_key',
132
+ 'Wait for a Redis key to match a condition',
133
+ {
134
+ condition: z.record(z.any()).describe('Condition to match against the key value'),
135
+ timeout: z.number().optional().describe('Timeout in milliseconds')
136
+ },
137
+ async ({ condition, timeout }) => {
138
+ try {
139
+ const data = await apiClient.post('/v1/redis/keys/wait-for', { condition, timeout })
140
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
141
+ } catch (error) {
142
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
143
+ }
144
+ }
145
+ )
146
+ }