Skip to content

Commit 7aefff6

Browse files
committed
Improve the MCP authentication message
1 parent 0421441 commit 7aefff6

File tree

2 files changed

+47
-7
lines changed
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode
  • packages/cli-v3/src/mcp

2 files changed

+47
-7
lines changed

apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import { CheckCircleIcon } from "@heroicons/react/24/solid";
22
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
3-
import { title } from "process";
43
import { typedjson, useTypedLoaderData } from "remix-typedjson";
54
import { z } from "zod";
6-
import { ErrorIcon } from "~/assets/icons/ErrorIcon";
75
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
8-
import { LinkButton } from "~/components/primitives/Buttons";
96
import { Callout } from "~/components/primitives/Callout";
107
import { Header1 } from "~/components/primitives/Headers";
118
import { Icon } from "~/components/primitives/Icon";
129
import { Paragraph } from "~/components/primitives/Paragraph";
1310
import { logger } from "~/services/logger.server";
1411
import { createPersonalAccessTokenFromAuthorizationCode } from "~/services/personalAccessToken.server";
1512
import { requireUserId } from "~/services/session.server";
16-
import { rootPath } from "~/utils/pathBuilder";
1713

1814
const ParamsSchema = z.object({
1915
authorizationCode: z.string(),
2016
});
2117

18+
const SearchParamsSchema = z.object({
19+
source: z.string().optional(),
20+
clientName: z.string().optional(),
21+
});
22+
2223
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
2324
const userId = await requireUserId(request);
2425

@@ -32,13 +33,23 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
3233
});
3334
}
3435

36+
const url = new URL(request.url);
37+
const searchObject = Object.fromEntries(url.searchParams.entries());
38+
39+
const searchParams = SearchParamsSchema.safeParse(searchObject);
40+
41+
const source = (searchParams.success ? searchParams.data.source : undefined) ?? "cli";
42+
const clientName = (searchParams.success ? searchParams.data.clientName : undefined) ?? "unknown";
43+
3544
try {
3645
const personalAccessToken = await createPersonalAccessTokenFromAuthorizationCode(
3746
parsedParams.data.authorizationCode,
3847
userId
3948
);
4049
return typedjson({
4150
success: true as const,
51+
source,
52+
clientName,
4253
});
4354
} catch (error) {
4455
if (error instanceof Response) {
@@ -49,6 +60,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
4960
return typedjson({
5061
success: false as const,
5162
error: error.message,
63+
source,
64+
clientName,
5265
});
5366
}
5467

@@ -73,7 +86,7 @@ export default function Page() {
7386
<Icon icon={CheckCircleIcon} className="h-6 w-6 text-emerald-500" /> Successfully
7487
authenticated
7588
</Header1>
76-
<Paragraph>Return to your terminal to continue.</Paragraph>
89+
<Paragraph>{getInstructionsForSource(result.source, result.clientName)}</Paragraph>
7790
</div>
7891
) : (
7992
<div>
@@ -91,3 +104,21 @@ export default function Page() {
91104
</AppContainer>
92105
);
93106
}
107+
108+
const prettyClientNames: Record<string, string> = {
109+
"claude-code": "Claude Code",
110+
"cursor-vscode": "Cursor",
111+
"Visual Studio Code": "VSCode",
112+
"windsurf-client": "Windsurf",
113+
"claude-ai": "Claude Desktop",
114+
};
115+
116+
function getInstructionsForSource(source: string, clientName: string) {
117+
if (source === "mcp") {
118+
if (clientName) {
119+
return `Return to your ${prettyClientNames[clientName] ?? clientName} to continue.`;
120+
}
121+
}
122+
123+
return `Return to your terminal to continue.`;
124+
}

packages/cli-v3/src/mcp/auth.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,19 @@ export async function mcpAuth(options: McpAuthOptions): Promise<LoginResult> {
9292
//generate authorization code
9393
const authorizationCodeResult = await createAuthorizationCode(apiClient);
9494

95+
const url = new URL(authorizationCodeResult.url);
96+
97+
url.searchParams.set("source", "mcp");
98+
99+
const clientName = options.server.server.getClientVersion()?.name;
100+
101+
if (clientName) {
102+
url.searchParams.set("clientName", clientName);
103+
}
95104
// Only elicitInput if the client has the elicitation capability
96105

97106
// Elicit the user to visit the authorization code URL
98-
const allowLogin = await askForLoginPermission(opts.server, authorizationCodeResult.url);
107+
const allowLogin = await askForLoginPermission(opts.server, url.toString());
99108

100109
if (!allowLogin) {
101110
return {
@@ -105,7 +114,7 @@ export async function mcpAuth(options: McpAuthOptions): Promise<LoginResult> {
105114
}
106115

107116
// Open the authorization code URL in the browser
108-
await open(authorizationCodeResult.url);
117+
await open(url.toString());
109118

110119
// Poll for the personal access token
111120
const indexResult = await pRetry(

0 commit comments

Comments
 (0)