@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,334 @@
1
+ /**
2
+ * API Discovery Tools
3
+ *
4
+ * Meta-tools for discovering and executing API endpoints via search.
5
+ * Replaces 400+ individual endpoint tools with 3 flexible tools.
6
+ */
7
+
8
+ import { z } from 'zod'
9
+ import { registerMcpTool } from './tool-registry'
10
+ import type { McpToolContext } from './types'
11
+ import {
12
+ getApiEndpoints,
13
+ getEndpointByOperationId,
14
+ searchEndpoints,
15
+ type ApiEndpoint,
16
+ } from './api-endpoint-index'
17
+
18
+ /**
19
+ * Load API discovery tools into the registry
20
+ */
21
+ export async function loadApiDiscoveryTools(): Promise<number> {
22
+ // Ensure endpoints are parsed and cached
23
+ const endpoints = await getApiEndpoints()
24
+ console.error(`[API Discovery] ${endpoints.length} endpoints available for discovery`)
25
+
26
+ // Register the three discovery tools
27
+ registerApiDiscoverTool()
28
+ registerApiExecuteTool()
29
+ registerApiSchemaTool()
30
+
31
+ return 3
32
+ }
33
+
34
+ /**
35
+ * api_discover - Find relevant API endpoints based on a query
36
+ */
37
+ function registerApiDiscoverTool(): void {
38
+ registerMcpTool(
39
+ {
40
+ name: 'api_discover',
41
+ description: `Find API endpoints in Open Mercato by keyword or action.
42
+
43
+ CAPABILITIES: This tool searches 400+ endpoints that can CREATE, READ, UPDATE, and DELETE
44
+ data across all modules (customers, products, orders, shipments, invoices, etc.).
45
+
46
+ SEARCH: Uses hybrid search (fulltext + vector) for best results. You can filter by HTTP method.
47
+
48
+ EXAMPLES:
49
+ - "customer endpoints" - Find all customer-related APIs
50
+ - "create order" - Find endpoint to create new orders
51
+ - "delete product" - Find endpoint to delete products (confirm with user before executing!)
52
+ - "update company name" - Find endpoint to modify companies
53
+ - "search customers" - Find search/list endpoints
54
+
55
+ Returns: method, path, description, and operationId for each match.
56
+ Use operationId with api_schema to get detailed parameter info before calling api_execute.`,
57
+ inputSchema: z.object({
58
+ query: z
59
+ .string()
60
+ .describe('Natural language query to find relevant endpoints (e.g., "customer list")'),
61
+ method: z
62
+ .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
63
+ .optional()
64
+ .describe('Filter by HTTP method'),
65
+ limit: z.number().optional().default(10).describe('Max results to return (default: 10)'),
66
+ }),
67
+ requiredFeatures: [], // Available to all authenticated users
68
+ handler: async (input: { query: string; method?: string; limit?: number }, ctx) => {
69
+ const { query, method, limit = 10 } = input
70
+
71
+ // Search for matching endpoints
72
+ const searchService = ctx.container?.resolve<any>('searchService')
73
+ const matches = await searchEndpoints(searchService, query, { limit, method })
74
+
75
+ if (matches.length === 0) {
76
+ return {
77
+ success: true,
78
+ message: 'No matching endpoints found. Try different search terms.',
79
+ endpoints: [],
80
+ }
81
+ }
82
+
83
+ // Format results for LLM consumption
84
+ const results = matches.map((endpoint) => ({
85
+ operationId: endpoint.operationId,
86
+ method: endpoint.method,
87
+ path: endpoint.path,
88
+ description: endpoint.description || endpoint.summary,
89
+ tags: endpoint.tags,
90
+ parameters: endpoint.parameters.map((p) => ({
91
+ name: p.name,
92
+ in: p.in,
93
+ required: p.required,
94
+ type: p.type,
95
+ })),
96
+ hasRequestBody: endpoint.requestBodySchema !== null,
97
+ }))
98
+
99
+ return {
100
+ success: true,
101
+ message: `Found ${results.length} matching endpoint(s)`,
102
+ endpoints: results,
103
+ hint: 'Use api_schema to get detailed parameter info, or api_execute to call an endpoint',
104
+ }
105
+ },
106
+ },
107
+ { moduleId: 'api' }
108
+ )
109
+ }
110
+
111
+ /**
112
+ * api_execute - Execute an API endpoint
113
+ */
114
+ function registerApiExecuteTool(): void {
115
+ registerMcpTool(
116
+ {
117
+ name: 'api_execute',
118
+ description: `Execute an API call to CREATE, READ, UPDATE, or DELETE data in Open Mercato.
119
+
120
+ WARNING: This tool can MODIFY and DELETE data. Be careful with mutations!
121
+
122
+ METHODS:
123
+ - GET: Read/search data (safe, no confirmation needed)
124
+ - POST: Create new records (confirm data with user first)
125
+ - PUT/PATCH: Update existing records (confirm changes with user)
126
+ - DELETE: Remove records permanently (ALWAYS confirm with user before executing!)
127
+
128
+ WORKFLOW:
129
+ 1. First use api_discover to find the right endpoint
130
+ 2. Use api_schema to understand required parameters
131
+ 3. For POST/PUT/PATCH/DELETE: Confirm with user what will be changed
132
+ 4. Execute the call with proper parameters
133
+
134
+ PARAMETERS:
135
+ - method: HTTP method (GET, POST, PUT, PATCH, DELETE)
136
+ - path: API path with parameters replaced (e.g., /customers/companies/123)
137
+ - query: Query parameters as key-value object (for GET requests and filtering)
138
+ - body: Request body for POST/PUT/PATCH (object with required fields)`,
139
+ inputSchema: z.object({
140
+ method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),
141
+ path: z
142
+ .string()
143
+ .describe('API path with parameters replaced (e.g., /customers/123, /orders)'),
144
+ query: z
145
+ .record(z.string(), z.string())
146
+ .optional()
147
+ .describe('Query parameters as key-value pairs'),
148
+ body: z
149
+ .record(z.string(), z.unknown())
150
+ .optional()
151
+ .describe('Request body for POST/PUT/PATCH requests'),
152
+ }),
153
+ requiredFeatures: [], // ACL checked at API level
154
+ handler: async (
155
+ input: {
156
+ method: string
157
+ path: string
158
+ query?: Record<string, string>
159
+ body?: Record<string, unknown>
160
+ },
161
+ ctx: McpToolContext
162
+ ) => {
163
+ const { method, path, query, body } = input
164
+
165
+ // Build URL
166
+ const baseUrl =
167
+ process.env.NEXT_PUBLIC_API_BASE_URL ||
168
+ process.env.NEXT_PUBLIC_APP_URL ||
169
+ process.env.APP_URL ||
170
+ 'http://localhost:3000'
171
+
172
+ // Ensure path starts with /api
173
+ const apiPath = path.startsWith('/api') ? path : `/api${path}`
174
+ let url = `${baseUrl}${apiPath}`
175
+
176
+ // Add query parameters
177
+ const queryParams = { ...query }
178
+
179
+ // Add context to query for GET, to body for mutations
180
+ if (method === 'GET') {
181
+ if (ctx.tenantId) queryParams.tenantId = ctx.tenantId
182
+ if (ctx.organizationId) queryParams.organizationId = ctx.organizationId
183
+ }
184
+
185
+ if (Object.keys(queryParams).length > 0) {
186
+ const separator = url.includes('?') ? '&' : '?'
187
+ url += separator + new URLSearchParams(queryParams).toString()
188
+ }
189
+
190
+ // Build body with context
191
+ let requestBody: Record<string, unknown> | undefined
192
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
193
+ requestBody = { ...body }
194
+ if (ctx.tenantId) requestBody.tenantId = ctx.tenantId
195
+ if (ctx.organizationId) requestBody.organizationId = ctx.organizationId
196
+ }
197
+
198
+ // Build headers
199
+ const headers: Record<string, string> = {
200
+ 'Content-Type': 'application/json',
201
+ }
202
+ if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret
203
+ if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId
204
+ if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId
205
+
206
+ // Execute request
207
+ try {
208
+ const response = await fetch(url, {
209
+ method,
210
+ headers,
211
+ body: requestBody ? JSON.stringify(requestBody) : undefined,
212
+ })
213
+
214
+ const responseText = await response.text()
215
+
216
+ if (!response.ok) {
217
+ return {
218
+ success: false,
219
+ statusCode: response.status,
220
+ error: `API error ${response.status}`,
221
+ details: tryParseJson(responseText),
222
+ }
223
+ }
224
+
225
+ return {
226
+ success: true,
227
+ statusCode: response.status,
228
+ data: tryParseJson(responseText),
229
+ }
230
+ } catch (error) {
231
+ return {
232
+ success: false,
233
+ error: error instanceof Error ? error.message : 'Request failed',
234
+ }
235
+ }
236
+ },
237
+ },
238
+ { moduleId: 'api' }
239
+ )
240
+ }
241
+
242
+ /**
243
+ * api_schema - Get detailed schema for an endpoint
244
+ */
245
+ function registerApiSchemaTool(): void {
246
+ registerMcpTool(
247
+ {
248
+ name: 'api_schema',
249
+ description: `Get detailed schema for an API endpoint before executing it.
250
+
251
+ IMPORTANT: Always check the schema before calling POST, PUT, PATCH, or DELETE endpoints
252
+ to understand what parameters are required.
253
+
254
+ USAGE:
255
+ - Use the operationId from api_discover results
256
+ - Returns: path parameters, query parameters, and request body schema
257
+ - Shows which fields are required vs optional
258
+ - Includes field types and descriptions
259
+
260
+ This helps you construct the correct api_execute call with all required data.`,
261
+ inputSchema: z.object({
262
+ operationId: z.string().describe('Operation ID from api_discover results'),
263
+ }),
264
+ requiredFeatures: [],
265
+ handler: async (input: { operationId: string }) => {
266
+ const endpoint = await getEndpointByOperationId(input.operationId)
267
+
268
+ if (!endpoint) {
269
+ return {
270
+ success: false,
271
+ error: `Endpoint not found: ${input.operationId}`,
272
+ hint: 'Use api_discover to find available endpoints',
273
+ }
274
+ }
275
+
276
+ return {
277
+ success: true,
278
+ endpoint: {
279
+ operationId: endpoint.operationId,
280
+ method: endpoint.method,
281
+ path: endpoint.path,
282
+ description: endpoint.description,
283
+ tags: endpoint.tags,
284
+ deprecated: endpoint.deprecated,
285
+ requiredFeatures: endpoint.requiredFeatures,
286
+ parameters: endpoint.parameters,
287
+ requestBodySchema: endpoint.requestBodySchema,
288
+ },
289
+ usage: buildUsageExample(endpoint),
290
+ }
291
+ },
292
+ },
293
+ { moduleId: 'api' }
294
+ )
295
+ }
296
+
297
+ /**
298
+ * Build usage example for an endpoint
299
+ */
300
+ function buildUsageExample(endpoint: ApiEndpoint): string {
301
+ const pathParams = endpoint.parameters.filter((p) => p.in === 'path')
302
+ const queryParams = endpoint.parameters.filter((p) => p.in === 'query')
303
+
304
+ let example = `api_execute with:\n method: "${endpoint.method}"\n path: "${endpoint.path}"`
305
+
306
+ if (pathParams.length > 0) {
307
+ example += `\n (replace ${pathParams.map((p) => `{${p.name}}`).join(', ')} in path)`
308
+ }
309
+
310
+ if (queryParams.length > 0) {
311
+ const queryExample = queryParams
312
+ .slice(0, 3)
313
+ .map((p) => `"${p.name}": "..."`)
314
+ .join(', ')
315
+ example += `\n query: { ${queryExample} }`
316
+ }
317
+
318
+ if (endpoint.requestBodySchema) {
319
+ example += `\n body: { ... } (see requestBodySchema above)`
320
+ }
321
+
322
+ return example
323
+ }
324
+
325
+ /**
326
+ * Try to parse JSON, return original string if fails
327
+ */
328
+ function tryParseJson(text: string): unknown {
329
+ try {
330
+ return JSON.parse(text)
331
+ } catch {
332
+ return text
333
+ }
334
+ }
@@ -0,0 +1,243 @@
1
+ import type {
2
+ SearchEntityConfig,
3
+ SearchResultPresenter,
4
+ IndexableRecord,
5
+ } from '@open-mercato/search/types'
6
+ import type { ApiEndpoint } from './api-endpoint-index'
7
+
8
+ /**
9
+ * Entity ID for API endpoints in the search index.
10
+ * Following the module:entity naming convention.
11
+ */
12
+ export const API_ENDPOINT_ENTITY_ID = 'ai_assistant:api_endpoint' as const
13
+
14
+ /**
15
+ * Tenant ID for global endpoints.
16
+ * API endpoints are not tenant-scoped, so we use a special "system" UUID.
17
+ * This is the nil UUID (all zeros) reserved for system-wide resources.
18
+ */
19
+ export const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000'
20
+
21
+ /**
22
+ * Default configuration for API endpoint search.
23
+ */
24
+ export const API_ENDPOINT_SEARCH_CONFIG = {
25
+ /** Maximum endpoints to return from search */
26
+ defaultLimit: 20,
27
+ /** Minimum relevance score (0-1) */
28
+ minScore: 0.15,
29
+ /** Strategies to use (in priority order) */
30
+ strategies: ['fulltext', 'vector'] as const,
31
+ } as const
32
+
33
+ /**
34
+ * Search entity configuration for API endpoints.
35
+ * This configures how endpoints are indexed and searched.
36
+ */
37
+ export const apiEndpointEntityConfig: SearchEntityConfig = {
38
+ entityId: API_ENDPOINT_ENTITY_ID,
39
+ enabled: true,
40
+ priority: 90, // High priority, just below tools
41
+
42
+ /**
43
+ * Build searchable content from an API endpoint.
44
+ */
45
+ buildSource: (ctx) => {
46
+ const endpoint = ctx.record as unknown as ApiEndpoint
47
+ const method = endpoint.method || ''
48
+ const path = endpoint.path || ''
49
+ const summary = endpoint.summary || ''
50
+ const description = endpoint.description || ''
51
+ const tags = endpoint.tags || []
52
+ const operationId = endpoint.operationId || ''
53
+
54
+ // Normalize path: replace slashes and braces with spaces for better search
55
+ const normalizedPath = path.replace(/[/{}\[\]]/g, ' ').trim()
56
+
57
+ // Build action words from method
58
+ const actionWords = getActionWordsForMethod(method)
59
+
60
+ // Build text content for embedding and fulltext search
61
+ const textContent = [
62
+ `${method} ${normalizedPath}`,
63
+ operationId.replace(/[_.-]/g, ' '),
64
+ summary,
65
+ description,
66
+ ...actionWords,
67
+ ...tags,
68
+ ]
69
+ .filter(Boolean)
70
+ .join(' | ')
71
+
72
+ return {
73
+ text: textContent,
74
+ fields: {
75
+ method,
76
+ path,
77
+ operationId,
78
+ summary,
79
+ description,
80
+ tags,
81
+ module: extractModuleFromPath(path),
82
+ deprecated: endpoint.deprecated ?? false,
83
+ requiredFeatures: endpoint.requiredFeatures ?? [],
84
+ },
85
+ presenter: {
86
+ title: `${method} ${path}`,
87
+ subtitle: summary || description.slice(0, 100),
88
+ icon: getIconForMethod(method),
89
+ },
90
+ checksumSource: { operationId, method, path, summary },
91
+ }
92
+ },
93
+
94
+ /**
95
+ * Format result for display in search UI.
96
+ */
97
+ formatResult: (ctx) => {
98
+ const endpoint = ctx.record as unknown as ApiEndpoint
99
+ return {
100
+ title: `${endpoint.method} ${endpoint.path}`,
101
+ subtitle: (endpoint.summary || endpoint.description || '').slice(0, 100),
102
+ icon: getIconForMethod(endpoint.method),
103
+ }
104
+ },
105
+
106
+ /**
107
+ * Field policy for search strategies.
108
+ */
109
+ fieldPolicy: {
110
+ searchable: ['method', 'path', 'operationId', 'summary', 'description', 'tags', 'module'],
111
+ hashOnly: [],
112
+ excluded: ['parameters', 'requestBodySchema', 'requiredFeatures'],
113
+ },
114
+ }
115
+
116
+ /**
117
+ * Convert an API endpoint to an indexable record for search.
118
+ *
119
+ * @param endpoint - The API endpoint to index
120
+ * @returns IndexableRecord ready for search indexing
121
+ */
122
+ export function endpointToIndexableRecord(endpoint: ApiEndpoint): IndexableRecord {
123
+ const method = endpoint.method
124
+ const path = endpoint.path
125
+ const normalizedPath = path.replace(/[/{}\[\]]/g, ' ').trim()
126
+ const actionWords = getActionWordsForMethod(method)
127
+
128
+ // Build text for vector embedding
129
+ const embeddingText = [
130
+ `${method} ${normalizedPath}`,
131
+ endpoint.operationId.replace(/[_.-]/g, ' '),
132
+ endpoint.summary,
133
+ endpoint.description,
134
+ ...actionWords,
135
+ ...endpoint.tags,
136
+ ]
137
+ .filter(Boolean)
138
+ .join(' | ')
139
+
140
+ const presenter: SearchResultPresenter = {
141
+ title: `${method} ${path}`,
142
+ subtitle: (endpoint.summary || endpoint.description || '').slice(0, 100),
143
+ icon: getIconForMethod(method),
144
+ }
145
+
146
+ return {
147
+ entityId: API_ENDPOINT_ENTITY_ID,
148
+ recordId: endpoint.operationId,
149
+ tenantId: GLOBAL_TENANT_ID,
150
+ organizationId: null,
151
+ fields: {
152
+ method,
153
+ path,
154
+ operationId: endpoint.operationId,
155
+ summary: endpoint.summary,
156
+ description: endpoint.description,
157
+ tags: endpoint.tags,
158
+ module: extractModuleFromPath(path),
159
+ deprecated: endpoint.deprecated,
160
+ requiredFeatures: endpoint.requiredFeatures,
161
+ },
162
+ presenter,
163
+ text: embeddingText,
164
+ checksumSource: {
165
+ operationId: endpoint.operationId,
166
+ method,
167
+ path,
168
+ summary: endpoint.summary,
169
+ },
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Get action words for HTTP method to improve semantic search.
175
+ */
176
+ function getActionWordsForMethod(method: string): string[] {
177
+ switch (method.toUpperCase()) {
178
+ case 'GET':
179
+ return ['read', 'fetch', 'get', 'list', 'retrieve', 'find', 'search', 'query']
180
+ case 'POST':
181
+ return ['create', 'add', 'new', 'insert', 'submit', 'register']
182
+ case 'PUT':
183
+ return ['update', 'replace', 'modify', 'set', 'change', 'edit']
184
+ case 'PATCH':
185
+ return ['update', 'modify', 'partial', 'change', 'edit', 'patch']
186
+ case 'DELETE':
187
+ return ['delete', 'remove', 'destroy', 'cancel', 'erase']
188
+ default:
189
+ return []
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Get icon for HTTP method.
195
+ */
196
+ function getIconForMethod(method: string): string {
197
+ switch (method.toUpperCase()) {
198
+ case 'GET':
199
+ return 'search'
200
+ case 'POST':
201
+ return 'plus'
202
+ case 'PUT':
203
+ case 'PATCH':
204
+ return 'edit'
205
+ case 'DELETE':
206
+ return 'trash'
207
+ default:
208
+ return 'api'
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Extract module name from API path.
214
+ */
215
+ function extractModuleFromPath(path: string): string | null {
216
+ // Common module patterns in paths
217
+ const match = path.match(/^\/([\w-]+)/)
218
+ if (match) {
219
+ return match[1]
220
+ }
221
+ return null
222
+ }
223
+
224
+ /**
225
+ * Compute a simple checksum for endpoint definitions.
226
+ * Used to detect changes and avoid unnecessary re-indexing.
227
+ */
228
+ export function computeEndpointsChecksum(
229
+ endpoints: Array<{ operationId: string; method: string; path: string }>
230
+ ): string {
231
+ const content = endpoints
232
+ .map((e) => `${e.operationId}:${e.method}:${e.path}`)
233
+ .sort()
234
+ .join('|')
235
+
236
+ // Simple hash using string code points
237
+ let hash = 0
238
+ for (let i = 0; i < content.length; i++) {
239
+ const char = content.charCodeAt(i)
240
+ hash = ((hash << 5) - hash + char) | 0
241
+ }
242
+ return hash.toString(16)
243
+ }