Goal: Make repaera/mcp and repaera/chat fully standard, registry-compatible, and work seamlessly with ALL common MCP clients (Claude Desktop, Cursor, Windsurf, VS Code, ChatGPT, etc.) + your own chat client
- Use latest official SDK:
@modelcontextprotocol/sdk@latestand@modelcontextprotocol/hono@latest(for server) - Auth must be Bearer token only (Authorization: Bearer )
- Server must expose
/.well-known/oauth-protected-resource(RFC 9728) - On missing/invalid token → return 401 + WWW-Authenticate: Bearer ... header
- On insufficient scope → 403 + WWW-Authenticate with error=insufficient_scope
- Token verification via introspection endpoint (from AUTH_ISSUER)
- user_id comes from token.sub (no more JWT fallback)
- Add / update: "@modelcontextprotocol/sdk": "latest"
- Add / update: "@modelcontextprotocol/hono": "latest"
Create this exact file (or replace existing auth logic):
import "dotenv/config";
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
import type { OAuthProtectedResourceMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
export const protectedResourceMetadata: OAuthProtectedResourceMetadata = {
resource: process.env.RESOURCE_URI || "https://your-mcp-server.com",
authorization_servers: [process.env.AUTH_ISSUER!],
scopes_supported: ["tools:read", "tools:execute"],
client_id_metadata_document_supported: true,
};
const tokenVerifier = {
verifyAccessToken: async (token: string) => {
const res = await fetch(process.env.INTROSPECTION_ENDPOINT!, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
token,
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET || "",
}),
});
if (!res.ok) throw new Error("Token verification failed");
const data = await res.json();
if (!data.active) throw new Error("Token inactive");
return {
userId: data.sub,
scopes: (data.scope || "").split(" ").filter(Boolean),
};
},
};
export const authMiddleware = requireBearerAuth({
verifier: tokenVerifier,
resourceMetadata: protectedResourceMetadata,
requiredScopes: [], // per-tool scopes can be added later
});- Add public metadata route:
app.get("/.well-known/oauth-protected-resource", (c) => c.json(protectedResourceMetadata));- Update StreamableHTTPServerTransport:
const transport = new StreamableHTTPServerTransport({
endpoint: "/mcp",
onRequest: [authMiddleware], // ← important
});- Delete ALL old custom header logic (MCP_TOKEN, X-User-Token, X-User-Id)
Add these variables (remove old MCP_TOKEN ones):
RESOURCE_URI=https://your-mcp-server.com
AUTH_ISSUER=https://your-auth-provider.com/realms/mcp
INTROSPECTION_ENDPOINT=https://your-auth-provider.com/realms/mcp/protocol/openid-connect/token/introspect
OAUTH_CLIENT_ID=mcp-server-introspector
OAUTH_CLIENT_SECRET=your-client-secret- Add icon to every registerTool:
_meta: { icon: "..." } - Keep existing todos table and user_id filtering (already correct)
Replace old header logic with official auth-aware transport:
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
// or use @ai-sdk/mcp with authProvider if you prefer
const transport = new StreamableHTTPClientTransport({
url: process.env.MCP_APPS_URL || process.env.MCP_URL!,
// No manual headers needed anymore
// The SDK will automatically handle 401 → metadata discovery
});
const mcpClient = createMCPClient({ transport });MCP_APPS_URL=https://your-mcp-server.com/mcp
MCP_APPS_TOKEN=eyJ... # ← real OAuth access token from your AUTH_ISSUER
# Delete or ignore: MCP_JWT_SECRET, MCP_TOKEN- Remove any X-User-Token / JWT signing code
- Add a note in UI: “Use a valid OAuth token from your auth provider”
- Deploy updated MCP server
- Test with official inspector:
npx @modelcontextprotocol/inspector https://your-mcp-server.com - Test with your chat client using real Bearer token
- Test in Claude Desktop / Cursor by adding the server URL directly
- Confirm: no custom headers, proper 401/403 with WWW-Authenticate, and user isolation still works via token.sub
This spec makes both repos 100% compliant, registry-ready, and zero-setup for any standard MCP client.