Conversation
WalkthroughA new POST route Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant aiRouter
participant mcpService
participant Driver
participant composeEmail
Client->>aiRouter: POST /do/:action (action, body, X-Connection-Id)
aiRouter->>mcpService: getDriverFromConnectionId(connectionId)
mcpService-->>aiRouter: Driver instance
aiRouter->>aiRouter: switch(action)
alt ListThreads
aiRouter->>Driver: listThreads("inbox")
Driver-->>aiRouter: threads metadata
else ComposeEmail
aiRouter->>composeEmail: composeEmail(prompt, subject, username, connectionId)
composeEmail-->>aiRouter: composed email body
else SendEmail
aiRouter->>Driver: sendEmail(to, subject, message, [], {})
Driver-->>aiRouter: send result
else Unknown action
aiRouter-->>Client: 400 Not implemented
end
aiRouter-->>Client: JSON response
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
apps/server/wrangler.jsonc (1)
70-70: Replace placeholder API key with proper configuration.The placeholder value
"1234567890"should be replaced with a valid ElevenLabs API key for local development, or documented as a configuration requirement.Consider adding a comment or documentation indicating this needs to be configured with an actual API key.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/server/src/routes/ai.ts(1 hunks)apps/server/src/services/mcp-service/mcp.ts(1 hunks)apps/server/wrangler.jsonc(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/server/src/routes/ai.ts (2)
apps/server/src/services/mcp-service/mcp.ts (1)
getDriverFromConnectionId(12-37)apps/server/src/trpc/routes/ai/compose.ts (1)
composeEmail(29-106)
🪛 Biome (1.9.4)
apps/server/src/routes/ai.ts
[error] 26-35: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 38-43: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 46-55: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (1)
apps/server/src/services/mcp-service/mcp.ts (1)
12-12: LGTM! Export enables AI router integration.The function export is necessary for the new AI router functionality while maintaining the existing logic and error handling.
| const action = c.req.param('action') as Tools; | ||
| const body = await c.req.json(); | ||
| console.log('[DEBUG] action', action, body); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add input validation for request body.
The request body is parsed without validation, which could lead to runtime errors if required fields are missing.
Consider using a schema validation library like Zod to validate the request body based on the action type:
const bodySchema = z.object({
prompt: z.string().optional(),
emailSubject: z.string().optional(),
to: z.array(z.object({
name: z.string().optional(),
email: z.string().email(),
})).optional(),
subject: z.string().optional(),
message: z.string().optional(),
});
const body = bodySchema.parse(await c.req.json());🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts around lines 15 to 17, the request body is
parsed without validation, which risks runtime errors if required fields are
missing. To fix this, define a Zod schema that matches the expected structure of
the request body, including optional and required fields as appropriate. Then,
replace the direct JSON parsing with a call to the schema's parse method to
validate and parse the input safely before using it.
| const newBody = await composeEmail({ | ||
| prompt: body.prompt, | ||
| emailSubject: body.emailSubject, | ||
| username: 'Nizar Abi Zaher', |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove hard-coded username.
The username 'Nizar Abi Zaher' is hard-coded and should be retrieved from the user's connection or profile data.
Consider retrieving the username from the database using the connection ID or from user authentication context.
🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts at line 41, replace the hard-coded username
'Nizar Abi Zaher' with a dynamic value retrieved from the user's connection or
authentication context. Modify the code to access the current user's profile or
session data to get the username, ensuring it reflects the actual connected user
instead of a fixed string.
| const result = await driver.create({ | ||
| to: body.to.map((to: any) => ({ | ||
| name: to.name ?? to.email, | ||
| email: to.email ?? 'founders@0.email', |
There was a problem hiding this comment.
Remove hard-coded fallback email address.
The fallback email 'founders@0.email' should not be hard-coded. This could lead to emails being sent to unintended recipients.
Either require a valid email or throw an error if none is provided:
- email: to.email ?? 'founders@0.email',
+ email: to.email,Add validation to ensure all recipients have valid email addresses before processing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| email: to.email ?? 'founders@0.email', | |
| email: to.email, |
🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts at line 49, remove the hard-coded fallback email
'founders@0.email'. Instead, add validation to check if to.email is present and
valid; if not, throw an error to prevent processing with an invalid or missing
email. This ensures no unintended recipients receive emails and enforces proper
input validation.
| switch (action) { | ||
| case Tools.ListThreads: | ||
| const threads = await Promise.all( | ||
| (await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) => | ||
| driver.get(thread.id).then((thread) => ({ | ||
| id: thread.latest?.id, | ||
| subject: thread.latest?.subject, | ||
| sender: thread.latest?.sender, | ||
| date: thread.latest?.receivedOn, | ||
| })), | ||
| ), | ||
| ); | ||
| return c.json({ success: true, result: threads }); | ||
| case Tools.ComposeEmail: | ||
| const newBody = await composeEmail({ | ||
| prompt: body.prompt, | ||
| emailSubject: body.emailSubject, | ||
| username: 'Nizar Abi Zaher', | ||
| connectionId, | ||
| }); | ||
| return c.json({ success: true, result: newBody }); | ||
| case Tools.SendEmail: | ||
| const result = await driver.create({ | ||
| to: body.to.map((to: any) => ({ | ||
| name: to.name ?? to.email, | ||
| email: to.email ?? 'founders@0.email', | ||
| })), | ||
| subject: body.subject, | ||
| message: body.message, | ||
| attachments: [], | ||
| headers: {}, | ||
| }); | ||
| return c.json({ success: true, result }); | ||
| default: | ||
| return c.json({ success: false, error: 'Not implemented' }, 400); | ||
| } |
There was a problem hiding this comment.
Fix switch case variable declarations to prevent scope bleed.
The static analysis correctly identifies that variable declarations in switch cases can be accessed by other cases, which is a potential source of bugs.
Wrap each case in a block to scope the variables properly:
- switch (action) {
- case Tools.ListThreads:
- const threads = await Promise.all(
+ switch (action) {
+ case Tools.ListThreads: {
+ const threads = await Promise.all(
(await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) =>
driver.get(thread.id).then((thread) => ({
id: thread.latest?.id,
subject: thread.latest?.subject,
sender: thread.latest?.sender,
date: thread.latest?.receivedOn,
})),
),
);
return c.json({ success: true, result: threads });
- case Tools.ComposeEmail:
- const newBody = await composeEmail({
+ }
+ case Tools.ComposeEmail: {
+ const newBody = await composeEmail({
prompt: body.prompt,
emailSubject: body.emailSubject,
username: 'Nizar Abi Zaher',
connectionId,
});
return c.json({ success: true, result: newBody });
- case Tools.SendEmail:
- const result = await driver.create({
+ }
+ case Tools.SendEmail: {
+ const result = await driver.create({
to: body.to.map((to: any) => ({
name: to.name ?? to.email,
email: to.email ?? 'founders@0.email',
})),
subject: body.subject,
message: body.message,
attachments: [],
headers: {},
});
return c.json({ success: true, result });
+ }
default:
return c.json({ success: false, error: 'Not implemented' }, 400);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| switch (action) { | |
| case Tools.ListThreads: | |
| const threads = await Promise.all( | |
| (await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) => | |
| driver.get(thread.id).then((thread) => ({ | |
| id: thread.latest?.id, | |
| subject: thread.latest?.subject, | |
| sender: thread.latest?.sender, | |
| date: thread.latest?.receivedOn, | |
| })), | |
| ), | |
| ); | |
| return c.json({ success: true, result: threads }); | |
| case Tools.ComposeEmail: | |
| const newBody = await composeEmail({ | |
| prompt: body.prompt, | |
| emailSubject: body.emailSubject, | |
| username: 'Nizar Abi Zaher', | |
| connectionId, | |
| }); | |
| return c.json({ success: true, result: newBody }); | |
| case Tools.SendEmail: | |
| const result = await driver.create({ | |
| to: body.to.map((to: any) => ({ | |
| name: to.name ?? to.email, | |
| email: to.email ?? 'founders@0.email', | |
| })), | |
| subject: body.subject, | |
| message: body.message, | |
| attachments: [], | |
| headers: {}, | |
| }); | |
| return c.json({ success: true, result }); | |
| default: | |
| return c.json({ success: false, error: 'Not implemented' }, 400); | |
| } | |
| switch (action) { | |
| case Tools.ListThreads: { | |
| const threads = await Promise.all( | |
| (await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) => | |
| driver.get(thread.id).then((thread) => ({ | |
| id: thread.latest?.id, | |
| subject: thread.latest?.subject, | |
| sender: thread.latest?.sender, | |
| date: thread.latest?.receivedOn, | |
| })), | |
| ), | |
| ); | |
| return c.json({ success: true, result: threads }); | |
| } | |
| case Tools.ComposeEmail: { | |
| const newBody = await composeEmail({ | |
| prompt: body.prompt, | |
| emailSubject: body.emailSubject, | |
| username: 'Nizar Abi Zaher', | |
| connectionId, | |
| }); | |
| return c.json({ success: true, result: newBody }); | |
| } | |
| case Tools.SendEmail: { | |
| const result = await driver.create({ | |
| to: body.to.map((to: any) => ({ | |
| name: to.name ?? to.email, | |
| email: to.email ?? 'founders@0.email', | |
| })), | |
| subject: body.subject, | |
| message: body.message, | |
| attachments: [], | |
| headers: {}, | |
| }); | |
| return c.json({ success: true, result }); | |
| } | |
| default: | |
| return c.json({ success: false, error: 'Not implemented' }, 400); | |
| } |
🧰 Tools
🪛 Biome (1.9.4)
[error] 26-35: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 38-43: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 46-55: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Unsafe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts between lines 24 and 59, the variables declared
inside each switch case are not scoped properly, which can cause them to be
accessible in other cases and lead to bugs. To fix this, wrap the code inside
each case block with curly braces {} to create a new block scope, ensuring
variables like threads, newBody, and result are confined to their respective
cases.

Implement AI Email Actions API Endpoint
Description
This PR adds a new
/do/:actionendpoint to the AI router that enables email-related functionality through the MCP service. The endpoint supports three main actions:Additionally, it exports the
getDriverFromConnectionIdfunction from the MCP service to make it accessible to the new endpoint and adds the required ElevenLabs API key to the environment variables.Type of Change
Areas Affected
Testing Done
Security Considerations
Checklist
Additional Notes
The new endpoint requires a valid connection ID in the X-Connection-Id header for authentication. It returns appropriate error responses when the connection ID is missing or invalid.