Full application build: landing page, auth, chat with MCP tool use, harness management, and OAuth#8
Conversation
… Header component
…ing - should've been in the staging already
…ng related deps to apps/web
…d per conversation, add usersettings logic
Feat/landing page
Feat/login and onboarding
There was a problem hiding this comment.
Pull request overview
This PR delivers an end-to-end initial build of Harness: a TanStack Start + Clerk + Convex web app paired with a FastAPI backend that supports streaming chat, MCP tool execution, MCP server health checks, and MCP OAuth flows.
Changes:
- Added FastAPI streaming chat endpoint (OpenRouter) plus MCP client integration, health checks, and MCP OAuth routes.
- Introduced Convex schema + queries/mutations for harnesses, conversations, messages (parts/tool calls/usage), OAuth token storage, and user settings.
- Implemented core web UI routes and components for sign-in, harness management/editing, chat streaming UI, and supporting design system components.
Reviewed changes
Copilot reviewed 66 out of 81 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/fastapi/requirements.txt | Adds FastAPI backend dependencies for SSE + JWT verification/crypto. |
| packages/fastapi/app/services/openrouter.py | Implements OpenRouter streaming + non-streaming chat helpers. |
| packages/fastapi/app/services/mcp_client.py | Adds MCP JSON-RPC client with tool listing/calling + session/tool caching. |
| packages/fastapi/app/services/convex.py | Adds Convex HTTP API helpers for saving assistant messages and patching usage. |
| packages/fastapi/app/routes/mcp_oauth.py | Adds MCP OAuth start/callback/status/revoke endpoints. |
| packages/fastapi/app/routes/mcp_health.py | Adds MCP server health checks and prompt generation endpoint. |
| packages/fastapi/app/routes/chat.py | Adds SSE chat streaming route with tool-call loop and Convex persistence. |
| packages/fastapi/app/models.py | Adds Pydantic request models for chat + harness/MCP server config. |
| packages/fastapi/app/main.py | Adds app lifespan, shared httpx client, CORS, and route wiring. |
| packages/fastapi/app/dependencies.py | Adds FastAPI dependencies for shared http client + current user. |
| packages/fastapi/app/config.py | Adds settings validation and model mapping/thinking configuration. |
| packages/fastapi/app/auth.py | Adds Clerk JWT verification via JWKS. |
| packages/convex-backend/convex/userSettings.ts | Adds user settings query/mutation with defaults. |
| packages/convex-backend/convex/seed.ts | Adds seed/clear utilities for dev data. |
| packages/convex-backend/convex/schema.ts | Defines Convex schema (harnesses/conversations/messages/OAuth tokens/settings). |
| packages/convex-backend/convex/migrations.ts | Placeholder for future Convex migrations. |
| packages/convex-backend/convex/messages.ts | Adds message CRUD + internal assistant saves + usage patching. |
| packages/convex-backend/convex/mcpOAuthTokens.ts | Adds OAuth token storage/status APIs (public + internal). |
| packages/convex-backend/convex/harnesses.ts | Adds harness CRUD and suggested prompts persistence. |
| packages/convex-backend/convex/conversations.ts | Adds conversation CRUD and recency listing. |
| packages/convex-backend/convex/_generated/dataModel.d.ts | Updates generated Convex types to use schema-aware DataModel. |
| packages/convex-backend/convex/_generated/api.d.ts | Updates generated API typings for all Convex modules. |
| packages/backend/convex/_generated/server.js | Removes deprecated generated Convex backend artifacts. |
| packages/backend/convex/_generated/server.d.ts | Removes deprecated generated Convex backend artifacts. |
| packages/backend/convex/_generated/dataModel.d.ts | Removes deprecated generated Convex backend artifacts. |
| packages/backend/convex/_generated/api.js | Removes deprecated generated Convex backend artifacts. |
| packages/backend/convex/_generated/api.d.ts | Removes deprecated generated Convex backend artifacts. |
| apps/web/vite.config.ts | Updates Vite config (deps optimization, host allowances). |
| apps/web/tsconfig.json | Adds path alias for the Convex backend workspace package. |
| apps/web/src/styles.css | Adds Geist fonts, markdown/code highlighting styles, and theme tweaks. |
| apps/web/src/routes/sign-in.tsx | Adds a dedicated Clerk sign-in page. |
| apps/web/src/routes/harnesses/index.tsx | Adds harness listing UI with controls (start/stop/edit/delete). |
| apps/web/src/routes/harnesses/$harnessId.tsx | Adds harness editor UI including MCP server config and OAuth connect UX. |
| apps/web/src/routes/__root.tsx | Adds chromeless routes, Clerk appearance, tooltips, and toaster wiring. |
| apps/web/src/routeTree.gen.ts | Updates generated TanStack Router route tree for new routes. |
| apps/web/src/lib/use-chat-stream.ts | Adds client-side SSE streaming hook with events for tools/thinking/usage. |
| apps/web/src/lib/models.ts | Adds model selection list used by harness editor. |
| apps/web/src/lib/floating-dots.ts | Adds deterministic floating-dot background generator for landing UI. |
| apps/web/src/lib/floating-dots.test.ts | Adds vitest coverage for floating-dot generator determinism/ranges. |
| apps/web/src/integrations/clerk/provider.tsx | Removes prior Clerk provider wrapper (replaced by root route config). |
| apps/web/src/integrations/clerk/header-user.tsx | Removes prior header user widget (replaced by new chrome/UX). |
| apps/web/src/env.ts | Adds VITE_FASTAPI_URL env var. |
| apps/web/src/components/ui/tooltip.tsx | Adds tooltip primitives wrapper. |
| apps/web/src/components/ui/textarea.tsx | Adds textarea component. |
| apps/web/src/components/ui/skeleton.tsx | Adds skeleton loading component. |
| apps/web/src/components/ui/sheet.tsx | Adds sheet (dialog) component. |
| apps/web/src/components/ui/separator.tsx | Adds separator component. |
| apps/web/src/components/ui/select.tsx | Adds select component. |
| apps/web/src/components/ui/scroll-area.tsx | Adds scroll-area component. |
| apps/web/src/components/ui/input.tsx | Adds input component. |
| apps/web/src/components/ui/dropdown-menu.tsx | Adds dropdown menu component. |
| apps/web/src/components/ui/dialog.tsx | Adds dialog component. |
| apps/web/src/components/ui/checkbox.tsx | Adds checkbox component. |
| apps/web/src/components/ui/card.tsx | Adds card component. |
| apps/web/src/components/ui/button.tsx | Updates button styling variants/sizes. |
| apps/web/src/components/ui/badge.tsx | Adds badge component. |
| apps/web/src/components/ui/avatar.tsx | Adds avatar component. |
| apps/web/src/components/message-actions.tsx | Adds copy/regenerate/usage UI actions (display-mode aware). |
| apps/web/src/components/mcp-server-status.tsx | Adds MCP server status UI and OAuth reconnect UX. |
| apps/web/src/components/markdown-message.tsx | Adds markdown rendering with highlighting and copyable code blocks. |
| apps/web/src/components/harness-mark.tsx | Adds Harness SVG mark component. |
| apps/web/src/components/Header.tsx | Removes legacy header/nav component. |
| apps/web/public/favicon.svg | Adds Harness favicon. |
| apps/web/package.json | Adds dependencies for markdown/highlighting/fonts/motion/convex workspace. |
| apps/web/biome.json | Tweaks Biome lint config. |
| apps/web/.env.example | Adds example FastAPI backend URL env var. |
| .cursor/rules/tanstack-auth-guards.mdc | Documents TanStack Router auth-guard conventions. |
| .cursor/rules/convex-id-types.mdc | Documents Convex branded Id type conventions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| # Get issuer from unverified claims to fetch JWKS | ||
| unverified_claims = jwt.decode(token, options={"verify_signature": False}) | ||
| issuer = unverified_claims.get("iss", "") | ||
| if not issuer: | ||
| raise HTTPException(status_code=401, detail="Token missing issuer") | ||
|
|
||
| # Fetch JWKS and find matching key | ||
| jwks = await _get_jwks(http_client, issuer) | ||
| key = None |
There was a problem hiding this comment.
JWT verification currently trusts the iss claim from unverified token payload to decide where to fetch JWKS. A malicious client can mint their own RS256 JWT, set iss to an attacker-controlled domain with a matching JWKS, and have it validate successfully. Pin the expected Clerk issuer(s) in configuration (e.g., CLERK_ISSUER / JWKS URL) and reject tokens whose iss doesn't match, instead of discovering JWKS based on the token itself.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
…essage content. Updated database schema to index chat titles and chat messages. Changed managed harness icon
…ery (may not be best approach since we only load more once and usePaginatedQuery comes from convex/react while useQuery comes from @tanstack/react-query. Other changes are added yellow highlighting when jumping to a message from search
…s matches total queries. This bug occurred because convex doesn't know if it's inital fetch is everyting. Thus, functions that get the total query count were added in conversations.ts
…ack to original conversation.
… uses javascript's URL constructor to check for invlaid urls. Also makes sure there are no spaces in the url
…pagination. Index still paginates for now though. Stress testing has not been done yet
…nt version histories of an edited prompt, which implicitly forks a new conversation and retains all past histories. Used a rooted tree structure for conversation history management.
Feat/fork and edit
….tsx Keeps data-message-id for search highlighting from feat/chat-search, and React.Fragment wrapper + editingMessageId logic from staging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Feat/chat search
Feat/selectable mcps to staging
|
@claude review this |
|
Claude finished @DIodide's task in 1m 56s —— View job PR ReviewThis is a large, well-structured initial build. The architecture is solid and the code quality is generally high. Below are the findings, ordered by severity. Tasks
🔴 Critical — Security1. JWT issuer trusted from unverified token payload ( Already flagged by Copilot. The Fix: pin the expected Clerk issuer in 2. Audience verification disabled ( options={"verify_aud": False}Tokens issued for a different Clerk application (e.g. staging vs prod) will be accepted. Set the expected audience in settings and verify it. 3. window.opener.postMessage({json.dumps(message_data)}, "*");The OAuth callback result (including 4. SSRF via user-supplied MCP server URLs ( MCP server URLs from the client request are used directly in HTTP calls with no allowlist or validation. A user could configure 5. Session cache is shared across users (
🟠 High6. OAuth PKCE state stored in-process only (
7. The 8. Hardcoded personal ngrok URL in CORS config ( "https://aab7-140-180-240-231.ngrok-free.app",
"http://127.0.0.1:57609",These look like personal dev machine origins committed to the repo. CORS origins should come from environment config, not be hardcoded. Fix this → 🟡 Medium9. JWKS cache has no TTL and is never refreshed ( Keys are cached globally for the process lifetime. If Clerk rotates keys (which it can), the only recovery path is when a specific 10. Duplicate model entries in {"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4"},
{"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4 (Thinking)"},Two models share the same 11. The reverse map from sanitized → original tool name is a flat global dict. If two servers both export a tool that sanitizes to the same key, the second registration silently overwrites the first. The key should include the server name prefix (i.e. the full 12. No message content size limit ( The full message history is forwarded to OpenRouter on every request with no cap on total content length. A user with a very long conversation could trigger large token costs or upstream timeouts. Consider enforcing a max on 13. def _close_popup_html(...):
...
import jsonShould be a top-level import. 🔵 Low / Nits14. Thinking models are capped at 4096 tokens and others at 16384. This is reasonable but worth making configurable per-harness, especially since reasoning models typically need more tokens. 15. Sessions for MCP servers that go offline stay in the in-memory cache indefinitely. A background cleanup (e.g., evict on 400/404) or a TTL would prevent stale state accumulating. 16. Bearer auth tokens stored in plaintext in Convex (
17. userId: v.optional(v.string()),If this is the user who sent/received the message, it should probably be required (at least for user messages). An absent 18. Convex Deleting a harness leaves its associated conversations with a stale SummaryThe codebase is clean and well-organized for an initial full-stack build. The two highest-priority fixes are the JWT issuer pinning (item 1, already flagged by Copilot) and the session/tool cache sharing across users (item 5), which is a data isolation bug that could expose one user's OAuth-authenticated tool calls to another. The |
Summary
This PR represents the full initial build of Harness, bringing the application from skeleton to a functional AI chat
platform with MCP (Model Context Protocol) integration. Key additions include:
creation, TanStack Router auth guards for protected routes
visualization, chat interrupts, auto-scroll, and per-conversation state management
decoupled harnesses from conversations with last-used tracking
checks with UI status indicators, and error surfacing to frontend
token storage and refresh via Convex
service, structured logging, config validation, connection pooling via
httpx.AsyncClientOAuth tokens, and user settings; seed data; composite indexes for recency queries
management, Geist font, toast notifications, dark theme with Clerk appearance customization
thinking model variants with reasoning parameter support
Changes
packages/backendin favor ofpackages/convex-backendHeadercomponent and Clerk integration files, replaced with route-level auth guards and chromeless routesupport
PRs included