- This page is the authorization page presented by the MCP server, routing the user upstream. This is only
- needed on 2025-03-26 Auth spec, where the MCP server acts as it's own authoriztion server. This page should
- be present to avoid confused deputy attacks.
-
Please verify your identity with the upstream provider
+
+
+
Your User Identity
+
Loading...
+
+
+
+
+
+
+
+
+
+ Testing Multiple Users: Open this page in different browser windows or incognito tabs to simulate different users. Each will have their own unique User ID and separate MCP sessions.
+
+
+
+
`);
@@ -36,6 +263,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) {
// The state returned from the upstream auth server is actually the authorization code
state: mcpAuthorizationCode,
code: upstreamAuthorizationCode,
+ userId, // User ID from the authorization flow
} = req.query;
// This is where you'd exchange the upstreamAuthorizationCode for access/refresh tokens
@@ -63,6 +291,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) {
mcpTokens,
clientId: pendingAuth.clientId,
issuedAt: Date.now() / 1000,
+ userId: (userId as string) || 'anonymous-user', // Include user ID from auth flow
}
// Store the upstream authorization data
diff --git a/src/index.ts b/src/index.ts
index 5f7aa32..37e94e9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,6 +36,11 @@ const baseSecurityHeaders = (req: express.Request, res: express.Response, next:
// simple logging middleware
const logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
+ if (req.url.includes('/mcp')) {
+ console.log(' Headers:', JSON.stringify(req.headers, null, 2));
+ console.log(' Body:', JSON.stringify(req.body, null, 2));
+ }
next();
};
@@ -106,7 +111,12 @@ app.get("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP
app.post("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP);
app.delete("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP);
-// Upstream auth routes
+// Static assets
+app.get("/mcp-logo.png", (req, res) => {
+ res.sendFile("mcp.png", { root: "." });
+});
+
+// Upstream auth routes
app.get("/fakeupstreamauth/authorize", cors(corsOptions), handleFakeAuthorize);
app.get("/fakeupstreamauth/callback", cors(corsOptions), handleFakeAuthorizeRedirect);
diff --git a/src/redis.ts b/src/redis.ts
index cc57f00..1316592 100644
--- a/src/redis.ts
+++ b/src/redis.ts
@@ -142,7 +142,7 @@ export class MockRedisClient implements RedisClient {
options = { url: "redis://localhost:6379" };
private store = new Map();
private lists = new Map();
- private subscribers = new Map void)[]>();
+ public subscribers = new Map void)[]>(); // Public for testing access
private errorCallbacks = new Map void)[]>();
async get(key: string): Promise {
diff --git a/src/services/auth.test.ts b/src/services/auth.test.ts
index e79dd10..6a761bd 100644
--- a/src/services/auth.test.ts
+++ b/src/services/auth.test.ts
@@ -193,6 +193,7 @@ describe("auth utils", () => {
},
clientId: "client-id",
issuedAt: Date.now() / 1000,
+ userId: "test-user-id",
}
await saveMcpInstallation(accessToken, mcpInstallation);
@@ -239,6 +240,7 @@ describe("auth utils", () => {
},
clientId: "client-id",
issuedAt: Date.now() / 1000,
+ userId: "test-user-id",
});
const getDel = jest.spyOn(mockRedis, 'getDel').mockImplementationOnce(() => {
@@ -252,6 +254,7 @@ describe("auth utils", () => {
},
clientId: "client-id",
issuedAt: Date.now() / 1000,
+ userId: "test-user-id",
};
const value = JSON.stringify(mcpInstallation);
const iv = crypto.randomBytes(16);