( args: Record<string, unknown>, authContext?: MCPAuthContext, )
| 183 | * Handle the chat_with_addie tool call |
| 184 | */ |
| 185 | export async function handleChatTool( |
| 186 | args: Record<string, unknown>, |
| 187 | authContext?: MCPAuthContext, |
| 188 | ): Promise<string> { |
| 189 | const message = args.message; |
| 190 | const history = args.history as ConversationMessage[] | undefined; |
| 191 | |
| 192 | if (!message || typeof message !== 'string') { |
| 193 | return JSON.stringify({ |
| 194 | error: 'message is required and must be a string', |
| 195 | }); |
| 196 | } |
| 197 | |
| 198 | // Check if knowledge search is ready |
| 199 | if (!isKnowledgeReady()) { |
| 200 | return JSON.stringify({ |
| 201 | error: 'Addie is still initializing. Please try again in a moment.', |
| 202 | }); |
| 203 | } |
| 204 | |
| 205 | try { |
| 206 | const client = getChatClient(); |
| 207 | |
| 208 | // Convert history to thread context format expected by AddieClaudeClient |
| 209 | const threadContext = history?.map((msg) => ({ |
| 210 | user: msg.role === 'user' ? 'user' : 'assistant', |
| 211 | text: msg.content, |
| 212 | })); |
| 213 | |
| 214 | // Cost cap (#2790 / #2950): bucket by the authenticated MCP sub |
| 215 | // when available. In prod, MCP auth is OAuth Bearer, so `sub` is |
| 216 | // the user or M2M client id — either is a legitimate per-caller |
| 217 | // bucket key. The anonymous tier (~$1/day) is the right ceiling |
| 218 | // for safe-tools-only chat traffic. If auth is disabled (dev |
| 219 | // mode) or the sub is missing, fall back to `uncapped: true` so |
| 220 | // local testing isn't budget-limited; prod MCP requires auth by |
| 221 | // default so the fallback doesn't expose a production surface. |
| 222 | const sub = authContext?.sub; |
| 223 | const costOption = sub && sub !== 'anonymous' |
| 224 | ? { costScope: { userId: `mcp:${sub}`, tier: 'anonymous' as const } } |
| 225 | : { uncapped: true as const }; |
| 226 | |
| 227 | const response = await client.processMessage( |
| 228 | message, |
| 229 | threadContext, |
| 230 | undefined, // No request-specific tools for anonymous |
| 231 | undefined, // No rules override |
| 232 | { maxIterations: 5, ...costOption }, // Lower iteration limit for anonymous users |
| 233 | ); |
| 234 | |
| 235 | const result: ChatResponse = { |
| 236 | response: response.text, |
| 237 | tools_used: response.tools_used, |
| 238 | }; |
| 239 | |
| 240 | return JSON.stringify(result); |
| 241 | } catch (error) { |
| 242 | logger.error({ error }, 'MCP Chat: Error processing message'); |
nothing calls this directly
no test coverage detected