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.
+
[](https://github.com/junseo2323/claudexHub/actions/workflows/ci.yml)
+[](https://nodejs.org/)
+[](https://modelcontextprotocol.io/)
+[](./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);