Add sep-835 limit retry scenario, add suite options to server command and update pending scenarios (#49)
* lefthook: move deps installs to optional local change * add test scenario for infinite retry and negative test * Add broken client for retry limit negative test and fix lefthook formatting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix www-authenticate in scope scenario * add suites and shift pending to reflect current pending * lint --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f2aaedaaa4
commit
b64e054918
1 changed files with 402 additions and 73 deletions
113
examples/clients/typescript/auth-test-no-retry-limit.ts
Normal file
113
examples/clients/typescript/auth-test-no-retry-limit.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import {
|
||||
auth,
|
||||
extractWWWAuthenticateParams,
|
||||
UnauthorizedError
|
||||
} from '@modelcontextprotocol/sdk/client/auth.js';
|
||||
import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import type { Middleware } from '@modelcontextprotocol/sdk/client/middleware.js';
|
||||
import { ConformanceOAuthProvider } from './helpers/ConformanceOAuthProvider';
|
||||
import { runAsCli } from './helpers/cliRunner';
|
||||
import { logger } from './helpers/logger';
|
||||
|
||||
/**
|
||||
* Broken client that retries auth infinitely without any retry limit.
|
||||
* BUG: Does not implement retry limits, causing infinite auth loops.
|
||||
*/
|
||||
|
||||
const withOAuthRetryNoLimit = (
|
||||
clientName: string,
|
||||
baseUrl?: string | URL
|
||||
): Middleware => {
|
||||
const provider = new ConformanceOAuthProvider(
|
||||
'http://localhost:3000/callback',
|
||||
{
|
||||
client_name: clientName,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
}
|
||||
);
|
||||
|
||||
return (next: FetchLike) => {
|
||||
return async (
|
||||
input: string | URL,
|
||||
init?: RequestInit
|
||||
): Promise<Response> => {
|
||||
const makeRequest = async (): Promise<Response> => {
|
||||
const headers = new Headers(init?.headers);
|
||||
const tokens = await provider.tokens();
|
||||
if (tokens) {
|
||||
headers.set('Authorization', `Bearer ${tokens.access_token}`);
|
||||
}
|
||||
return await next(input, { ...init, headers });
|
||||
};
|
||||
|
||||
let response = await makeRequest();
|
||||
|
||||
// BUG: No retry limit - keeps retrying on every 401/403
|
||||
while (response.status === 401 || response.status === 403) {
|
||||
const serverUrl =
|
||||
baseUrl ||
|
||||
(typeof input === 'string' ? new URL(input).origin : input.origin);
|
||||
|
||||
const { resourceMetadataUrl, scope } =
|
||||
extractWWWAuthenticateParams(response);
|
||||
let result = await auth(provider, {
|
||||
serverUrl,
|
||||
resourceMetadataUrl,
|
||||
scope,
|
||||
fetchFn: next
|
||||
});
|
||||
|
||||
if (result === 'REDIRECT') {
|
||||
const authorizationCode = await provider.getAuthCode();
|
||||
result = await auth(provider, {
|
||||
serverUrl,
|
||||
resourceMetadataUrl,
|
||||
scope,
|
||||
authorizationCode,
|
||||
fetchFn: next
|
||||
});
|
||||
if (result !== 'AUTHORIZED') {
|
||||
throw new UnauthorizedError(
|
||||
`Authentication failed with result: ${result}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
response = await makeRequest();
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function runClient(serverUrl: string): Promise<void> {
|
||||
const client = new Client(
|
||||
{ name: 'test-auth-client-no-retry-limit', version: '1.0.0' },
|
||||
{ capabilities: {} }
|
||||
);
|
||||
|
||||
const oauthFetch = withOAuthRetryNoLimit(
|
||||
'test-auth-client-no-retry-limit',
|
||||
new URL(serverUrl)
|
||||
)(fetch);
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
|
||||
fetch: oauthFetch
|
||||
});
|
||||
|
||||
await client.connect(transport);
|
||||
logger.debug('✅ Successfully connected to MCP server');
|
||||
|
||||
await client.listTools();
|
||||
logger.debug('✅ Successfully listed tools');
|
||||
|
||||
await transport.close();
|
||||
logger.debug('✅ Connection closed successfully');
|
||||
}
|
||||
|
||||
runAsCli(runClient, import.meta.url, 'auth-test-no-retry-limit <server-url>');
|
||||
Loading…
Reference in a new issue