Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client implementation of MCP auth #144

Merged
merged 25 commits into from
Feb 17, 2025
Merged

Client implementation of MCP auth #144

merged 25 commits into from
Feb 17, 2025

Conversation

jspahrsummers
Copy link
Member

@jspahrsummers jspahrsummers commented Feb 6, 2025

This implements the client side only of the MCP auth draft spec.

Example usage

A real implementation needs to save OAuth token state, code verifiers, etc. to a persistent store, but this sample at least demonstrates the bits that are necessary:

class MyAuthProvider implements OAuthClientProvider {
  private tokens_: OAuthTokens | undefined;
  private codeVerifier_: string | undefined;

  get redirectUrl(): string {
    return "http://localhost:3000/callback";
  }

  get clientMetadata(): OAuthClientMetadata {
    return {
      redirect_uris: [this.redirectUrl],
      client_name: "My MCP Client"
    };
  }

  clientInformation(): OAuthClientInformation {
    return {
      client_id: "my-client-id",
      client_secret: "my-client-secret"
    };
  }

  async tokens(): Promise<OAuthTokens | undefined> {
    return this.tokens_;
  }

  async saveTokens(tokens: OAuthTokens): Promise<void> {
    this.tokens_ = tokens;
    console.log("Received new tokens:", tokens);
  }

  async redirectToAuthorization(authUrl: URL): Promise<void> {
    console.log("Redirecting to:", authUrl);
    // In a browser, you might do: window.location.href = authUrl.href;
  }

  async saveCodeVerifier(verifier: string): Promise<void> {
    this.codeVerifier_ = verifier;
  }

  async codeVerifier(): Promise<string> {
    if (!this.codeVerifier_) {
      throw new Error("No code verifier saved");
    }
    return this.codeVerifier_;
  }
}

const transport = new SSEClientTransport(
  new URL("https://api.example.com/sse"),
  {
    authProvider: new MyAuthProvider()
  }
);

// The transport will automatically:
// 1. Add auth headers to all requests
// 2. Refresh expired tokens when 401 responses are received
// 3. Store new tokens via the auth provider
// 4. Begin authorization flow if refresh fails

@jspahrsummers jspahrsummers marked this pull request as ready for review February 12, 2025 14:21
Copy link
Contributor

@jerome3o-anthropic jerome3o-anthropic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great! - just a couple of non-blocking questions

const metadata = {
...validMetadata,
response_types_supported: ["code"],
code_challenge_methods_supported: ["plain"], // Does not support 'S256'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] Do we only support S256?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, OAuth 2.1 basically* requires this

throw error;
}

if (result !== "AUTHORIZED") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the intended behaviour when we need to redirect? should the caller just catch this error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my understanding is:

  • the provider will initiate the redirect on a 401
  • once the user is redirected back we will call auth() again but with the authorization code
  • that will populate the provider's tokens
  • and then we init a new transport with that provider

I could be missing something though - is that how we intend to get the authorization code in here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. This is a good reminder to add more docs, though—and I also wanted to add a convenience method that makes it easier to finish the auth flow via the SSEClientTransport class.


if (req.url === "/token" && req.method === "POST") {
// Handle token refresh request - always fail
res.writeHead(400).end();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] should this be 401

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jspahrsummers jspahrsummers merged commit 41e0d26 into main Feb 17, 2025
3 checks passed
@jspahrsummers jspahrsummers deleted the justin/client-auth branch February 17, 2025 10:46
@csmoakpax8 csmoakpax8 mentioned this pull request Mar 26, 2025
MediaInfluences pushed a commit to MediaInfluences/typescript-sdk that referenced this pull request Apr 3, 2025
…/justin/client-auth

Client implementation of MCP auth
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

2 participants