Conversation
Collaborator
Author
|
@claude review this PR |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds an MCP-powered slash command system that lets users discover and invoke MCP tools via /... in the chat input, including a backend tool-listing endpoint, Convex persistence, and a frontend autocomplete/menu + forced tool dispatch flow.
Changes:
- Add FastAPI
/api/commands/listendpoint to list available MCP tools as slash commands. - Persist commands in Convex and associate command IDs to harness MCP servers.
- Add frontend slash-command UI/hook and send
forced_toolthrough the chat stream to force a specific tool on the first iteration.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/fastapi/app/services/openrouter.py | Adds optional tool_choice override support for OpenRouter requests. |
| packages/fastapi/app/routes/commands.py | New endpoint that converts MCP tools into slash-command payloads. |
| packages/fastapi/app/routes/chat.py | Supports forced_tool by setting OpenRouter tool_choice on the first iteration. |
| packages/fastapi/app/models.py | Adds forced_tool to chat requests and a request model for /commands/list. |
| packages/fastapi/app/main.py | Registers the new commands router. |
| packages/convex-backend/convex/schema.ts | Adds commands table and stores commandIds on harness MCP server entries. |
| packages/convex-backend/convex/harnesses.ts | Updates MCP server validator to include optional commandIds. |
| packages/convex-backend/convex/commands.ts | New Convex functions to upsert commands and fetch by IDs. |
| packages/convex-backend/convex/_generated/api.d.ts | Adds generated API typings for the new commands module. |
| apps/web/src/routes/onboarding.tsx | Syncs commands after harness creation and stores command IDs on MCP servers. |
| apps/web/src/routes/harnesses/$harnessId.tsx | Syncs commands after MCP server changes and stores command IDs on MCP servers. |
| apps/web/src/routes/chat/index.tsx | Loads stored commands, adds slash-command UI, and forwards forced_tool during streaming. |
| apps/web/src/lib/use-chat-stream.ts | Adds forced_tool to the stream request type. |
| apps/web/src/lib/mcp.ts | Adds types/utilities for command fetching and MCP server payload shaping. |
| apps/web/src/components/slash-commands.tsx | New hook + menu component implementing slash-command autocomplete and parsing. |
| apps/web/src/components/mcp-server-status.tsx | Triggers command refresh after OAuth reconnect. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+4
to
+8
| /** | ||
| * Upsert commands: insert new ones, update existing ones (matched by name + userId). | ||
| * Returns an array of command IDs in the same order as the input. | ||
| */ | ||
| export const upsert = mutation({ |
Comment on lines
+27
to
+31
| ctx.db | ||
| .query("commands") | ||
| .withIndex("by_name", (q) => q.eq("name", cmd.name)) | ||
| .unique(), | ||
| ), |
Comment on lines
+571
to
+577
| # Force a specific tool on the first iteration when forced_tool is set | ||
| tool_choice: dict | str | None = None | ||
| if body.forced_tool and iteration == 0 and tools: | ||
| tool_choice = { | ||
| "type": "function", | ||
| "function": {"name": body.forced_tool}, | ||
| } |
|
|
||
| const selectCommand = useCallback( | ||
| (cmd: SlashCommand) => { | ||
| setText(`/${cmd.tool} `); |
Comment on lines
+32
to
+39
| if (afterSlash === cmd.tool || afterSlash.startsWith(`${cmd.tool} `)) { | ||
| return { | ||
| toolName: cmd.name, | ||
| message: afterSlash.slice(cmd.tool.length).trim(), | ||
| }; | ||
| } | ||
| } | ||
|
|
Comment on lines
+3436
to
3440
| // For slash commands, send the cleaned message (without /command prefix) to the LLM | ||
| const llmContent = messageContent; | ||
|
|
||
| // Add the new user message (with any current attachments) | ||
| if (readyAttachments.length > 0) { |
Comment on lines
+49
to
+54
| name: v.string(), | ||
| server: v.string(), | ||
| tool: v.string(), | ||
| description: v.string(), | ||
| parametersJson: v.string(), | ||
| }).index("by_name", ["name"]), |
Comment on lines
+56
to
+60
| export const getByIds = query({ | ||
| args: { ids: v.array(v.id("commands")) }, | ||
| handler: async (ctx, args) => { | ||
| const results = await Promise.all(args.ids.map((id) => ctx.db.get(id))); | ||
| return results.filter(Boolean); |
Comment on lines
+222
to
+223
| * Returns the raw command list with $-prefixed keys stripped from parameters, | ||
| * or null if the fetch fails. |
Comment on lines
+1171
to
+1174
| tool: c?.tool, | ||
| description: c?.description, | ||
| parameters: JSON.parse(c?.parametersJson), | ||
| }))} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/to discover and invoke MCP tools directly from the chat input/commands/listendpoint fetches available tools from connected MCP servers and returns them as slash commandscommandstable) with upsert logic so the list stays in sync with MCP serversuseSlashCommandInputhook handles command parsing, filtering, keyboard navigation (Arrow keys, Enter, Tab, Escape), and forced tool call dispatch via the existing chat streamHow it works
/commands/listwith the harness's MCP server config//{tool_name}— the user adds a message and sendsforced_toolparameter so OpenRouter invokes the correct MCP toolFiles changed
slash-commands.tsx(new),mcp.ts,use-chat-stream.ts,chat/index.tsx,harnesses/$harnessId.tsx,onboarding.tsx,mcp-server-status.tsxroutes/commands.py(new),routes/chat.py,services/openrouter.py,models.py,main.pycommands.ts(new),schema.ts,harnesses.tsTest plan
/in chat input — dropdown appears with available MCP tools/search_courses) closes the dropdown