@open-mercato/ai-assistant 0.4.2-canary-c02407ff85

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 (193) hide show
  1. package/AGENTS.md +1090 -0
  2. package/README.md +607 -0
  3. package/build.mjs +92 -0
  4. package/dist/di.js +8 -0
  5. package/dist/di.js.map +7 -0
  6. package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
  7. package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
  8. package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
  9. package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
  10. package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
  11. package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
  12. package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
  13. package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
  14. package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
  15. package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
  16. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
  17. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
  18. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
  19. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
  20. package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
  21. package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
  22. package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
  23. package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
  24. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
  25. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
  26. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
  27. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
  28. package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
  29. package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
  30. package/dist/frontend/components/CommandPalette/index.js +28 -0
  31. package/dist/frontend/components/CommandPalette/index.js.map +7 -0
  32. package/dist/frontend/constants.js +41 -0
  33. package/dist/frontend/constants.js.map +7 -0
  34. package/dist/frontend/hooks/index.js +13 -0
  35. package/dist/frontend/hooks/index.js.map +7 -0
  36. package/dist/frontend/hooks/useCommandPalette.js +1094 -0
  37. package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
  38. package/dist/frontend/hooks/useMcpTools.js +66 -0
  39. package/dist/frontend/hooks/useMcpTools.js.map +7 -0
  40. package/dist/frontend/hooks/usePageContext.js +48 -0
  41. package/dist/frontend/hooks/usePageContext.js.map +7 -0
  42. package/dist/frontend/hooks/useRecentActions.js +56 -0
  43. package/dist/frontend/hooks/useRecentActions.js.map +7 -0
  44. package/dist/frontend/hooks/useRecentTools.js +55 -0
  45. package/dist/frontend/hooks/useRecentTools.js.map +7 -0
  46. package/dist/frontend/index.js +35 -0
  47. package/dist/frontend/index.js.map +7 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.js.map +7 -0
  50. package/dist/frontend/utils/index.js +7 -0
  51. package/dist/frontend/utils/index.js.map +7 -0
  52. package/dist/frontend/utils/toolMatcher.js +95 -0
  53. package/dist/frontend/utils/toolMatcher.js.map +7 -0
  54. package/dist/index.js +57 -0
  55. package/dist/index.js.map +7 -0
  56. package/dist/modules/ai_assistant/acl.js +14 -0
  57. package/dist/modules/ai_assistant/acl.js.map +7 -0
  58. package/dist/modules/ai_assistant/api/chat/route.js +152 -0
  59. package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
  60. package/dist/modules/ai_assistant/api/health/route.js +27 -0
  61. package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
  62. package/dist/modules/ai_assistant/api/route/route.js +123 -0
  63. package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
  64. package/dist/modules/ai_assistant/api/settings/route.js +60 -0
  65. package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
  66. package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
  67. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
  68. package/dist/modules/ai_assistant/api/tools/route.js +48 -0
  69. package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
  70. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
  71. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
  72. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
  73. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
  74. package/dist/modules/ai_assistant/cli.js +192 -0
  75. package/dist/modules/ai_assistant/cli.js.map +7 -0
  76. package/dist/modules/ai_assistant/di.js +11 -0
  77. package/dist/modules/ai_assistant/di.js.map +7 -0
  78. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
  79. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
  80. package/dist/modules/ai_assistant/index.js +13 -0
  81. package/dist/modules/ai_assistant/index.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
  83. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
  85. package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
  87. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
  89. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/auth.js +87 -0
  91. package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
  93. package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
  95. package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/http-server.js +367 -0
  97. package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
  99. package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
  101. package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
  103. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
  105. package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
  107. package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
  109. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
  111. package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
  113. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
  115. package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
  117. package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
  119. package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
  121. package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
  123. package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
  125. package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/types.js +1 -0
  127. package/dist/modules/ai_assistant/lib/types.js.map +7 -0
  128. package/package.json +108 -0
  129. package/src/di.ts +11 -0
  130. package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
  131. package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
  132. package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
  133. package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
  134. package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
  135. package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
  136. package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
  137. package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
  138. package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
  139. package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
  140. package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
  141. package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
  142. package/src/frontend/components/CommandPalette/index.ts +14 -0
  143. package/src/frontend/constants.ts +35 -0
  144. package/src/frontend/hooks/index.ts +5 -0
  145. package/src/frontend/hooks/useCommandPalette.ts +1389 -0
  146. package/src/frontend/hooks/useMcpTools.ts +73 -0
  147. package/src/frontend/hooks/usePageContext.ts +61 -0
  148. package/src/frontend/hooks/useRecentActions.ts +64 -0
  149. package/src/frontend/hooks/useRecentTools.ts +69 -0
  150. package/src/frontend/index.ts +39 -0
  151. package/src/frontend/types.ts +260 -0
  152. package/src/frontend/utils/index.ts +1 -0
  153. package/src/frontend/utils/toolMatcher.ts +127 -0
  154. package/src/index.ts +92 -0
  155. package/src/modules/ai_assistant/acl.ts +10 -0
  156. package/src/modules/ai_assistant/api/chat/route.ts +213 -0
  157. package/src/modules/ai_assistant/api/health/route.ts +30 -0
  158. package/src/modules/ai_assistant/api/route/route.ts +149 -0
  159. package/src/modules/ai_assistant/api/settings/route.ts +73 -0
  160. package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
  161. package/src/modules/ai_assistant/api/tools/route.ts +57 -0
  162. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
  163. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
  164. package/src/modules/ai_assistant/cli.ts +233 -0
  165. package/src/modules/ai_assistant/di.ts +9 -0
  166. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
  167. package/src/modules/ai_assistant/index.ts +11 -0
  168. package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
  169. package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
  170. package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
  171. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
  172. package/src/modules/ai_assistant/lib/auth.ts +185 -0
  173. package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
  174. package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
  175. package/src/modules/ai_assistant/lib/http-server.ts +498 -0
  176. package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
  177. package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
  178. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
  179. package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
  180. package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
  181. package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
  182. package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
  183. package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
  184. package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
  185. package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
  186. package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
  187. package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
  188. package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
  189. package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
  190. package/src/modules/ai_assistant/lib/types.ts +147 -0
  191. package/test-schema.ts +37 -0
  192. package/tsconfig.json +10 -0
  193. package/watch.mjs +6 -0
@@ -0,0 +1,308 @@
1
+ import type { SearchService } from '@open-mercato/search/service'
2
+ import type { SearchStrategyId, IndexableRecord } from '@open-mercato/search/types'
3
+ import type { McpToolRegistry, McpToolDefinition } from './types'
4
+ import {
5
+ TOOL_ENTITY_ID,
6
+ GLOBAL_TENANT_ID,
7
+ TOOL_SEARCH_CONFIG,
8
+ toolToIndexableRecord,
9
+ computeToolsChecksum,
10
+ } from './tool-index-config'
11
+
12
+ /**
13
+ * Result from tool search.
14
+ */
15
+ export type ToolSearchResult = {
16
+ /** Tool name (recordId) */
17
+ toolName: string
18
+ /** Relevance score */
19
+ score: number
20
+ /** Module that registered the tool */
21
+ moduleId?: string
22
+ /** Features required to use this tool */
23
+ requiredFeatures?: string[]
24
+ }
25
+
26
+ /**
27
+ * Status of available search strategies.
28
+ */
29
+ export type StrategyStatus = {
30
+ fulltext: boolean
31
+ vector: boolean
32
+ tokens: boolean
33
+ /** Ordered list of available strategies */
34
+ available: SearchStrategyId[]
35
+ /** Strategies that will be used by default */
36
+ default: SearchStrategyId[]
37
+ }
38
+
39
+ /**
40
+ * Result from tool indexing operation.
41
+ */
42
+ export type IndexingResult = {
43
+ indexed: number
44
+ skipped: number
45
+ strategies: SearchStrategyId[]
46
+ checksum: string
47
+ }
48
+
49
+ /**
50
+ * Options for searching tools.
51
+ */
52
+ export type ToolSearchOptions = {
53
+ /** Maximum results to return (default: 12) */
54
+ limit?: number
55
+ /** Override strategies to use */
56
+ strategies?: SearchStrategyId[]
57
+ /** User's features for ACL filtering */
58
+ userFeatures?: string[]
59
+ /** Is user a super admin (bypasses ACL) */
60
+ isSuperAdmin?: boolean
61
+ /** Minimum score threshold (default: 0.2) */
62
+ minScore?: number
63
+ }
64
+
65
+ /**
66
+ * Service for searching and indexing MCP tools using hybrid search strategies.
67
+ *
68
+ * Uses the existing search module infrastructure to provide:
69
+ * - Fulltext search (Meilisearch) when configured
70
+ * - Vector/semantic search (PgVector) when configured
71
+ * - Token-based search (PostgreSQL hash) as fallback
72
+ *
73
+ * Results are merged using Reciprocal Rank Fusion (RRF) with weights:
74
+ * - fulltext: 1.2
75
+ * - vector: 1.0
76
+ * - tokens: 0.8
77
+ */
78
+ export class ToolSearchService {
79
+ private lastIndexChecksum: string | null = null
80
+
81
+ constructor(
82
+ private readonly searchService: SearchService,
83
+ private readonly toolRegistry: McpToolRegistry
84
+ ) {}
85
+
86
+ /**
87
+ * Get status of available search strategies.
88
+ * This helps the AI understand which integrations are available.
89
+ */
90
+ async getStrategyStatus(): Promise<StrategyStatus> {
91
+ const [fulltext, vector, tokens] = await Promise.all([
92
+ this.searchService.isStrategyAvailable('fulltext'),
93
+ this.searchService.isStrategyAvailable('vector'),
94
+ this.searchService.isStrategyAvailable('tokens'),
95
+ ])
96
+
97
+ const available: SearchStrategyId[] = []
98
+ if (fulltext) available.push('fulltext')
99
+ if (vector) available.push('vector')
100
+ if (tokens) available.push('tokens')
101
+
102
+ const defaultStrategies = this.searchService.getDefaultStrategies()
103
+
104
+ return {
105
+ fulltext,
106
+ vector,
107
+ tokens,
108
+ available,
109
+ default: defaultStrategies.filter((s) => available.includes(s)),
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Search for relevant tools using hybrid strategies.
115
+ *
116
+ * @param query - User's search query or message
117
+ * @param options - Search options
118
+ * @returns Array of matching tools sorted by relevance
119
+ */
120
+ async searchTools(
121
+ query: string,
122
+ options: ToolSearchOptions = {}
123
+ ): Promise<ToolSearchResult[]> {
124
+ const {
125
+ limit = TOOL_SEARCH_CONFIG.defaultLimit,
126
+ strategies,
127
+ userFeatures = [],
128
+ isSuperAdmin = false,
129
+ minScore = TOOL_SEARCH_CONFIG.minScore,
130
+ } = options
131
+
132
+ // Use hybrid search with all available strategies
133
+ const searchResults = await this.searchService.search(query, {
134
+ tenantId: GLOBAL_TENANT_ID,
135
+ organizationId: null,
136
+ entityTypes: [TOOL_ENTITY_ID],
137
+ strategies,
138
+ limit: limit * 2, // Get extra results to account for ACL filtering
139
+ })
140
+
141
+ // Map to tool results and filter by ACL
142
+ const toolResults: ToolSearchResult[] = []
143
+
144
+ for (const result of searchResults) {
145
+ // Skip results below minimum score
146
+ if (result.score < minScore) continue
147
+
148
+ const metadata = result.metadata as Record<string, unknown> | undefined
149
+ const requiredFeatures = (metadata?.requiredFeatures as string[]) ?? []
150
+ const moduleId = metadata?.moduleId as string | undefined
151
+
152
+ // Filter by user's feature access
153
+ if (!this.hasFeatureAccess(requiredFeatures, userFeatures, isSuperAdmin)) {
154
+ continue
155
+ }
156
+
157
+ toolResults.push({
158
+ toolName: result.recordId,
159
+ score: result.score,
160
+ moduleId,
161
+ requiredFeatures,
162
+ })
163
+
164
+ // Stop when we have enough results
165
+ if (toolResults.length >= limit) break
166
+ }
167
+
168
+ return toolResults
169
+ }
170
+
171
+ /**
172
+ * Index all tools from the registry using available strategies.
173
+ * Uses checksum-based change detection to avoid unnecessary re-indexing.
174
+ *
175
+ * @param force - Force re-indexing even if checksum hasn't changed
176
+ * @returns Indexing result with statistics
177
+ */
178
+ async indexTools(force = false): Promise<IndexingResult> {
179
+ const tools = Array.from(this.toolRegistry.getTools().values())
180
+ const modules = this.getToolModules()
181
+
182
+ // Compute checksum to detect changes
183
+ const checksum = computeToolsChecksum(
184
+ tools.map((t) => ({ name: t.name, description: t.description }))
185
+ )
186
+
187
+ // Skip if checksum matches and not forced
188
+ if (!force && this.lastIndexChecksum === checksum) {
189
+ const status = await this.getStrategyStatus()
190
+ return {
191
+ indexed: 0,
192
+ skipped: tools.length,
193
+ strategies: status.available,
194
+ checksum,
195
+ }
196
+ }
197
+
198
+ // Build indexable records for all tools
199
+ const records: IndexableRecord[] = []
200
+ for (const tool of tools) {
201
+ const moduleId = modules.get(tool.name)
202
+ records.push(toolToIndexableRecord(tool, moduleId))
203
+ }
204
+
205
+ // Bulk index using available strategies
206
+ if (records.length > 0) {
207
+ await this.searchService.bulkIndex(records)
208
+ }
209
+
210
+ // Update checksum
211
+ this.lastIndexChecksum = checksum
212
+
213
+ const status = await this.getStrategyStatus()
214
+ return {
215
+ indexed: records.length,
216
+ skipped: 0,
217
+ strategies: status.available,
218
+ checksum,
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Purge all tool records from the search index.
224
+ * Useful for cleanup or complete re-indexing.
225
+ */
226
+ async purgeIndex(): Promise<void> {
227
+ await this.searchService.purge(TOOL_ENTITY_ID, GLOBAL_TENANT_ID)
228
+ this.lastIndexChecksum = null
229
+ }
230
+
231
+ /**
232
+ * Get tools by module ID from the registry.
233
+ */
234
+ getToolsByModule(moduleId: string): string[] {
235
+ return this.toolRegistry.listToolsByModule(moduleId)
236
+ }
237
+
238
+ /**
239
+ * Build a map of tool name to module ID.
240
+ */
241
+ private getToolModules(): Map<string, string> {
242
+ const modules = new Map<string, string>()
243
+
244
+ // Common module prefixes that indicate tool ownership
245
+ const modulePatterns = [
246
+ { pattern: /^customers_/, moduleId: 'customers' },
247
+ { pattern: /^sales_/, moduleId: 'sales' },
248
+ { pattern: /^catalog_/, moduleId: 'catalog' },
249
+ { pattern: /^staff_/, moduleId: 'staff' },
250
+ { pattern: /^resources_/, moduleId: 'resources' },
251
+ { pattern: /^planner/, moduleId: 'planner' },
252
+ { pattern: /^search_/, moduleId: 'search' },
253
+ { pattern: /^context_/, moduleId: 'context' },
254
+ { pattern: /^auth_/, moduleId: 'auth' },
255
+ { pattern: /^directory_/, moduleId: 'directory' },
256
+ { pattern: /^dashboards_/, moduleId: 'dashboards' },
257
+ { pattern: /^entities_/, moduleId: 'entities' },
258
+ { pattern: /^dictionaries_/, moduleId: 'dictionaries' },
259
+ { pattern: /^workflows_/, moduleId: 'workflows' },
260
+ { pattern: /^business_rules_/, moduleId: 'business_rules' },
261
+ { pattern: /^audit_logs_/, moduleId: 'audit_logs' },
262
+ { pattern: /^attachments_/, moduleId: 'attachments' },
263
+ { pattern: /^feature_toggles_/, moduleId: 'feature_toggles' },
264
+ { pattern: /^currencies_/, moduleId: 'currencies' },
265
+ { pattern: /^example_/, moduleId: 'example' },
266
+ { pattern: /^cli_/, moduleId: 'cli' },
267
+ ]
268
+
269
+ for (const toolName of this.toolRegistry.listToolNames()) {
270
+ for (const { pattern, moduleId } of modulePatterns) {
271
+ if (pattern.test(toolName)) {
272
+ modules.set(toolName, moduleId)
273
+ break
274
+ }
275
+ }
276
+ }
277
+
278
+ return modules
279
+ }
280
+
281
+ /**
282
+ * Check if user has required features to access a tool.
283
+ */
284
+ private hasFeatureAccess(
285
+ requiredFeatures: string[] | undefined,
286
+ userFeatures: string[],
287
+ isSuperAdmin: boolean
288
+ ): boolean {
289
+ if (isSuperAdmin) return true
290
+ if (!requiredFeatures?.length) return true
291
+
292
+ return requiredFeatures.every((required) => {
293
+ // Direct match
294
+ if (userFeatures.includes(required)) return true
295
+ // Wildcard match
296
+ if (userFeatures.includes('*')) return true
297
+
298
+ // Prefix wildcard match (e.g., 'customers.*' matches 'customers.people.view')
299
+ return userFeatures.some((feature) => {
300
+ if (feature.endsWith('.*')) {
301
+ const prefix = feature.slice(0, -2)
302
+ return required.startsWith(prefix + '.')
303
+ }
304
+ return false
305
+ })
306
+ })
307
+ }
308
+ }
@@ -0,0 +1,147 @@
1
+ import type { z } from 'zod'
2
+ import type { AwilixContainer } from 'awilix'
3
+
4
+ /**
5
+ * Execution context for MCP tool calls.
6
+ * Includes tenant/org scope, user info, and DI container.
7
+ */
8
+ export interface McpToolContext {
9
+ tenantId: string | null
10
+ organizationId: string | null
11
+ userId: string | null
12
+ container: AwilixContainer
13
+ userFeatures: string[]
14
+ isSuperAdmin: boolean
15
+ /** API key secret for authenticating HTTP requests to internal APIs */
16
+ apiKeySecret?: string
17
+ }
18
+
19
+ /**
20
+ * Tool definition that modules register.
21
+ */
22
+ export interface McpToolDefinition<TInput = unknown, TOutput = unknown> {
23
+ /** Unique tool identifier (e.g., 'customers.search', 'sales.createOrder') */
24
+ name: string
25
+ /** Human-readable description for the MCP client */
26
+ description: string
27
+ /** Zod schema for input validation */
28
+ inputSchema: z.ZodType<TInput>
29
+ /** Required features to execute this tool */
30
+ requiredFeatures?: string[]
31
+ /** The actual handler function */
32
+ handler: (input: TInput, context: McpToolContext) => Promise<TOutput>
33
+ }
34
+
35
+ /**
36
+ * Options for tool registration.
37
+ */
38
+ export interface ToolRegistrationOptions {
39
+ /** Module identifier (e.g., 'customers', 'sales') */
40
+ moduleId?: string
41
+ }
42
+
43
+ /**
44
+ * Tool registry interface for DI.
45
+ */
46
+ export interface McpToolRegistry {
47
+ registerTool<TInput, TOutput>(
48
+ tool: McpToolDefinition<TInput, TOutput>,
49
+ options?: ToolRegistrationOptions
50
+ ): void
51
+
52
+ getTools(): Map<string, McpToolDefinition>
53
+
54
+ getTool(name: string): McpToolDefinition | undefined
55
+
56
+ listToolNames(): string[]
57
+
58
+ listToolsByModule(moduleId: string): string[]
59
+ }
60
+
61
+ /**
62
+ * MCP server configuration.
63
+ */
64
+ export interface McpServerConfig {
65
+ /** Server name for MCP identification */
66
+ name: string
67
+ /** Server version */
68
+ version: string
69
+ /** Enable debug logging */
70
+ debug?: boolean
71
+ }
72
+
73
+ /**
74
+ * Options for creating an MCP server.
75
+ */
76
+ export interface McpServerOptions {
77
+ config: McpServerConfig
78
+ container: AwilixContainer
79
+ /** Manual context (used when --tenant/--org/--user flags provided) */
80
+ context?: {
81
+ tenantId: string | null
82
+ organizationId: string | null
83
+ userId: string | null
84
+ }
85
+ /** API key secret for authentication (alternative to manual context) */
86
+ apiKeySecret?: string
87
+ }
88
+
89
+ /**
90
+ * Result from tool execution.
91
+ */
92
+ export interface ToolExecutionResult {
93
+ success: boolean
94
+ result?: unknown
95
+ error?: string
96
+ errorCode?: 'NOT_FOUND' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'EXECUTION_ERROR'
97
+ }
98
+
99
+ // =============================================================================
100
+ // Client Types
101
+ // =============================================================================
102
+
103
+ /**
104
+ * Tool information returned by listTools().
105
+ */
106
+ export type ToolInfo = {
107
+ name: string
108
+ description: string
109
+ inputSchema: Record<string, unknown>
110
+ }
111
+
112
+ /**
113
+ * Result from callTool().
114
+ */
115
+ export type ToolResult = {
116
+ success: boolean
117
+ result?: unknown
118
+ error?: string
119
+ }
120
+
121
+ /**
122
+ * Common interface for all MCP client modes.
123
+ */
124
+ export interface McpClientInterface {
125
+ /** List available tools (filtered by API key's permissions) */
126
+ listTools(): Promise<ToolInfo[]>
127
+
128
+ /** Execute a tool */
129
+ callTool(name: string, args: unknown): Promise<ToolResult>
130
+
131
+ /** Close the client and release resources */
132
+ close(): Promise<void>
133
+ }
134
+
135
+ /**
136
+ * HTTP session data for tracking authenticated sessions.
137
+ */
138
+ export type McpHttpSession = {
139
+ sessionId: string
140
+ keyId: string
141
+ tenantId: string | null
142
+ organizationId: string | null
143
+ userId: string
144
+ features: string[]
145
+ isSuperAdmin: boolean
146
+ createdAt: Date
147
+ }
package/test-schema.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod'
2
+
3
+ // Test with string format for dates
4
+ const dateSchema = z.object({
5
+ createdAt: z.date()
6
+ })
7
+
8
+ // Check if there's a 'string' option for unrepresentable
9
+ const testOptions = [
10
+ { unrepresentable: 'string' },
11
+ { unrepresentable: 'error' },
12
+ { unrepresentable: 'any' },
13
+ { target: 'jsonSchema7' },
14
+ { target: 'openApi3' },
15
+ ]
16
+
17
+ for (const opt of testOptions) {
18
+ try {
19
+ const result = z.toJSONSchema(dateSchema, opt as any)
20
+ console.log('Option ' + JSON.stringify(opt) + ':')
21
+ console.log(' Result:', JSON.stringify(result, null, 2).slice(0, 400))
22
+ } catch (e: any) {
23
+ console.log('Option ' + JSON.stringify(opt) + ' error:', e.message)
24
+ }
25
+ }
26
+
27
+ // What about coerce.date - does it behave differently?
28
+ console.log('\n--- Testing z.coerce.date ---')
29
+ const coerceDateSchema = z.object({
30
+ createdAt: z.coerce.date()
31
+ })
32
+ try {
33
+ const result = z.toJSONSchema(coerceDateSchema, { unrepresentable: 'any' } as any)
34
+ console.log('coerce.date result:', JSON.stringify(result, null, 2))
35
+ } catch (e: any) {
36
+ console.log('coerce.date error:', e.message)
37
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig.json",
4
+ "compilerOptions": {
5
+ "noEmit": false,
6
+ "declaration": false,
7
+ "outDir": "./dist"
8
+ },
9
+ "include": ["src/**/*"]
10
+ }
package/watch.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { watch } from '../../scripts/watch.mjs'
2
+ import { dirname } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ watch(__dirname)