Skip to content

feat(multi-instance): Multi-Instance OAuth Federation with per-session introspection#277

Merged
polaz merged 55 commits intomainfrom
feat/#274-feat-multi-instance-oauth-federation-with-per-sess
Feb 4, 2026
Merged

feat(multi-instance): Multi-Instance OAuth Federation with per-session introspection#277
polaz merged 55 commits intomainfrom
feat/#274-feat-multi-instance-oauth-federation-with-per-sess

Conversation

@polaz
Copy link
Copy Markdown
Member

@polaz polaz commented Feb 4, 2026

Summary

Implements full multi-instance GitLab support with OAuth, allowing one MCP server to serve users connecting to different GitLab instances simultaneously. Each user session maintains its own GitLab instance context with proper caching at correct levels.

Closes #274

Changes

Core Infrastructure (Phase 1)

  • InstanceRegistry singleton for managing multiple GitLab instances
  • InstanceRateLimiter for per-instance concurrent request limiting
  • NamespaceTierDetector for per-namespace tier detection via GraphQL
    • CRITICAL: Tier is per-NAMESPACE, not per-instance (gitlab.com users may have mixed tiers)
  • Extended TokenContext with apiUrl for per-request instance routing
  • Extended OAuthSession with gitlabApiUrl and selectedInstance

Per-Instance Connection Pooling (NEW)

  • InstanceConnectionPool singleton managing per-instance HTTP/2 connections
    • Undici Pool with HTTP/2 multiplexing and keepalive
    • Per-instance TLS configuration support (insecureSkipVerify)
    • Connection reuse across requests (30s keepalive, 5min max)
    • Pool statistics for monitoring (connected, free, pending, running)
  • enhancedFetch() integration with per-instance dispatchers
    • InstanceRegistry.getDispatcher() provides per-instance Undici dispatcher
    • REST API calls now use per-instance HTTP/2 connection pools
    • Falls back to global dispatcher for unregistered instances
  • Thread-safe GraphQL client access via InstanceRegistry.getGraphQLClient()
    • Each instance gets isolated GraphQL client
    • No more singleton endpoint mutation
    • Auth headers can be set per-request (for OAuth tokens)
  • ConnectionManager.getInstanceClient() for per-instance client access
    • Falls back to legacy client for backwards compatibility
    • Integrates with InstanceRegistry when initialized

Configuration System (Phase 2)

  • GITLAB_INSTANCES_FILE - Path to YAML/JSON config file
  • GITLAB_INSTANCES - Environment variable (URL, array, or JSON)
  • Backward compatible with GITLAB_API_URL (legacy single-instance)
  • Zod schemas for config validation
  • Cross-platform home directory expansion via os.homedir()

CLI Commands (Phase 3)

  • instances list - List configured instances
  • instances add - Interactive instance addition
  • instances remove - Guidance for removal
  • instances test - Connection testing with version info
  • instances info - Detailed instance information
  • instances sample-config - Generate YAML/JSON templates

Feature Availability (Phase 4)

  • Per-namespace tier detection (Free/Premium/Ultimate)
  • Feature availability mapping per tier
  • 5-minute TTL namespace tier cache per session

Rate Limiting (Phase 5)

  • Per-instance concurrent request limiting
  • Configurable queue size and timeout
  • Metrics tracking (active, queued, rejected, avg wait)

OAuth Flow (Phase 6)

  • Instance selection stored in OAuth state
  • Session bound to selected instance
  • Instance label preserved in session

Context Switching (Phase 7)

  • Blocked in OAuth mode - session tied to instance, requires re-auth
  • Allowed in static token mode - triggers re-introspection
  • Clears namespace tier cache on switch
  • Sends tools/list_changed notification

Documentation (Phase 8)

  • docs/guide/multi-instance.md - Getting started guide
  • docs/advanced/federation.md - Architecture deep dive
  • docs/advanced/tier-detection.md - Tier detection explanation
  • docs/advanced/context-switching.md - Instance switching behavior
  • docs/configuration/instances.md - Configuration reference
  • docs/configuration/rate-limiting.md - Rate limiting reference
  • docs/cli/instances.md - CLI commands reference
  • Updated README.md with multi-instance section

Test Plan

  • Unit tests for InstanceRegistry (514 lines)
  • Unit tests for InstanceRateLimiter (240 lines)
  • Unit tests for instances-loader (340 lines)
  • Unit tests for instances-schema (250 lines)
  • Unit tests for token-context extensions (44 lines)
  • Unit tests for ConnectionManager enhanced (per-instance client tests)
  • Unit tests for fetch utilities (extractBaseUrl)
  • Integration tests for InstanceRegistry (201 lines)
  • All existing tests pass (4441 unit + 365 integration)

Architecture Highlights

Thread-Safe Multi-Instance Access

┌─────────────────────────────────────────────────────────────┐
│                    InstanceRegistry                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ gitlab.com  │  │ git.corp.io │  │ gl.dev.net  │         │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘         │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
          ▼                ▼                ▼
┌─────────────────────────────────────────────────────────────┐
│                 InstanceConnectionPool                       │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ Undici Pool │  │ Undici Pool │  │ Undici Pool │         │
│  │ + GraphQL   │  │ + GraphQL   │  │ + GraphQL   │         │
│  │   Client    │  │   Client    │  │   Client    │         │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘         │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
          ▼                ▼                ▼
┌─────────────────────────────────────────────────────────────┐
│                    enhancedFetch()                           │
│  - Per-instance HTTP/2 dispatcher from InstanceConnectionPool│
│  - Per-instance rate limiting from InstanceRateLimiter       │
│  - Automatic retry with exponential backoff                  │
└─────────────────────────────────────────────────────────────┘

Each instance gets:

  • Dedicated HTTP/2 connection pool (up to 10 concurrent connections)
  • Isolated GraphQL client (no endpoint mutation)
  • Per-instance rate limiting
  • Per-instance TLS configuration

Breaking Changes

None - fully backward compatible with existing single-instance configuration.

polaz added 4 commits February 4, 2026 16:23
- Add InstanceRegistry service for managing multiple GitLab instances
- Add InstanceRateLimiter for per-instance concurrent request limiting
- Add instance configuration schema and loader (YAML/JSON/env var support)
- Extend TokenContext with apiUrl for per-request instance routing
- Extend OAuthSession with gitlabApiUrl for session-to-instance binding
- Update enhancedFetch to use context URL with rate limiting integration
- Update ConnectionManager for per-instance introspection caching
- Add gitlabApiUrl field to Prisma schema for PostgreSQL storage
- Add unit tests for InstanceRateLimiter and instance config schemas

This lays the foundation for #274 multi-instance support. Users can now
configure multiple GitLab instances via GITLAB_INSTANCES env var or
GITLAB_INSTANCES_FILE config file. Per-session instance routing and
rate limiting are handled automatically.

Related to #274
…ture

- Add unit tests for instances-loader (config loading from file/env/legacy)
- Add unit tests for InstanceRegistry (singleton, registration, rate limiting)
- Add integration tests for InstanceRegistry (real GitLab interaction)

These tests verify the multi-instance OAuth federation infrastructure
introduced in the previous commit:
- Configuration loading from YAML/JSON files and environment variables
- Instance registration and URL normalization
- Per-instance rate limiting with slot acquisition
- Introspection caching with TTL
- Connection status tracking
- Integration with ConnectionManager

Related to #274
… context switching

- Add NamespaceTierDetector service with per-session caching (5-min TTL)
  - GraphQL query for namespace plan detection
  - Feature availability mapping per tier (free/premium/ultimate)
  - CRITICAL: tier is per-NAMESPACE, not per-instance (gitlab.com users may have mixed)

- Add CLI instance management commands (instances-command.ts)
  - list: show configured instances
  - add: interactive instance configuration
  - remove: guidance for removal
  - test: connection testing with version info
  - info: detailed instance information
  - sample-config: generate yaml/json templates

- Extend OAuth flow with instance selection
  - AuthCodeFlowState/DeviceFlowState now carry selectedInstance/selectedInstanceLabel
  - callback.ts and authorize.ts use selectedInstance for session gitlabApiUrl

- Add context switching support (blocked in OAuth mode)
  - ContextManager.switchInstance() for static token mode
  - Clears namespace tier cache on switch
  - Re-initializes ConnectionManager for new instance
  - Sends tools/list_changed notification to clients

- Extend ConnectionManager with reinitialize() for instance switching

Related to #274
…nce support

- Add docs/guide/multi-instance.md - getting started guide
- Add docs/advanced/federation.md - architecture deep dive
- Add docs/advanced/tier-detection.md - namespace tier detection explanation
- Add docs/advanced/context-switching.md - instance switching behavior
- Add docs/configuration/instances.md - instance configuration reference
- Add docs/configuration/rate-limiting.md - rate limiting reference
- Add docs/cli/instances.md - CLI commands reference
- Update README.md with multi-instance section
- Update sidebar navigation in vitepress config

Related to #274
Copilot AI review requested due to automatic review settings February 4, 2026 15:18
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 4, 2026

Test Coverage Report

Overall Coverage: 95.74%

Metric Percentage
Statements 95.22%
Branches 86.03%
Functions 94.83%
Lines 95.74%

View detailed coverage report

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements multi-instance GitLab support (OAuth + config + CLI) by introducing an instance registry with per-instance rate limiting and introspection caching, and by extending OAuth session/context data to carry the selected instance.

Changes:

  • Added InstanceRegistry + InstanceRateLimiter services and wired rate-limit acquisition into enhancedFetch.
  • Added multi-instance configuration system (Zod schema + loader) and new instances CLI commands.
  • Added namespace-tier detection service + extensive documentation and test coverage for the new components.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/unit/services/InstanceRegistry.test.ts Unit tests for registry behavior (singleton, URL normalization, caching, rate limits).
tests/unit/services/InstanceRateLimiter.test.ts Unit tests for concurrency + queueing rate limiter behavior and metrics.
tests/unit/oauth/token-context.test.ts Tests for new token-context accessors (apiUrl, instanceLabel).
tests/unit/config/instances-schema.test.ts Tests for instance config schema parsing/defaulting and URL parsing helper.
tests/unit/config/instances-loader.test.ts Tests for instance config loading precedence (file/env/legacy/default) and helpers.
tests/integration/services/InstanceRegistry.test.ts Integration tests exercising registry initialization/caching/metrics with real env.
src/utils/fetch.ts Adds base-URL helper and per-instance rate-limit slot acquisition to enhancedFetch.
src/services/NamespaceTierDetector.ts New per-session namespace tier detection + caching via GraphQL.
src/services/InstanceRegistry.ts New singleton registry managing instance config/state, rate limiters, and introspection cache.
src/services/InstanceRateLimiter.ts New per-instance concurrent rate limiter with queueing and metrics.
src/services/ConnectionManager.ts Initializes registry and adds per-instance introspection caching + reinitialize hook.
src/server.ts Extends token context setup to include apiUrl and instanceLabel.
src/oauth/types.ts Extends OAuth session/state and token context types for multi-instance support.
src/oauth/token-context.ts Adds accessors for apiUrl and instanceLabel from AsyncLocalStorage.
src/oauth/storage/postgresql.ts Persists gitlabApiUrl and instanceLabel in PostgreSQL session storage.
src/oauth/index.ts Re-exports the new token-context helpers.
src/oauth/endpoints/callback.ts Stores selected instance URL/label into the created OAuth session.
src/oauth/endpoints/authorize.ts Stores selected instance URL/label into the created OAuth session (device flow poll).
src/middleware/oauth-auth.ts Exposes session instance URL/label through res.locals for request context.
src/entities/context/context-manager.ts Adds static-token instance switching entrypoint (blocked in OAuth mode).
src/config/instances-schema.ts New Zod schemas/types + URL string parsing helper for instance config formats.
src/config/instances-loader.ts New loader supporting file/env/legacy/default instance configuration sources.
src/config/index.ts New barrel export for instance config modules.
src/cli/instances/instances-command.ts New instances CLI (list/add/remove/test/info/sample-config).
src/cli/instances/index.ts CLI module export for instances commands.
prisma/schema.prisma Adds gitlab_api_url and instance_label fields to OAuthSession.
docs/guide/multi-instance.md New guide explaining setup and behavior for multi-instance mode.
docs/configuration/rate-limiting.md New configuration reference for per-instance concurrency/queue settings.
docs/configuration/instances.md New configuration reference for multi-instance file/env formats.
docs/cli/instances.md New CLI reference for instance management commands.
docs/advanced/tier-detection.md New deep-dive on per-namespace tier detection and caching.
docs/advanced/federation.md New architecture doc for federation/caching layers and request flow.
docs/advanced/context-switching.md New doc explaining instance switching rules/behavior by auth mode.
docs/.vitepress/config.mts Adds navigation entries for new multi-instance docs and CLI/config pages.
README.md Adds a multi-instance section and links to the new documentation.

- Mask OAuth clientSecret when displaying instance configuration
- Use password prompt instead of text for OAuth secret input
- Fix npm -> yarn in YAML package install message
- Add isExpired field to InstanceSummary introspection for TTL awareness
- Fix port vs OAuth client ID parsing (use valid port range 1-65535)
- Strip paths/query/fragments from GitLab URLs in schema
- Add space-separated URL support in GITLAB_INSTANCES parsing
- Document rate limit slot retention during retries
- Document per-instance TLS limitation
- Clarify reinitialize() URL handling in ConnectionManager

Related to #274
polaz added 2 commits February 4, 2026 18:02
- Add comprehensive unit tests for getFeaturesForTier()
- Add tests for clearNamespaceTierCache() with session-specific clearing
- Add tests for getNamespaceTier() including GraphQL queries
- Add tests for cache TTL expiration (5 min)
- Add tests for plan name normalization (gold/silver/bronze)
- Add tests for isFeatureAvailable() and cache metrics
- Coverage: 97.64% statements, 93.02% branches, 100% functions
Replace spread operator with explicit field list when showing
instance configuration to user. Displays url, label, oauth.clientId,
oauth.scopes, and hasSecret flag without exposing actual secrets.
Show only oauthConfigured boolean flag instead of accessing
oauth object fields. Satisfies CodeQL sensitive data rule.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 5 comments.

polaz added 2 commits February 4, 2026 18:47
- instances-command: list, add, remove, test, info, sample-config
- instances-loader: YAML loading, space-separated URLs parsing
- addInstance: OAuth flow, cancel handling at each prompt step
- testInstance: HTTP responses, network errors, registry init
- showInstanceInfo: full info display, missing optional fields
- instances-schema: preserve pathname in GitLabUrlSchema for subpath deployments
- instances-schema: rewrite parseInstanceUrlString with right-to-left parsing
- instances-schema: distinguish port numbers (1-65535) from OAuth clientIds
- instances-loader: handle both MODULE_NOT_FOUND and ERR_MODULE_NOT_FOUND
- ConnectionManager: document multi-instance GraphQL endpoint limitation
- fetch: document extractBaseUrl subpath limitation
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

polaz added 2 commits February 4, 2026 19:01
Add test for queued request timeout after queueTimeout expires.
Validates that requests waiting in queue are properly rejected
with timing out error when the configured timeout is reached.
- Add OAuth unauthenticated version detection tests
- Add ensureIntrospected caching path tests
- Add reinitialize method tests
- Add switchInstance tests (OAuth blocked, static mode)
- Add invalid scope configuration tests
- Add reset without initial context test
- Add tests for cli/instances/index.ts exports
- Add tests for config/index.ts exports
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.

polaz added 2 commits February 4, 2026 23:53
- Add Math.max(0, ...) guard in release() to handle edge cases
- Expand extractBaseUrl comment explaining nested loop necessity
- instances-command.ts: note that config with secrets never reaches logging
- instances-schema.ts: explain multi-strategy parsing rationale
- fetch.ts: add thundering herd note for slot retention during backoff
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated 3 comments.

- Update createDispatcher JSDoc to explain fallback-only role
- Add security note about non-sensitive fields in CLI output
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated 4 comments.

polaz added 2 commits February 5, 2026 00:16
- instances-command.ts: document YAML regex pattern coverage
- fetch.ts: clarify labeled break exits both loops on match
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated 3 comments.

polaz added 2 commits February 5, 2026 00:32
- NamespaceTierDetector: note that sessionId is UUID (no colons)
- fetch.ts: explain why endsWith won't work for mid-path suffixes
- instances-command.ts: note regex won't re-match masked values
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated 1 comment.

User just entered the clientId and needs the exact value for their config.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 47 changed files in this pull request and generated no new comments.

@polaz polaz merged commit 1fc0a8f into main Feb 4, 2026
28 checks passed
@polaz polaz deleted the feat/#274-feat-multi-instance-oauth-federation-with-per-sess branch February 4, 2026 22:59
sw-release-bot bot pushed a commit that referenced this pull request Feb 4, 2026
## [6.55.0](v6.54.0...v6.55.0) (2026-02-04)

### Features

* **multi-instance:** Multi-Instance OAuth Federation with per-session introspection ([#277](#277)) ([1fc0a8f](1fc0a8f)), closes [#274](#274) [#274](#274) [#274](#274) [#274](#274) [#274](#274) [#274](#274)
@sw-release-bot
Copy link
Copy Markdown

sw-release-bot bot commented Feb 4, 2026

🎉 This PR is included in version 6.55.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Multi-Instance OAuth Federation with per-session introspection

3 participants