The backend API for getctx.org — an open registry for discovering, publishing, and installing Claude Code skills, MCP servers, and CLI tools.
ctx install @anthropic/claude-skill # that's it
Built with Hono on Cloudflare Workers. Zero cold start, globally distributed.
AI coding agents (Claude Code, Cursor, Windsurf, etc.) need a shared way to discover and install tools. ctx provides:
- A package registry for skills, MCP servers, and CLI tools
- One-command install that auto-configures any supported agent
- Hybrid search (FTS + vector embeddings) to find the right tool
- An open protocol —
GET /:fullName.ctxreturns plain-text instructions any agent can parse
# Clone and install
git clone https://github.com/ctx-hq/registry.git && cd registry
pnpm install
# Set up Cloudflare resources
cp wrangler.toml.example wrangler.toml
# Edit wrangler.toml — fill in your D1 database_id
# Create local database and start dev server
pnpm db:migrate
pnpm dev- Node.js 22+, pnpm 10+
- Wrangler CLI (
pnpm add -g wrangler) - A Cloudflare account (free plan works)
-
Copy config template:
cp wrangler.toml.example wrangler.toml
-
Create Cloudflare resources (first time only):
wrangler d1 create ctx-registry # Copy the database_id into wrangler.toml wrangler r2 bucket create ctx-formulas -
Set secrets (for GitHub OAuth):
wrangler secret put GITHUB_CLIENT_SECRET
-
Apply migrations and run:
pnpm db:migrate pnpm dev
| Command | Description |
|---|---|
pnpm dev |
Local dev server (port 8787) |
pnpm test |
Run test suite (Vitest) |
pnpm typecheck |
TypeScript type checking |
pnpm db:migrate |
Apply D1 migrations locally |
pnpm deploy |
Deploy to Cloudflare Workers |
Pushes to main trigger automatic deployment via GitHub Actions. Required secrets:
| Secret | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN |
Wrangler deploy authentication |
D1_DATABASE_ID |
D1 database identifier |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/packages |
— | List packages (filter: type, category; sort: downloads, created) |
| GET | /v1/packages/:fullName |
— | Package details with version history and categories |
| GET | /v1/packages/:fullName/versions |
— | List all versions |
| GET | /v1/packages/:fullName/versions/:version |
— | Version detail (manifest, readme, publisher username) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/search?q=&mode= |
— | Search packages (mode: fts, vector, hybrid) |
| POST | /v1/resolve |
— | Bulk version constraint resolution |
| GET | /v1/packages/:fullName/resolve/:constraint |
— | Resolve single version constraint |
| GET | /:fullName.ctx |
— | Agent-readable install instructions (plain text) |
| GET | /v1/categories |
— | List all categories with package counts |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/publish |
Bearer | Publish a version (multipart: manifest + archive) |
| POST | /v1/yank/:fullName/:version |
Bearer | Yank a version |
| GET | /v1/download/:fullName/:version |
— | Download formula archive |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/auth/device |
— | Start device authorization flow |
| POST | /v1/auth/token |
— | Poll for access token |
| POST | /v1/auth/github |
— | Exchange GitHub OAuth code for token |
| GET | /v1/me |
Bearer | Current user profile |
| GET | /v1/me/tokens |
Bearer | List API tokens (never exposes token values) |
| POST | /v1/me/tokens |
Bearer | Create a named token (optional: expires_in_days) |
| DELETE | /v1/me/tokens/:id |
Bearer | Revoke a token |
| DELETE | /v1/me |
Bearer | Delete account (anonymize PII, reassign packages) |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/orgs |
Bearer | Create organization |
| GET | /v1/orgs/:name |
— | Org details |
| GET | /v1/orgs/:name/members |
Bearer | List members (members only) |
| POST | /v1/orgs/:name/members |
Bearer | Add member (owner/admin only) |
| DELETE | /v1/orgs/:name/members/:username |
Bearer | Remove member (owner only) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/scanner/sources |
Bearer | List scanner sources |
| GET | /v1/scanner/candidates |
Bearer | List discovered candidates |
| GET | /v1/scanner/candidates/:id |
Bearer | Candidate detail |
| POST | /v1/scanner/run |
Admin | Trigger manual scan |
| POST | /v1/scanner/candidates/:id/approve |
Admin | Approve and import |
| POST | /v1/scanner/candidates/:id/reject |
Admin | Reject candidate |
| GET | /v1/scanner/stats |
Bearer | Scanner statistics |
src/
├── index.ts # Entry point, middleware, error handling, cron
├── bindings.ts # Cloudflare binding type definitions
├── models/types.ts # Shared TypeScript interfaces
├── routes/ # HTTP handlers
│ ├── auth.ts # OAuth, tokens, account deletion
│ ├── packages.ts # Package CRUD
│ ├── search.ts # FTS + vector hybrid search
│ ├── publish.ts # Package publishing
│ ├── resolve.ts # Bulk version resolution
│ ├── versions.ts # Single version resolution
│ ├── download.ts # Archive downloads
│ ├── orgs.ts # Organization management
│ ├── scanner.ts # Package discovery pipeline
│ ├── agent.ts # /:fullName.ctx agent endpoint
│ ├── categories.ts # Category listing
│ └── health.ts # Health check
├── services/ # Business logic
│ ├── scanner.ts # GitHub topic scanner
│ ├── importer.ts # Candidate → package import
│ ├── enrichment.ts # LLM-powered metadata enrichment
│ ├── search.ts # Hybrid search engine
│ ├── categories.ts # Category seeding and queries
│ └── publish.ts # Publish validation
├── middleware/
│ ├── auth.ts # Bearer token authentication
│ ├── security-headers.ts # Security headers + CORS
│ └── rate-limit.ts # Per-user / per-IP rate limiting
└── utils/ # Naming, semver, errors, response helpers
migrations/ # D1 SQL migrations (0001–0009)
test/ # Vitest test suite
| Binding | Type | Purpose |
|---|---|---|
| DB | D1 | Package metadata, users, orgs, audit log |
| FORMULAS | R2 | Formula archive storage (tar.gz) |
| CACHE | KV | Rate limiting, device flow state |
| VECTORIZE | Vectorize | Package embedding index for semantic search |
| AI | Workers AI | Embedding generation and metadata enrichment |
| ENRICHMENT_QUEUE | Queue | Async enrichment pipeline |
- Authentication: SHA-256 hashed Bearer tokens (high-entropy, unsalted — same approach as GitHub/npm)
- Rate limiting: 180 req/min per IP (anonymous), 600 req/min per user (authenticated, keyed by user ID)
- Security headers:
X-Content-Type-Options,X-Frame-Options,Content-Security-Policy,Referrer-Policy - Account deletion: Full PII anonymization with unique tombstones, package reassignment to sentinel user
- Data minimization: API responses never expose internal UUIDs;
published_byreturns username via JOIN
Packages follow scoped naming: @scope/name
- Scope and name: lowercase alphanumeric with hyphens
- Examples:
@anthropic/claude-skill,@community/github-mcp
MIT © ctx-hq