From 0933f4b9722dd37dfdec34513961f40785e6f328 Mon Sep 17 00:00:00 2001 From: junseo2323 Date: Mon, 22 Jun 2026 16:22:38 +0900 Subject: [PATCH] fix: attribute MCP drafts and polish README --- README.md | 86 ++++++++++++++++++----------- app/api/mcp/route.ts | 5 +- src/mcp/server.ts | 12 +++- src/mcp/tools/draft-context-card.ts | 6 +- test/tools.test.ts | 21 +++++++ 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 245e8e2..27bbfea 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,56 @@ +
+ # ClaudexHub +### [๐ŸŒ claudexhub.fly.dev](https://claudexhub.fly.dev/) + +**Shared engineering memory for AI coding agents.** + +Solve a problem once. Turn it into a trusted Context Card.
+Let Claude Code, Codex, Cursor, and Antigravity find it next time. + [![CI](https://github.com/junseo2323/claudexHub/actions/workflows/ci.yml/badge.svg)](https://github.com/junseo2323/claudexHub/actions/workflows/ci.yml) +[![Node.js 20+](https://img.shields.io/badge/Node.js-20%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org/) +[![MCP](https://img.shields.io/badge/MCP-server-6C5CE7)](https://modelcontextprotocol.io/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) + +[Live app](https://claudexhub.fly.dev/) ยท +[Connect an agent](#connect-an-agent) ยท +[MCP tools](#mcp-tools) ยท +[Architecture](#architecture) ยท +[ํ•œ๊ตญ์–ด](./README.ko.md) -[ํ•œ๊ตญ์–ด README](./README.ko.md) +
-An **agent-first developer knowledge platform**. AI coding agents (Claude Code, -Codex, Cursor, Antigravity) read and write **Context Cards** โ€” structured problem-solving -units โ€” through an MCP server, so a fix solved once can be searched and reused -later instead of re-derived from scratch. +--- -ClaudexHub provides GitHub sign-in, API tokens, a remote MCP endpoint, and a -web app for searching, reviewing, and publishing shared engineering knowledge. +ClaudexHub is an **agent-first developer knowledge platform**. Coding agents +search and write structured **Context Cards** through MCP, preserving verified +fixes, evidence, confidence, reuse outcomes, and token savings across sessions +and tools. -## What's here +```text +problem solved โ†’ draft captured โ†’ human approved โ†’ fix reused โ†’ trust improved +``` + +## Why ClaudexHub? + +| Remember | Retrieve | Trust | Improve | +| --- | --- | --- | --- | +| Capture fixes from logs, diffs, commits, PRs, and conversations. | Hybrid FTS5 + vector search returns compact, agent-friendly briefs. | Secret redaction, human approval, evidence, and confidence scoring are built in. | Reuse feedback updates rankings, reputation, and estimated tokens saved. | -> ๐Ÿ“„ Product spec: [`docs/PLANNING.md`](./docs/PLANNING.md) ยท spec-vs-build gap -> analysis: [`docs/SPEC-GAP.md`](./docs/SPEC-GAP.md). +## Highlights -- **MCP server** exposing 7 tools over hosted HTTP and local stdio. -- **Local SQLite** store with **hybrid search**: FTS5 keyword + sqlite-vec - embedding similarity, fused into a confidence score. -- **Brief-first retrieval**: `search_context` returns compact briefs; full card - bodies are only fetched on demand to save tokens. -- **Redaction**: secrets (keys, JWTs, DB URLs, emails, โ€ฆ) are stripped before a - card is stored or published. -- **Human approval**: agents create *drafts*; publishing requires an explicit - approval step. -- A **web app** (`app/`, Next.js) for authentication, tokens, card authoring, - review, search, teams, profiles, and operational status. -- A **dev CLI** and **seed data** (20 example cards). +- **Hosted or local MCP** โ€” use seven tools over authenticated HTTP or local stdio. +- **Agent-efficient retrieval** โ€” search returns briefs first; full cards load only on demand. +- **Shared memory with provenance** โ€” attach repository, files, commits, issues, and raw evidence. +- **Safe publishing workflow** โ€” drafts remain private until approval and a second secret scan. +- **Multi-user web app** โ€” GitHub sign-in, API tokens, teams, profiles, notifications, and leaderboards. +- **Portable core** โ€” one TypeScript domain layer powers MCP, CLI, web, tests, and seed tooling. + +> Product documents: [Planning](./docs/PLANNING.md) ยท +> [Specification gaps](./docs/SPEC-GAP.md) ยท +> [Deployment](./DEPLOYMENT.md) ## MCP tools @@ -44,19 +66,19 @@ web app for searching, reviewing, and publishing shared engineering knowledge. ## Connect an agent -Run one command. A browser opens for GitHub sign-in, then the CLI creates a -hosted API token and registers ClaudexHub automatically: +Run one command. Your browser opens for GitHub sign-in, then the CLI creates an +API token and registers the hosted MCP server automatically: ```bash -npx -y --package https://github.com/junseo2323/claudexHub/releases/download/v0.3.0/claudexhub-0.3.0.tgz claudexhub connect claude -npx -y --package https://github.com/junseo2323/claudexHub/releases/download/v0.3.0/claudexhub-0.3.0.tgz claudexhub connect codex -npx -y --package https://github.com/junseo2323/claudexHub/releases/download/v0.3.0/claudexhub-0.3.0.tgz claudexhub connect cursor -npx -y --package https://github.com/junseo2323/claudexHub/releases/download/v0.3.0/claudexhub-0.3.0.tgz claudexhub connect antigravity +npx -y \ + --package https://github.com/junseo2323/claudexHub/releases/download/v0.3.0/claudexhub-0.3.0.tgz \ + claudexhub connect codex ``` -Use `connect all` to configure every supported agent. No JSON editing or local -database setup is required. See the live guide at -[claudexhub.fly.dev](https://claudexhub.fly.dev/). +Replace `codex` with `claude`, `cursor`, `antigravity`, or `all`. No JSON editing +or local database setup is required. + +> The command connects to `https://claudexhub.fly.dev/api/mcp`. ## Setup (from source) @@ -246,7 +268,7 @@ locally: { "mcpServers": { "claudexhub": { - "url": "https:///api/mcp", + "url": "https://claudexhub.fly.dev/api/mcp", "headers": { "Authorization": "Bearer clx_โ€ฆ" } } } diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index 1af09fa..2c42be6 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -29,11 +29,12 @@ async function handle(req: Request): Promise { const auth = req.headers.get("authorization") ?? ""; const token = auth.startsWith("Bearer ") ? auth.slice(7).trim() : ""; - if (!token || !verifyApiToken(token)) return rpcError(401, "unauthorized"); + const userId = token ? verifyApiToken(token) : undefined; + if (!userId) return rpcError(401, "unauthorized"); const db = getDb(); migrate(db); - const server = buildServer(db); + const server = buildServer(db, { userId }); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, // stateless: each request is self-contained enableJsonResponse: true, diff --git a/src/mcp/server.ts b/src/mcp/server.ts index dabd5f4..08ace29 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -15,11 +15,14 @@ import { } from "./tools/submit-for-approval.js"; import { recordFeedbackSchema, makeRecordFeedbackHandler } from "./tools/record-feedback.js"; import { markStaleSchema, makeMarkStaleHandler } from "./tools/mark-stale.js"; +import { UserRepository } from "../domain/users.js"; /** Build an McpServer with the 7 ClaudexHub tools registered against `db`. */ -export function buildServer(db: DB): McpServer { +export function buildServer(db: DB, context: { userId?: string } = {}): McpServer { const repo = new Repository(db); const search = new SearchService(db); + const authorUserId = context.userId; + const users = authorUserId ? new UserRepository(db) : undefined; const server = new McpServer( { @@ -79,7 +82,12 @@ export function buildServer(db: DB): McpServer { "via publish_context_card before it becomes searchable.", inputSchema: draftContextCardSchema, }, - makeDraftContextCardHandler(repo), + makeDraftContextCardHandler( + repo, + authorUserId && users + ? (cardId) => users.setCardAuthor(cardId, authorUserId) + : undefined, + ), ); server.registerTool( diff --git a/src/mcp/tools/draft-context-card.ts b/src/mcp/tools/draft-context-card.ts index 0deaad4..4decc04 100644 --- a/src/mcp/tools/draft-context-card.ts +++ b/src/mcp/tools/draft-context-card.ts @@ -26,7 +26,10 @@ export const draftContextCardSchema = { const inputObject = z.object(draftContextCardSchema); -export function makeDraftContextCardHandler(repo: Repository) { +export function makeDraftContextCardHandler( + repo: Repository, + assignAuthor?: (cardId: string) => void, +) { return async (args: z.infer) => { const problem = args.problem_summary ?? args.content ?? ""; if (!problem) { @@ -70,6 +73,7 @@ export function makeDraftContextCardHandler(repo: Repository) { // Redact card fields before storing anything. const { card: redactedInput, report: cardReport } = redactCard(rawInput); const created = await repo.createCard(redactedInput as CardInput); + assignAuthor?.(created.id); // Store the (redacted) raw evidence for provenance, and fold its findings // into the report so the human reviewer sees everything that was stripped. diff --git a/test/tools.test.ts b/test/tools.test.ts index d693a9a..66161cd 100644 --- a/test/tools.test.ts +++ b/test/tools.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, afterEach } from "vitest"; import type { DB } from "../src/db/connection.js"; import { Repository } from "../src/domain/repository.js"; +import { UserRepository } from "../src/domain/users.js"; import { makeDraftContextCardHandler } from "../src/mcp/tools/draft-context-card.js"; import { makePublishContextCardHandler } from "../src/mcp/tools/publish-context-card.js"; import { makeGetContextCardHandler } from "../src/mcp/tools/get-context-card.js"; @@ -67,6 +68,26 @@ describe("MCP tool handlers", () => { expect(ev?.commit_sha).toBe("a1b2c3d4e5"); }); + it("draft_context_card attributes hosted MCP drafts to the token owner", async () => { + db = freshDb(); + const repo = new Repository(db); + const users = new UserRepository(db); + const alice = users.getOrCreateLocal("alice"); + const draft = makeDraftContextCardHandler( + repo, + (cardId) => users.setCardAuthor(cardId, alice.id), + ); + + const res = await draft({ + source: "conversation", + problem_summary: "Leaderboard omitted MCP-created cards", + verified_fix: ["attribute the draft to the bearer token owner"], + }); + const out = parse(res); + + expect(users.getCardAuthorId(out.id)).toBe(alice.id); + }); + it("publish blocks when secrets remain in the card", async () => { db = freshDb(); const repo = new Repository(db);