@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,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