Skip to content

Conversation

@pcarleton
Copy link
Member

@pcarleton pcarleton commented Nov 18, 2025

Motivation and Context

fixes #32

How Has This Been Tested?

Breaking Changes

tests and negative tests

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
    • needs a typescript sdk change to pass
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

I did a few related refactors:

  • Inlined tests
  • allowed piping test output to jq
  • some fixes around how we handled WARNING, since it's a bunch of SHOULD's in this case

const scenario = getScenario(scenarioName)!;

console.log(`Starting scenario: ${scenarioName}`);
console.error(`Starting scenario: ${scenarioName}`);
Copy link
Member Author

Choose a reason for hiding this comment

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

this is to make it possible to pipe verbose output to jq or jless

server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<CallToolResult> => {
if (request.params.name === 'test-tool') {
Copy link
Member Author

Choose a reason for hiding this comment

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

this is so we can test an "auth only required on a tool call" scenario. It's via setRequestHandler because we're in the low level interface

private expectedScopes: string[] = []
) {}

registerToken(token: string, scopes: string[]) {
Copy link
Member Author

Choose a reason for hiding this comment

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

we're keeping track of the scopes here as a convenience. we could also fetch them from the AS, and might want to switch to doing that in the future.

InlineClientRunner
} from './test_helpers/testClient.js';
import path from 'path';
import { runClient as goodClient } from '../../../../examples/clients/typescript/auth-test.js';
Copy link
Member Author

Choose a reason for hiding this comment

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

I refactored the client tests to allow running them "inline" rather than spawning a subshell.

A subshell took a good 600ms, so the full auth suite took about 13s. With this, it's about 200ms for the full suite, so vitest watch is like a live refresh.

'examples/clients/typescript/auth-test.ts'
);
beforeAll(() => {
setLogLevel('error');
Copy link
Member Author

Choose a reason for hiding this comment

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

without this, we get a bunch of annoying stdout lines littering the test output

// Verify that only the expected checks failed
const failures = nonInfoChecks.filter((c) => c.status === 'FAILURE');
const failures = nonInfoChecks.filter(
(c) => c.status === 'FAILURE' || c.status === 'WARNING'
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm counting warning as failure in this test because we want the examples to not have warnings

@pcarleton
Copy link
Member Author

This is ready for review, but needs a typsecript change for tests to pass, will push that shortly.

@pcarleton
Copy link
Member Author

typescript-sdk PR to have this pass:
modelcontextprotocol/typescript-sdk#1133

I think I'll comment out the broken ones for now since we'll need to wait for typescript release i think to get the fix. (unless we want to patch release?)

@pcarleton
Copy link
Member Author

okay I skipped tests, we can unskip them later

* Broken client that only responds to 401, not 403.
* BUG: Ignores 403 responses which are used for step-up auth.
*/
export async function runClient(serverUrl: string): Promise<void> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

more of a style nit: all these "broken" examples look very similar (identical?) except for the specific way in which they're "broken".

Could consider having a single implementation and having the specific way it's actually broken be a param to createBrokenHandler, a higher level function that creates the handle401broken.

  type ClientBehavior =
    | { type: 'well-behaved' }  // or just omit for default
    | { type: 'ignore-resource-metadata' }
    | { type: 'ignore-scope' }
    | { type: 'partial-scopes'; scope: string }
    | { type: 'ignore-403' };

Could make this less verbose.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Potentially the well-behaved client could also share the same implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we prefer explicitness over conciseness though for conformance tests, so definitely just a thought 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

agreed on the repetitiveness, i've been resisting the urge to de-dupe too aggressively in order to let abstractions fall out from a few more iterations on tests before committing to one.

"@types/express": "^5.0.3",
"@types/node": "^22.10.2",
"@typescript/native-preview": "^7.0.0-dev.20251030.1",
"cors": "^2.8.5",
Copy link
Collaborator

@felixweinberger felixweinberger Nov 18, 2025

Choose a reason for hiding this comment

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

Is adding cors here intentional for this PR? Doesn't seem like we're importing this anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

when I ran npm link this started complaining, i think because the everything server uses it (even if we have a package.json in the server client repo).

I'd prefer to keep it to let the link flow be smoother even if it's a little odd.

@pcarleton pcarleton merged commit 5c15113 into main Nov 18, 2025
4 of 7 checks passed
@pcarleton pcarleton deleted the pcarleton/scopes-selection branch November 18, 2025 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client Auth: Scope Selection & Challenge Handling

3 participants