@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.
- package/AGENTS.md +1090 -0
- package/README.md +607 -0
- package/build.mjs +92 -0
- package/dist/di.js +8 -0
- package/dist/di.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
- package/dist/frontend/components/CommandPalette/index.js +28 -0
- package/dist/frontend/components/CommandPalette/index.js.map +7 -0
- package/dist/frontend/constants.js +41 -0
- package/dist/frontend/constants.js.map +7 -0
- package/dist/frontend/hooks/index.js +13 -0
- package/dist/frontend/hooks/index.js.map +7 -0
- package/dist/frontend/hooks/useCommandPalette.js +1094 -0
- package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
- package/dist/frontend/hooks/useMcpTools.js +66 -0
- package/dist/frontend/hooks/useMcpTools.js.map +7 -0
- package/dist/frontend/hooks/usePageContext.js +48 -0
- package/dist/frontend/hooks/usePageContext.js.map +7 -0
- package/dist/frontend/hooks/useRecentActions.js +56 -0
- package/dist/frontend/hooks/useRecentActions.js.map +7 -0
- package/dist/frontend/hooks/useRecentTools.js +55 -0
- package/dist/frontend/hooks/useRecentTools.js.map +7 -0
- package/dist/frontend/index.js +35 -0
- package/dist/frontend/index.js.map +7 -0
- package/dist/frontend/types.js +1 -0
- package/dist/frontend/types.js.map +7 -0
- package/dist/frontend/utils/index.js +7 -0
- package/dist/frontend/utils/index.js.map +7 -0
- package/dist/frontend/utils/toolMatcher.js +95 -0
- package/dist/frontend/utils/toolMatcher.js.map +7 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/ai_assistant/acl.js +14 -0
- package/dist/modules/ai_assistant/acl.js.map +7 -0
- package/dist/modules/ai_assistant/api/chat/route.js +152 -0
- package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/health/route.js +27 -0
- package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/route/route.js +123 -0
- package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +60 -0
- package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/route.js +48 -0
- package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +192 -0
- package/dist/modules/ai_assistant/cli.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +11 -0
- package/dist/modules/ai_assistant/di.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/index.js +13 -0
- package/dist/modules/ai_assistant/index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/auth.js +87 -0
- package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
- package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
- package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
- package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
- package/dist/modules/ai_assistant/lib/http-server.js +367 -0
- package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
- package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
- package/dist/modules/ai_assistant/lib/types.js +1 -0
- package/dist/modules/ai_assistant/lib/types.js.map +7 -0
- package/package.json +108 -0
- package/src/di.ts +11 -0
- package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
- package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
- package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
- package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
- package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
- package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
- package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
- package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
- package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
- package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
- package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
- package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
- package/src/frontend/components/CommandPalette/index.ts +14 -0
- package/src/frontend/constants.ts +35 -0
- package/src/frontend/hooks/index.ts +5 -0
- package/src/frontend/hooks/useCommandPalette.ts +1389 -0
- package/src/frontend/hooks/useMcpTools.ts +73 -0
- package/src/frontend/hooks/usePageContext.ts +61 -0
- package/src/frontend/hooks/useRecentActions.ts +64 -0
- package/src/frontend/hooks/useRecentTools.ts +69 -0
- package/src/frontend/index.ts +39 -0
- package/src/frontend/types.ts +260 -0
- package/src/frontend/utils/index.ts +1 -0
- package/src/frontend/utils/toolMatcher.ts +127 -0
- package/src/index.ts +92 -0
- package/src/modules/ai_assistant/acl.ts +10 -0
- package/src/modules/ai_assistant/api/chat/route.ts +213 -0
- package/src/modules/ai_assistant/api/health/route.ts +30 -0
- package/src/modules/ai_assistant/api/route/route.ts +149 -0
- package/src/modules/ai_assistant/api/settings/route.ts +73 -0
- package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
- package/src/modules/ai_assistant/api/tools/route.ts +57 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +233 -0
- package/src/modules/ai_assistant/di.ts +9 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
- package/src/modules/ai_assistant/index.ts +11 -0
- package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
- package/src/modules/ai_assistant/lib/auth.ts +185 -0
- package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
- package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
- package/src/modules/ai_assistant/lib/http-server.ts +498 -0
- package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
- package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
- package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
- package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
- package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
- package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
- package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
- package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
- package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
- package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
- package/src/modules/ai_assistant/lib/types.ts +147 -0
- package/test-schema.ts +37 -0
- package/tsconfig.json +10 -0
- 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
|
+
}
|