Your portable context layer for Claude & ChatGPT
Free to use: ctx4-ai.vercel.app
- MCP Server — Powered by mcp-handler. Supports Streamable HTTP and SSE transports via a single dynamic route.
- OAuth 2.1 Authorization — Full authorization code flow with PKCE, dynamic client registration, and a consent screen. MCP clients authenticate automatically.
- Magic Link Auth — Passwordless sign-in with Supabase magic links. No passwords to manage.
- Token Verification — Server-side JWT verification using Supabase JWKS. MCP tool calls are authenticated and scoped to the signed-in user.
- Protected Resource Metadata — Standards-compliant
/.well-known/oauth-protected-resourceendpoint for automatic MCP client configuration. - Shadcn UI + Tailwind v4 — Pre-configured UI components with Tailwind CSS v4, Biome linting, and formatting.
MCP Client (e.g. Claude, Cursor)
│
├─ Discovers OAuth config via /.well-known/oauth-protected-resource
├─ Registers dynamically with Supabase OAuth 2.1 Server
├─ Opens browser → user signs in (magic link) → approves scopes
└─ Exchanges code for tokens (PKCE)
│
▼
app/[transport]/route.ts ← MCP handler (mcp-handler)
│
├─ verifyToken() ← validates Bearer JWT via Supabase JWKS
└─ server tools ← MCP tools (ctx_instructions, ctx_bash)
The MCP handler in app/[transport]/route.ts responds on /mcp (HTTP transport). Authentication is handled by withMcpAuth, which delegates to lib/auth/verify-token.ts for JWT validation.
Supabase session middleware runs via proxy.ts, protecting browser routes while allowing Bearer-authenticated MCP requests to pass through.
git clone https://github.com/dubzdubz/ctx4-ai.git
cd ctx4-ai
pnpm installcp .env.example .envFill in your credentials (see .env.example for the full list):
# Supabase (from Dashboard → Settings → API)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Database (Supabase PostgreSQL connection string)
DATABASE_URL=postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
# GitHub App (from GitHub Developer Settings)
NEXT_PUBLIC_GITHUB_APP_SLUG=your-app-slug
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
GITHUB_APP_CLIENT_ID=Iv1.xxxxxxxxxx
GITHUB_APP_CLIENT_SECRET=xxxxxxxxxxxx
# Vercel (for sandbox auth — run `vercel link && vercel env pull`)
VERCEL_OIDC_TOKEN=your-vercel-oidc-token
Magic link auth:
In Supabase Dashboard → Authentication → URL Configuration:
- Set Site URL to
http://localhost:3000(or your production URL) - Add
http://localhost:3000/auth/confirmto Redirect URLs
OAuth 2.1 Server:
In Supabase Dashboard → Authentication → OAuth Server:
- Enable OAuth 2.1 Server
- Set Authorization Path to:
/auth/oauth/authorize - (Optional) Enable Dynamic Client Registration to allow MCP clients to self-register
pnpm devOpen http://localhost:3000 to see the app.
Add the server to your MCP client (Claude, Cursor, etc.):
{
"servers": {
"ctx4-ai": {
"url": "http://localhost:3000/mcp"
}
}
}The client will automatically:
- Discover OAuth configuration from Supabase
- Register itself dynamically (if enabled)
- Open browser for user authorization
- Exchange tokens and make authenticated MCP requests
Once connected, run the onboarding prompt to set up your context repo:
/ctx4:onboarding
This will scaffold your repo and ask a few questions about your preferences and workflow.
For best results, add this to your client's custom instructions or system prompt:
Use the ctx4 MCP to manage my long-term context and memory. Always call ctx_instructions first before using ctx_bash to understand how to interact with my context. Use it to store preferences, learnings, and anything that should persist across conversations.
Where to add:
- Claude: Settings → General → Personal Preferences
- ChatGPT: Settings → Personalization → Custom Instructions
Use the MCP Inspector to test and debug your server:
npx @mcpjam/inspector@latest| Tool | Description |
|---|---|
ctx_instructions |
Returns full context bootstrap (guide + instructions + context + resource/skill indexes) |
ctx_bash |
Sandboxed bash in /vercel/sandbox with auto git commit and push |
├── app/
│ ├── [transport]/route.ts # MCP handler (HTTP transport)
│ ├── .well-known/ # OAuth protected resource metadata
│ ├── api/
│ │ ├── github/ # GitHub App endpoints (callback, repos, select-repo, disconnect)
│ │ └── oauth/approve/route.ts # OAuth consent approval endpoint
│ ├── auth/
│ │ ├── confirm/page.tsx # Magic link confirmation
│ │ ├── error/page.tsx # Auth error page
│ │ ├── login/page.tsx # Login page (magic link form)
│ │ └── oauth/authorize/page.tsx # OAuth consent screen
│ ├── settings/page.tsx # User settings / profile + GitHub integration
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page
│ └── globals.css # Tailwind v4 + Shadcn theme
├── components/
│ ├── auth/ # Auth components (login, logout, consent)
│ ├── github/ # GitHub integration UI (repo manager)
│ └── ui/ # Shadcn UI components
├── lib/
│ ├── auth/verify-token.ts # JWT verification for MCP auth
│ ├── db/ # Drizzle ORM (schema, queries, client)
│ ├── github/app.ts # GitHub App client (Octokit, installation tokens)
│ ├── sandbox/ # SandboxManager, SandboxManagerPool, scanner
│ ├── supabase/ # Supabase client/server/middleware
│ └── tools/ # MCP tool implementations
├── drizzle/ # Database migrations
├── proxy.ts # Next.js middleware (session + route protection)
├── drizzle.config.ts # Drizzle ORM configuration
├── biome.jsonc # Biome linter/formatter config
└── .env.example # Environment variables template
Register new tools in app/[transport]/route.ts:
server.registerTool(
"my_tool",
{
title: "My Tool",
description: "Does something useful.",
inputSchema: {
query: z.string(),
},
},
async ({ query }, extra) => {
const userEmail = extra.authInfo?.extra?.email;
return {
content: [{ type: "text", text: `Result for ${query}` }],
};
},
);Edit the requiredScopes array in app/[transport]/route.ts to enforce specific OAuth scopes:
const authHandler = withMcpAuth(handler, verifyToken, {
required: true,
requiredScopes: ["openid", "email"], // empty by default (no scope enforcement)
// ...
});Edit proxy.ts and lib/supabase/middleware.ts to change which paths are public or protected. The MCP transport route (/mcp) is automatically bypassed when a Bearer token is present.
| Dependency | Purpose |
|---|---|
| Next.js 16 | React framework (App Router) |
| mcp-handler | MCP server adapter for Next.js |
| @modelcontextprotocol/sdk | MCP SDK |
| Supabase | Auth (OAuth 2.1) & PostgreSQL |
| Drizzle ORM | Database ORM & migrations |
| Vercel Sandbox | Isolated command execution (Firecracker microVM) |
| Octokit | GitHub App integration |
| Shadcn UI + Tailwind v4 | UI components & styling |
| Biome | Linting & formatting |
- mcp-handler — Vercel adapter for MCP servers on Next.js
- Supabase OAuth 2.1 Server — OAuth 2.1 and OIDC identity provider docs
- MCP Inspector — Test and debug MCP servers
- MCP Server Concepts — Tools, resources, and prompts
- MCP Authorization Specification — OAuth flow for MCP
- Next.js Documentation — Framework docs
Apache-2.0 — see LICENSE.