mirror of
https://github.com/coleam00/context-engineering-intro.git
synced 2025-12-29 16:14:56 +00:00
120 lines
3.7 KiB
TypeScript
120 lines
3.7 KiB
TypeScript
// import { env } from "cloudflare:workers";
|
|
import type { AuthRequest } from "@cloudflare/workers-oauth-provider";
|
|
import { Hono } from "hono";
|
|
import { Octokit } from "octokit";
|
|
import type { Props, ExtendedEnv } from "../types";
|
|
import {
|
|
clientIdAlreadyApproved,
|
|
parseRedirectApproval,
|
|
renderApprovalDialog,
|
|
fetchUpstreamAuthToken,
|
|
getUpstreamAuthorizeUrl,
|
|
} from "./oauth-utils";
|
|
const app = new Hono<{ Bindings: ExtendedEnv }>();
|
|
|
|
app.get("/authorize", async (c) => {
|
|
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
|
|
const { clientId } = oauthReqInfo;
|
|
if (!clientId) {
|
|
return c.text("Invalid request", 400);
|
|
}
|
|
|
|
if (
|
|
await clientIdAlreadyApproved(c.req.raw, oauthReqInfo.clientId, (c.env as any).COOKIE_ENCRYPTION_KEY)
|
|
) {
|
|
return redirectToGithub(c.req.raw, oauthReqInfo, c.env, {});
|
|
}
|
|
|
|
return renderApprovalDialog(c.req.raw, {
|
|
client: await c.env.OAUTH_PROVIDER.lookupClient(clientId),
|
|
server: {
|
|
description: "This is a demo MCP Remote Server using GitHub for authentication.",
|
|
logo: "https://avatars.githubusercontent.com/u/314135?s=200&v=4",
|
|
name: "Cloudflare GitHub MCP Server", // optional
|
|
},
|
|
state: { oauthReqInfo }, // arbitrary data that flows through the form submission below
|
|
});
|
|
});
|
|
|
|
app.post("/authorize", async (c) => {
|
|
// Validates form submission, extracts state, and generates Set-Cookie headers to skip approval dialog next time
|
|
const { state, headers } = await parseRedirectApproval(c.req.raw, (c.env as any).COOKIE_ENCRYPTION_KEY);
|
|
if (!state.oauthReqInfo) {
|
|
return c.text("Invalid request", 400);
|
|
}
|
|
|
|
return redirectToGithub(c.req.raw, state.oauthReqInfo, c.env, headers);
|
|
});
|
|
|
|
async function redirectToGithub(
|
|
request: Request,
|
|
oauthReqInfo: AuthRequest,
|
|
env: Env,
|
|
headers: Record<string, string> = {},
|
|
) {
|
|
return new Response(null, {
|
|
headers: {
|
|
...headers,
|
|
location: getUpstreamAuthorizeUrl({
|
|
client_id: (env as any).GITHUB_CLIENT_ID,
|
|
redirect_uri: new URL("/callback", request.url).href,
|
|
scope: "read:user",
|
|
state: btoa(JSON.stringify(oauthReqInfo)),
|
|
upstream_url: "https://github.com/login/oauth/authorize",
|
|
}),
|
|
},
|
|
status: 302,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* OAuth Callback Endpoint
|
|
*
|
|
* This route handles the callback from GitHub after user authentication.
|
|
* It exchanges the temporary code for an access token, then stores some
|
|
* user metadata & the auth token as part of the 'props' on the token passed
|
|
* down to the client. It ends by redirecting the client back to _its_ callback URL
|
|
*/
|
|
app.get("/callback", async (c) => {
|
|
// Get the oathReqInfo out of KV
|
|
const oauthReqInfo = JSON.parse(atob(c.req.query("state") as string)) as AuthRequest;
|
|
if (!oauthReqInfo.clientId) {
|
|
return c.text("Invalid state", 400);
|
|
}
|
|
|
|
// Exchange the code for an access token
|
|
const [accessToken, errResponse] = await fetchUpstreamAuthToken({
|
|
client_id: (c.env as any).GITHUB_CLIENT_ID,
|
|
client_secret: (c.env as any).GITHUB_CLIENT_SECRET,
|
|
code: c.req.query("code"),
|
|
redirect_uri: new URL("/callback", c.req.url).href,
|
|
upstream_url: "https://github.com/login/oauth/access_token",
|
|
});
|
|
if (errResponse) return errResponse;
|
|
|
|
// Fetch the user info from GitHub
|
|
const user = await new Octokit({ auth: accessToken }).rest.users.getAuthenticated();
|
|
const { login, name, email } = user.data;
|
|
|
|
// Return back to the MCP client a new token
|
|
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
|
|
metadata: {
|
|
label: name,
|
|
},
|
|
// This will be available on this.props inside MyMCP
|
|
props: {
|
|
accessToken,
|
|
email,
|
|
login,
|
|
name,
|
|
} as Props,
|
|
request: oauthReqInfo,
|
|
scope: oauthReqInfo.scope,
|
|
userId: login,
|
|
});
|
|
|
|
return Response.redirect(redirectTo);
|
|
});
|
|
|
|
export { app as GitHubHandler };
|