@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
package/README.md ADDED
@@ -0,0 +1,607 @@
1
+ # AI Assistant Module
2
+
3
+ AI-powered assistance capabilities for Open Mercato, featuring MCP (Model Context Protocol) server integration and OpenCode as the AI backend.
4
+
5
+ ## Features
6
+
7
+ - **MCP Server** - HTTP server exposing platform tools via Model Context Protocol
8
+ - **OpenCode Integration** - AI backend for natural language processing and tool execution
9
+ - **API Discovery Tools** - Meta-tools for dynamic API access (`api_discover`, `api_execute`, `api_schema`)
10
+ - **Two-Tier Authentication** - Server-level API key + user-level session tokens
11
+ - **Session Management** - Ephemeral API keys with configurable TTL for secure tool execution
12
+
13
+ ## Architecture Overview
14
+
15
+ ```mermaid
16
+ graph TB
17
+ subgraph "Web Application"
18
+ ChatAPI["/api/chat (SSE)"]
19
+ SessionMgr[Session Token Manager]
20
+ end
21
+
22
+ subgraph "OpenCode Server :4096"
23
+ OC[OpenCode Agent]
24
+ SSE[SSE /event endpoint]
25
+ Sessions[Session Manager]
26
+ end
27
+
28
+ subgraph "MCP HTTP Server :3001"
29
+ MCPEndpoint["/mcp endpoint"]
30
+ ServerAuth[Server Auth<br/>x-api-key]
31
+ UserAuth[User Auth<br/>_sessionToken]
32
+ Tools[Tool Registry]
33
+ end
34
+
35
+ subgraph "Open Mercato Platform"
36
+ Customers[Customers]
37
+ Sales[Sales]
38
+ Catalog[Catalog]
39
+ Search[Search]
40
+ end
41
+
42
+ ChatAPI -->|"Create session token"| SessionMgr
43
+ ChatAPI -->|"HTTP POST"| OC
44
+ ChatAPI <-->|"SSE Subscribe"| SSE
45
+ OC -->|"MCP Protocol"| MCPEndpoint
46
+ MCPEndpoint --> ServerAuth
47
+ ServerAuth --> UserAuth
48
+ UserAuth --> Tools
49
+ Tools --> Customers
50
+ Tools --> Sales
51
+ Tools --> Catalog
52
+ Tools --> Search
53
+ ```
54
+
55
+ ## Message Flow
56
+
57
+ ```mermaid
58
+ sequenceDiagram
59
+ participant User
60
+ participant API as /api/chat
61
+ participant DB as Database
62
+ participant OC as OpenCode :4096
63
+ participant MCP as MCP Server :3001
64
+
65
+ User->>API: POST {messages, sessionId?}
66
+
67
+ alt New Chat Session
68
+ API->>DB: createSessionApiKey(userId, roles)
69
+ DB-->>API: sessionToken (sess_xxx)
70
+ API->>API: Inject token into message
71
+ end
72
+
73
+ API-->>User: SSE: {type: 'thinking'}
74
+ API->>OC: POST /session/{id}/message
75
+
76
+ loop Tool Execution
77
+ OC->>MCP: Tool call with _sessionToken
78
+ MCP->>MCP: Validate x-api-key (Tier 1)
79
+ MCP->>DB: findApiKeyBySessionToken()
80
+ DB-->>MCP: User's roles & permissions
81
+ MCP->>MCP: Check tool permissions
82
+ MCP-->>OC: Tool result
83
+ end
84
+
85
+ OC-->>API: SSE: message.part.updated
86
+ API-->>User: SSE: {type: 'text', content: '...'}
87
+ OC-->>API: SSE: session.status = idle
88
+ API-->>User: SSE: {type: 'done', sessionId}
89
+ ```
90
+
91
+ ## Two-Tier Authentication
92
+
93
+ ```mermaid
94
+ graph TB
95
+ subgraph "Tier 1: Server Authentication"
96
+ Request[Incoming Request]
97
+ CheckKey{x-api-key header?}
98
+ ValidateKey[Compare with<br/>MCP_SERVER_API_KEY]
99
+ RejectServer[401 Unauthorized]
100
+ end
101
+
102
+ subgraph "Tier 2: User Authentication"
103
+ ExtractToken[Extract _sessionToken<br/>from tool args]
104
+ LookupToken[findApiKeyBySessionToken]
105
+ LoadACL[rbacService.loadAcl]
106
+ CheckPerms{Has required<br/>features?}
107
+ RejectUser[403 Insufficient Permissions]
108
+ end
109
+
110
+ subgraph "Execution"
111
+ Execute[Execute Tool<br/>with User Context]
112
+ Result[Return Result]
113
+ end
114
+
115
+ Request --> CheckKey
116
+ CheckKey -->|No| RejectServer
117
+ CheckKey -->|Yes| ValidateKey
118
+ ValidateKey -->|Invalid| RejectServer
119
+ ValidateKey -->|Valid| ExtractToken
120
+ ExtractToken --> LookupToken
121
+ LookupToken -->|Not found/expired| RejectUser
122
+ LookupToken -->|Found| LoadACL
123
+ LoadACL --> CheckPerms
124
+ CheckPerms -->|No| RejectUser
125
+ CheckPerms -->|Yes| Execute
126
+ Execute --> Result
127
+ ```
128
+
129
+ ## Session Token Flow
130
+
131
+ ```mermaid
132
+ sequenceDiagram
133
+ participant User
134
+ participant API as /api/chat
135
+ participant DB as api_keys table
136
+ participant OC as OpenCode
137
+ participant MCP as MCP Server
138
+
139
+ Note over User,MCP: New Chat Session
140
+ User->>API: POST /api/chat (first message)
141
+ API->>API: Get user's role IDs from JWT
142
+ API->>DB: createSessionApiKey({<br/>sessionToken,<br/>userId,<br/>userRoles,<br/>ttlMinutes: 120<br/>})
143
+ DB-->>API: Ephemeral key created
144
+ API->>OC: Message with session token
145
+
146
+ Note over OC,MCP: Tool Execution
147
+ OC->>MCP: Tool call with _sessionToken: "sess_xxx"
148
+ MCP->>MCP: Validate MCP_SERVER_API_KEY
149
+ MCP->>DB: findApiKeyBySessionToken()
150
+ DB-->>MCP: Session key with user's roles
151
+ MCP->>MCP: Load ACL from session key
152
+ MCP->>MCP: Check tool permissions
153
+ MCP-->>OC: Tool result
154
+ ```
155
+
156
+ ## API Discovery Tools
157
+
158
+ ```mermaid
159
+ flowchart LR
160
+ subgraph "User Query"
161
+ Q[User: "Find customers in New York"]
162
+ end
163
+
164
+ subgraph "AI Processing"
165
+ A1[api_discover<br/>'customers search']
166
+ A2[api_schema<br/>'/api/v1/customers']
167
+ A3[api_execute<br/>GET /api/v1/customers]
168
+ end
169
+
170
+ subgraph "Results"
171
+ R[15 customers found]
172
+ Response[AI: "I found 15 customers<br/>in New York..."]
173
+ end
174
+
175
+ Q --> A1
176
+ A1 -->|"Found endpoints"| A2
177
+ A2 -->|"Got schema"| A3
178
+ A3 --> R
179
+ R --> Response
180
+ ```
181
+
182
+ | Tool | Description |
183
+ |------|-------------|
184
+ | `api_discover` | Search for APIs by keyword, module, or HTTP method |
185
+ | `api_schema` | Get detailed schema for a specific endpoint |
186
+ | `api_execute` | Execute an API call with parameters |
187
+
188
+ ---
189
+
190
+ ## Quick Start
191
+
192
+ ### 1. Configure Environment Variables
193
+
194
+ ```bash
195
+ # MCP Server Authentication (required for production server)
196
+ MCP_SERVER_API_KEY=your-secure-server-key-here
197
+
198
+ # OpenCode URL (default: http://localhost:4096)
199
+ OPENCODE_URL=http://localhost:4096
200
+ ```
201
+
202
+ > **Note:** The `MCP_SERVER_API_KEY` must also be configured in OpenCode's `opencode.json` as the `x-api-key` header.
203
+
204
+ ### 2. Start the Stack
205
+
206
+ ```bash
207
+ # Start MCP server (choose one)
208
+ yarn mcp:dev # Development mode - API key auth only
209
+ yarn mcp:serve # Production mode - API key + session tokens
210
+
211
+ # Start OpenCode container
212
+ docker start opencode-mvp
213
+
214
+ # Verify connectivity
215
+ curl http://localhost:3001/health # MCP health
216
+ curl http://localhost:4096/global/health # OpenCode health
217
+ curl http://localhost:4096/mcp # MCP connection status
218
+ ```
219
+
220
+ ---
221
+
222
+ ## MCP Server Modes
223
+
224
+ The module provides two MCP HTTP server modes for different use cases:
225
+
226
+ ### Development Server (`yarn mcp:dev`)
227
+
228
+ A simplified HTTP server for local development. Authenticates once at startup using an API key.
229
+
230
+ **Features:**
231
+ - HTTP transport on port 3001 (configurable via `MCP_DEV_PORT`)
232
+ - Direct API key authentication (no session tokens needed)
233
+ - Tools filtered by API key permissions at startup
234
+ - Ideal for local testing and development
235
+
236
+ **Configuration (`.mcp.json`):**
237
+ ```json
238
+ {
239
+ "mcpServers": {
240
+ "open-mercato": {
241
+ "type": "http",
242
+ "url": "http://localhost:3001/mcp",
243
+ "headers": {
244
+ "x-api-key": "omk_your_api_key_here"
245
+ }
246
+ }
247
+ }
248
+ }
249
+ ```
250
+
251
+ **Running:**
252
+ ```bash
253
+ # API key from .mcp.json headers.x-api-key
254
+ yarn mcp:dev
255
+
256
+ # Or from environment variable
257
+ OPEN_MERCATO_API_KEY=omk_xxx yarn mcp:dev
258
+
259
+ # Custom port
260
+ MCP_DEV_PORT=3002 yarn mcp:dev
261
+
262
+ # Enable debug logging
263
+ MCP_DEBUG=true yarn mcp:dev
264
+ ```
265
+
266
+ **Endpoints:**
267
+ - `POST http://localhost:3001/mcp` - MCP protocol endpoint
268
+ - `GET http://localhost:3001/health` - Health check
269
+
270
+ ### Production Server (`yarn mcp:serve`)
271
+
272
+ A stateless HTTP server for production use. Requires two-tier authentication.
273
+
274
+ **Features:**
275
+ - HTTP transport on port 3001
276
+ - Two-tier authentication:
277
+ 1. Server-level: `x-api-key` header validated against `MCP_SERVER_API_KEY`
278
+ 2. User-level: `_sessionToken` parameter in each tool call
279
+ - Per-request permission checks based on user's session
280
+ - Ephemeral session tokens with 120-minute TTL
281
+
282
+ **Environment Variables:**
283
+ ```bash
284
+ MCP_SERVER_API_KEY=your-secure-server-key-here
285
+ ```
286
+
287
+ **How it works:**
288
+ 1. Chat API creates a session token for the logged-in user
289
+ 2. OpenCode connects with static `MCP_SERVER_API_KEY`
290
+ 3. Each tool call includes `_sessionToken` in arguments
291
+ 4. MCP server resolves user permissions from session token
292
+ 5. Tool executes with user's ACL context
293
+
294
+ ### Comparison
295
+
296
+ | Feature | Dev (`mcp:dev`) | Production (`mcp:serve`) |
297
+ |---------|-----------------|-------------------------|
298
+ | Authentication | API key only | API key + session tokens |
299
+ | Permission Check | Once at startup | Per tool call |
300
+ | Session Tokens | Not required | Required (`_sessionToken`) |
301
+ | Use Case | Local development | Web AI chat interface |
302
+ | User Context | From API key | From session token |
303
+
304
+ ---
305
+
306
+ ## OpenCode Integration
307
+
308
+ The AI Assistant uses **OpenCode** as the AI agent backend. OpenCode is a Go-based headless AI agent that connects to our MCP server for tool access.
309
+
310
+ ### OpenCode SSE Events
311
+
312
+ ```mermaid
313
+ graph LR
314
+ subgraph "Session Events"
315
+ SE1[session.status: busy]
316
+ SE2[session.status: idle]
317
+ SE3[session.status: waiting]
318
+ end
319
+
320
+ subgraph "Message Events"
321
+ ME1[message.updated]
322
+ ME2[message.part.updated]
323
+ end
324
+
325
+ subgraph "Question Events"
326
+ QE1[question.asked]
327
+ end
328
+
329
+ subgraph "Handlers"
330
+ H1[Emit 'thinking']
331
+ H2[Emit 'text' / 'tool-call']
332
+ H3[Emit 'question']
333
+ H4[Emit 'done']
334
+ end
335
+
336
+ SE1 --> H1
337
+ SE2 --> H4
338
+ SE3 --> H3
339
+ ME2 --> H2
340
+ QE1 --> H3
341
+ ```
342
+
343
+ ### OpenCode Configuration
344
+
345
+ ```json
346
+ {
347
+ "provider": "anthropic",
348
+ "model": "claude-sonnet-4-20250514",
349
+ "mcp": {
350
+ "open-mercato": {
351
+ "type": "sse",
352
+ "url": "http://host.docker.internal:3001/mcp",
353
+ "headers": {
354
+ "x-api-key": "your-secure-server-key-here"
355
+ }
356
+ }
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### OpenCode API Reference
362
+
363
+ | Endpoint | Method | Purpose |
364
+ |----------|--------|---------|
365
+ | `/event` | GET | SSE event stream |
366
+ | `/session` | POST | Create new session |
367
+ | `/session/{id}` | GET | Get session |
368
+ | `/session/{id}/message` | POST | Send message |
369
+ | `/question` | GET | List pending questions |
370
+ | `/question/{id}/reply` | POST | Answer question |
371
+ | `/global/health` | GET | Health check |
372
+ | `/mcp` | GET | MCP connection status |
373
+
374
+ ---
375
+
376
+ ## API Key Entity Extension
377
+
378
+ The `api_keys` table includes session-specific fields:
379
+
380
+ ```typescript
381
+ @Entity({ tableName: 'api_keys' })
382
+ export class ApiKey {
383
+ // ... existing fields ...
384
+
385
+ @Property({ name: 'session_token', type: 'text', nullable: true })
386
+ sessionToken?: string | null
387
+
388
+ @Property({ name: 'session_user_id', type: 'uuid', nullable: true })
389
+ sessionUserId?: string | null
390
+ }
391
+ ```
392
+
393
+ ### Session Key Service Functions
394
+
395
+ ```typescript
396
+ // Generate a unique session token
397
+ generateSessionToken(): string // Returns: "sess_xxxxxxxx..."
398
+
399
+ // Create an ephemeral API key for a chat session
400
+ createSessionApiKey(em, {
401
+ sessionToken: string,
402
+ userId: string,
403
+ userRoles: string[], // Role IDs (not names)
404
+ tenantId?: string | null,
405
+ organizationId?: string | null,
406
+ ttlMinutes?: number // Default: 120
407
+ }): Promise<{ keyId, secret, sessionToken }>
408
+
409
+ // Look up a session key
410
+ findApiKeyBySessionToken(em, sessionToken): Promise<ApiKey | null>
411
+
412
+ // Delete a session key
413
+ deleteSessionApiKey(em, sessionToken): Promise<void>
414
+ ```
415
+
416
+ ---
417
+
418
+ ## API Routes
419
+
420
+ | Route | Method | Description |
421
+ |-------|--------|-------------|
422
+ | `/api/chat` | POST | Streaming chat with AI (SSE) |
423
+ | `/api/tools` | GET | List all available tools |
424
+ | `/api/tools/execute` | POST | Execute a specific tool |
425
+ | `/api/settings` | GET/POST | AI provider configuration |
426
+ | `/api/mcp-servers` | GET/POST | External MCP server management |
427
+ | `/api/mcp-servers/[id]` | GET/PUT/DELETE | Single MCP server operations |
428
+
429
+ ### Chat API Request/Response
430
+
431
+ **Request**:
432
+ ```typescript
433
+ {
434
+ messages: Array<{ role: 'user' | 'assistant'; content: string }>
435
+ sessionId?: string // Optional, for continuing conversation
436
+ }
437
+ ```
438
+
439
+ **SSE Events**:
440
+ ```typescript
441
+ type ChatSSEEvent =
442
+ | { type: 'thinking' }
443
+ | { type: 'text'; content: string }
444
+ | { type: 'tool-call'; id: string; toolName: string; args: unknown }
445
+ | { type: 'tool-result'; id: string; toolName: string; result: unknown }
446
+ | { type: 'question'; question: OpenCodeQuestion }
447
+ | { type: 'metadata'; model?: string; provider?: string; tokens?: { input: number; output: number } }
448
+ | { type: 'done'; sessionId?: string }
449
+ | { type: 'error'; error: string }
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Directory Structure
455
+
456
+ ```
457
+ packages/ai-assistant/
458
+ ├── src/
459
+ │ ├── index.ts # Package exports
460
+ │ ├── di.ts # Dependency injection setup
461
+ │ ├── types.ts # Shared TypeScript types
462
+ │ │
463
+ │ ├── modules/ai_assistant/
464
+ │ │ ├── index.ts # Module exports
465
+ │ │ ├── acl.ts # Permission definitions
466
+ │ │ ├── cli.ts # CLI commands (mcp:serve, mcp:dev)
467
+ │ │ ├── di.ts # Module DI container
468
+ │ │ │
469
+ │ │ ├── lib/
470
+ │ │ │ ├── opencode-client.ts # OpenCode server client
471
+ │ │ │ ├── opencode-handlers.ts # Request handlers for OpenCode
472
+ │ │ │ ├── api-discovery-tools.ts # api_discover, api_execute, api_schema
473
+ │ │ │ ├── api-endpoint-index.ts # OpenAPI endpoint indexing
474
+ │ │ │ ├── http-server.ts # MCP HTTP server implementation
475
+ │ │ │ ├── mcp-server.ts # MCP stdio server implementation
476
+ │ │ │ ├── tool-registry.ts # Global tool registration
477
+ │ │ │ ├── tool-executor.ts # Tool execution logic
478
+ │ │ │ ├── tool-loader.ts # Discovers tools from modules
479
+ │ │ │ ├── mcp-tool-adapter.ts # Converts MCP tools to AI SDK format
480
+ │ │ │ └── types.ts # Module-specific types
481
+ │ │ │
482
+ │ │ ├── api/chat/
483
+ │ │ │ └── route.ts # POST /api/chat handler
484
+ │ │ │
485
+ │ │ └── frontend/components/
486
+ │ │ ├── AiAssistantSettingsPageClient.tsx
487
+ │ │ └── McpServersSection.tsx
488
+ │ │
489
+ │ └── frontend/
490
+ │ ├── index.ts # Frontend exports
491
+ │ ├── types.ts # Frontend TypeScript types
492
+ │ └── hooks/
493
+ │ └── useMcpTools.ts # Tool fetching and execution
494
+
495
+ ├── AGENTS.md # Technical guide for AI agents
496
+ └── README.md # This file
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Registering Tools
502
+
503
+ ```mermaid
504
+ flowchart LR
505
+ subgraph Modules
506
+ M1[customers/ai-tools.ts]
507
+ M2[sales/ai-tools.ts]
508
+ M3[search/ai-tools.ts]
509
+ end
510
+
511
+ subgraph Registration
512
+ R[registerMcpTool]
513
+ TR[Tool Registry]
514
+ end
515
+
516
+ subgraph Runtime
517
+ MCP[MCP Server]
518
+ ACL[ACL Check]
519
+ Exec[Execute Handler]
520
+ end
521
+
522
+ M1 -->|registerMcpTool| R
523
+ M2 -->|registerMcpTool| R
524
+ M3 -->|registerMcpTool| R
525
+ R --> TR
526
+ TR --> MCP
527
+ MCP --> ACL
528
+ ACL --> Exec
529
+ ```
530
+
531
+ Register tools via `registerMcpTool()`:
532
+
533
+ ```typescript
534
+ import { registerMcpTool } from '@open-mercato/ai-assistant/tools'
535
+ import { z } from 'zod'
536
+
537
+ registerMcpTool({
538
+ name: 'my_module_action',
539
+ description: 'Description of what this tool does',
540
+ inputSchema: z.object({
541
+ param1: z.string().describe('Description of param1'),
542
+ param2: z.number().optional(),
543
+ }),
544
+ requiredFeatures: ['my_module.action'],
545
+ handler: async (input, ctx) => {
546
+ const service = ctx.container.resolve('myService')
547
+ return { success: true, data: result }
548
+ }
549
+ }, { moduleId: 'my_module' })
550
+ ```
551
+
552
+ ---
553
+
554
+ ## Permissions (ACL)
555
+
556
+ | Feature ID | Description |
557
+ |------------|-------------|
558
+ | `ai_assistant.view` | View AI Assistant |
559
+ | `ai_assistant.settings.manage` | Manage AI settings |
560
+ | `ai_assistant.mcp.serve` | Start MCP Server |
561
+ | `ai_assistant.tools.list` | List MCP Tools |
562
+ | `ai_assistant.mcp_servers.view` | View MCP server configs |
563
+ | `ai_assistant.mcp_servers.manage` | Manage MCP server configs |
564
+
565
+ ---
566
+
567
+ ## Troubleshooting
568
+
569
+ | Symptom | Likely Cause | Fix |
570
+ |---------|--------------|-----|
571
+ | "Agent is working..." forever | OpenCode not responding | Check `curl http://localhost:4096/global/health` |
572
+ | "MCP connection failed" | MCP server not running | Start with `yarn mcp:serve` or `yarn mcp:dev` |
573
+ | Empty response | OpenCode not connected to MCP | Check `curl http://localhost:4096/mcp` |
574
+ | "Unauthorized" error | Missing/invalid API key | Check x-api-key in opencode.json matches MCP_SERVER_API_KEY |
575
+ | "Session expired" errors | Session token TTL exceeded | Start new chat session (creates new 120-min token) |
576
+ | Tools fail with UNAUTHORIZED | Missing _sessionToken | Verify AI is passing token in tool args |
577
+ | "Insufficient permissions" | User lacks required features | Check user's role assignments |
578
+
579
+ ---
580
+
581
+ ## Technical Notes
582
+
583
+ ### Zod 4 Schema Handling
584
+
585
+ The module includes handling for Zod 4 schemas with the Vercel AI SDK. See [AGENTS.md](./AGENTS.md) for implementation details.
586
+
587
+ ### Docker Configuration
588
+
589
+ ```yaml
590
+ # docker-compose.yml
591
+ services:
592
+ opencode:
593
+ build: ./docker/opencode
594
+ container_name: opencode-mvp
595
+ ports:
596
+ - "4096:4096"
597
+ environment:
598
+ - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
599
+ volumes:
600
+ - ./docker/opencode/opencode.json:/root/.opencode/opencode.json
601
+ ```
602
+
603
+ ---
604
+
605
+ ## License
606
+
607
+ Proprietary - Open Mercato
package/build.mjs ADDED
@@ -0,0 +1,92 @@
1
+ import * as esbuild from 'esbuild'
2
+ import { glob } from 'glob'
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs'
4
+ import { dirname, join, relative } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+
9
+ const entryPoints = await glob(join(__dirname, 'src/**/*.{ts,tsx}'), {
10
+ ignore: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx']
11
+ })
12
+
13
+ // Plugin to add .js extension to relative imports
14
+ const addJsExtension = {
15
+ name: 'add-js-extension',
16
+ setup(build) {
17
+ build.onEnd(async (result) => {
18
+ if (result.errors.length > 0) return
19
+ const outputFiles = await glob(join(__dirname, 'dist/**/*.js'))
20
+ for (const file of outputFiles) {
21
+ const fileDir = dirname(file)
22
+ let content = readFileSync(file, 'utf-8')
23
+ // Add .js to relative imports that don't have an extension
24
+ content = content.replace(
25
+ /from\s+["'](\.[^"']+)["']/g,
26
+ (match, path) => {
27
+ if (path.endsWith('.js') || path.endsWith('.json')) return match
28
+ // Check if it's a directory with index.js
29
+ const resolvedPath = join(fileDir, path)
30
+ if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
31
+ return `from "${path}/index.js"`
32
+ }
33
+ return `from "${path}.js"`
34
+ }
35
+ )
36
+ content = content.replace(
37
+ /import\s*\(\s*["'](\.[^"']+)["']\s*\)/g,
38
+ (match, path) => {
39
+ if (path.endsWith('.js') || path.endsWith('.json')) return match
40
+ // Check if it's a directory with index.js
41
+ const resolvedPath = join(fileDir, path)
42
+ if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
43
+ return `import("${path}/index.js")`
44
+ }
45
+ return `import("${path}.js")`
46
+ }
47
+ )
48
+ // Handle side-effect imports: import "./path" (no from clause)
49
+ content = content.replace(
50
+ /import\s+["'](\.[^"']+)["'];/g,
51
+ (match, path) => {
52
+ if (path.endsWith('.js') || path.endsWith('.json')) return match
53
+ // Check if it's a directory with index.js
54
+ const resolvedPath = join(fileDir, path)
55
+ if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
56
+ return `import "${path}/index.js";`
57
+ }
58
+ return `import "${path}.js";`
59
+ }
60
+ )
61
+ writeFileSync(file, content)
62
+ }
63
+ })
64
+ }
65
+ }
66
+
67
+ const outdir = join(__dirname, 'dist')
68
+
69
+ await esbuild.build({
70
+ entryPoints,
71
+ outdir,
72
+ outbase: join(__dirname, 'src'),
73
+ format: 'esm',
74
+ platform: 'node',
75
+ target: 'node18',
76
+ sourcemap: true,
77
+ jsx: 'automatic',
78
+ plugins: [addJsExtension],
79
+ })
80
+
81
+ // Copy JSON files from src to dist
82
+ const jsonFiles = await glob(join(__dirname, 'src/**/*.json'), {
83
+ ignore: ['**/node_modules/**']
84
+ })
85
+ for (const jsonFile of jsonFiles) {
86
+ const relativePath = relative(join(__dirname, 'src'), jsonFile)
87
+ const destPath = join(outdir, relativePath)
88
+ mkdirSync(dirname(destPath), { recursive: true })
89
+ copyFileSync(jsonFile, destPath)
90
+ }
91
+
92
+ console.log('ai-assistant built successfully')
package/dist/di.js ADDED
@@ -0,0 +1,8 @@
1
+ async function register(container) {
2
+ const { register: registerModule } = await import("./modules/ai_assistant/di.js");
3
+ registerModule(container);
4
+ }
5
+ export {
6
+ register
7
+ };
8
+ //# sourceMappingURL=di.js.map