Skip to content

Resolve dot segments in linear time when canonicalizing a URL - #575

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:remove_dot_segments_in_linear_time
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:remove_dot_segments_in_linear_time

Conversation

@koic

@koic koic commented Sep 24, 2026

Copy link
Copy Markdown
Member

Motivation and Context

Discovery.canonicalize_url resolved dot segments with RFC 3986 Section 5.2.4 applied literally: every . or .. rewrote the remaining input buffer, which copies it each time, so the cost grew with the square of the path's length. The path is the server's to choose, through a Protected Resource Metadata resource or an endpoint URL that reaches the canonicalization: resolving a 2 MB path made of a/../ took over 30 seconds, on every flow that received it.

The segments are now visited once with a stack, which is the same algorithm expressed over segments instead of over the buffer. The output is unchanged, including the RFC's handling of a trailing /. or /.. (the slash stays) and of a relative path (which URI#path never yields), as checked against the previous implementation on 100,000 random paths.

How Has This Been Tested?

New tests in test/mcp/client/oauth/discovery_test.rb canonicalize a URL with 300,000 dot segments within a bound the previous implementation exceeded many times over, and pin the trailing-slash and empty-segment cases.

Breaking Changes

None.

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
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

`Discovery.canonicalize_url` resolved dot segments with RFC 3986 Section 5.2.4 applied literally:
every `.` or `..` rewrote the remaining input buffer, which copies it each time, so the cost grew with the square
of the path's length. The path is the server's to choose, through a Protected Resource Metadata `resource`
or an endpoint URL that reaches the canonicalization: resolving a 2 MB path made of `a/../` took over 30 seconds,
on every flow that received it.

The segments are now visited once with a stack, which is the same algorithm expressed over segments instead of
over the buffer. The output is unchanged, including the RFC's handling of a trailing `/.`
or `/..` (the slash stays) and of a relative path (which `URI#path` never yields), as checked against
the previous implementation on 100,000 random paths.

## How Has This Been Tested?

New tests in `test/mcp/client/oauth/discovery_test.rb` canonicalize a URL with 300,000 dot segments within
a bound the previous implementation exceeded many times over, and pin the trailing-slash and empty-segment cases.

## Breaking Changes

None.
@koic
koic force-pushed the remove_dot_segments_in_linear_time branch from 5a1c362 to 5e2edae Compare September 24, 2026 15:36

This branch has not been deployed

No deployments
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.

2 participants