core/apps/webapp/app/routes/oauth.register.tsx
2025-08-24 19:58:39 +05:30

67 lines
2.0 KiB
TypeScript

import { json } from "@remix-run/node";
import { type ActionFunctionArgs } from "@remix-run/server-runtime";
import { oauth2Service } from "~/services/oauth2.server";
// Dynamic Client Registration for MCP clients (Claude, etc.)
export async function action({ request }: ActionFunctionArgs) {
if (request.method !== "POST") {
throw new Response("Method Not Allowed", { status: 405 });
}
try {
const body = await request.json();
const { client_name, redirect_uris, grant_types, response_types } = body;
// Validate required fields
if (
!redirect_uris ||
!Array.isArray(redirect_uris) ||
redirect_uris.length === 0
) {
return json(
{
error: "invalid_request",
error_description: "redirect_uris is required",
},
{ status: 400 },
);
}
// Create MCP client with special handling
const client = await oauth2Service.createDynamicClient({
name: client_name || "MCP Client",
redirectUris: redirect_uris,
grantTypes: grant_types || ["authorization_code"],
responseTypes: response_types || ["code"],
clientType: "mcp", // Special flag for MCP clients
requirePkce: true,
allowedScopes: "mcp,mcp.read,mcp.write,mcp:read,mcp:write",
});
return json({
client_id: client.clientId,
client_secret: client.clientSecret, // Include if confidential client
client_id_issued_at: Math.floor(Date.now() / 1000),
grant_types: client.grantTypes.split(","),
response_types: ["code"],
redirect_uris: client.redirectUris.split(","),
scope: client.allowedScopes,
token_endpoint_auth_method: "client_secret_basic",
});
} catch (error) {
console.error("Dynamic client registration error:", error);
return json(
{
error: "invalid_request",
error_description: "Failed to register client",
},
{ status: 400 },
);
}
}
// Prevent GET requests
export async function loader() {
throw new Response("Method Not Allowed", { status: 405 });
}