Skip to content

feat: Token scope detection at startup with graceful degradation#190

Merged
polaz merged 15 commits intomainfrom
feat/#188-feat-token-scope-detection-at-startup-with-gracefu
Jan 24, 2026
Merged

feat: Token scope detection at startup with graceful degradation#190
polaz merged 15 commits intomainfrom
feat/#188-feat-token-scope-detection-at-startup-with-gracefu

Conversation

@polaz
Copy link
Copy Markdown
Member

@polaz polaz commented Jan 24, 2026

Summary

  • Add TokenScopeDetector service that calls /api/v4/personal_access_tokens/self at startup to discover token scopes before attempting GraphQL introspection
  • Skip GraphQL introspection entirely when token lacks api/read_api scope — eliminates ugly 401 stack traces
  • Register only tools matching the token's actual permissions via scope-based filtering in RegistryManager
  • Show clean INFO messages with token name, available tools count, and actionable fix URL
  • Warn when token expires within 7 days
  • Add comprehensive docs/guide/authentication.md with PAT + OAuth walkthroughs, scope comparison table, and troubleshooting
  • Link authentication guide from all installation, deployment, and troubleshooting docs

Closes #188

Test plan

  • 27 new unit tests for TokenScopeDetector (detection, scope filtering, expiry, edge cases)
  • Updated RegistryManager.test.ts mock for new getTokenScopeInfo()
  • All 3908 existing unit tests pass
  • ESLint: 0 errors
  • TypeScript build: success
  • Manual: verify startup logs with api scope token (brief message, all tools)
  • Manual: verify startup logs with read_user-only token (limited message, 3 tools, no stack traces)
  • Manual: verify token expiry warning with soon-expiring token

Add TokenScopeDetector service that calls /api/v4/personal_access_tokens/self
at startup to discover token scopes. Based on detected scopes:

- Skip GraphQL introspection when token lacks api/read_api (no 401 stack traces)
- Register only tools matching the token's permissions
- Show clean INFO messages with actionable fix URLs
- Warn when token expires within 7 days

Also adds comprehensive authentication documentation (docs/guide/authentication.md)
with PAT and OAuth walkthroughs, scope comparison table, and troubleshooting.
Links added across all installation/deployment docs.
Copilot AI review requested due to automatic review settings January 24, 2026 18:09
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 24, 2026

Test Coverage Report

Overall Coverage: 93.73%

Metric Percentage
Statements 93.23%
Branches 84.96%
Functions 83.21%
Lines 93.73%

View detailed coverage report

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 24, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/TokenScopeDetector.ts 95.65% 0 Missing and 4 partials ⚠️
src/services/ConnectionManager.ts 88.46% 0 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

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

Adds startup-time GitLab token scope detection to avoid noisy GraphQL 401 stack traces and to register only tools permitted by the token, along with new/updated authentication documentation.

Changes:

  • Introduce TokenScopeDetector to detect scopes via /api/v4/personal_access_tokens/self, compute capabilities, and emit user-friendly startup logs (incl. expiry warnings).
  • Update ConnectionManager to detect scopes before GraphQL introspection, skip introspection when GraphQL scopes are missing, and add a REST-based version fallback.
  • Filter tool registration in RegistryManager based on detected token scopes; add/refresh docs linking to a new authentication guide.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/unit/services/TokenScopeDetector.test.ts Adds unit coverage for scope detection, tool filtering helpers, URL generation, and logging behavior.
tests/unit/RegistryManager.test.ts Updates mocks to accommodate getTokenScopeInfo() and scope filtering.
src/services/TokenScopeDetector.ts Implements token self-introspection, scope→tool mapping, and startup logging helpers.
src/services/ConnectionManager.ts Runs scope detection before introspection; skips GraphQL when not allowed; adds REST version fallback and exposes token scope info.
src/registry-manager.ts Applies scope-based filtering when building the available tool list.
docs/troubleshooting/connection.md Links scope troubleshooting to the new authentication guide.
docs/index.md Points token creation guidance to the authentication guide.
docs/guides/team-onboarding.md Links team onboarding token requirements to the authentication guide.
docs/guide/quick-start.md Adds a pointer to the authentication guide for first-time setup.
docs/guide/installation/npm.md Links GITLAB_TOKEN requirements to the authentication guide.
docs/guide/installation/manual.md Adds authentication guide link for token creation steps.
docs/guide/installation/index.md Links prerequisite PAT guidance to the authentication guide.
docs/guide/installation/docker.md Links PAT/scopes guidance to the authentication guide.
docs/guide/installation/claude-desktop.md Links PAT requirement to the authentication guide.
docs/guide/configuration.md Links GITLAB_TOKEN configuration details to the authentication guide.
docs/guide/authentication.md Adds comprehensive PAT + OAuth authentication walkthrough and scope comparison/troubleshooting.
docs/deployment/local-stdio.md Links PAT requirement to the authentication guide.
docs/deployment/docker-standalone.md Links PAT requirement to the authentication guide.
docs/.vitepress/config.mts Adds “Authentication” to the docs navigation.

semantic-release-bot and others added 3 commits January 24, 2026 20:36
## [6.41.4](v6.41.3...v6.41.4) (2026-01-24)

### Bug Fixes

* **auth:** use PRIVATE-TOKEN header for PAT authentication instead of Bearer ([#189](#189)) ([7799dde](7799dde)), closes [#187](#187)
- Replace global fetch with enhancedFetch in detectTokenScopes and
  detectVersionViaREST (respects timeouts, proxy, TLS settings)
- Parse expires_at as UTC date to avoid timezone off-by-one errors
- Use URL/URLSearchParams for proper encoding in getTokenCreationUrl
- Derive totalTools count dynamically from getToolScopeRequirements()
- Use jest.useFakeTimers() for deterministic expiry tests
- Test project/group token types, REST fallback, scope-gated paths
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 20 out of 20 changed files in this pull request and generated 3 comments.

- Cache getTokenCreationUrl result in logTokenScopeInfo to avoid
  double URL construction
- Replace `as any` with proper GitLabScope[] type in test fixtures
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 20 out of 20 changed files in this pull request and generated 2 comments.

…xt from scope map

Replace unsafe `as` type cast on /personal_access_tokens/self response
with Zod schema validation via safeParse(). Returns null with a debug
log if the response shape doesn't match expectations.

Remove manage_context from TOOL_SCOPE_REQUIREMENTS since it manages
local session state and never calls GitLab API — it's always available.
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 20 out of 20 changed files in this pull request and generated 5 comments.

…oc counts

- getToolScopeRequirements() now returns deep clone (arrays copied too)
- getTokenCreationUrl() preserves subpath for self-hosted instances
- Documentation updated: 43 scope-gated tools, read_user=2, read_api=23
- Example log output fixed to show URL-encoded comma (%2C)
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 20 out of 20 changed files in this pull request and generated 2 comments.

… type heuristic

- Replace unsafe `as GitLabScope[]` cast with z.enum validation that
  filters unknown scopes (future GitLab scopes are silently ignored)
- Derive GitLabScope type from const array via z.infer
- Remove token type detection by name prefix — token names are
  user-controlled and cannot reliably determine token type
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 20 out of 20 changed files in this pull request and generated 3 comments.

…ope filter test

- Log message now says "scope-gated tools" to clarify count excludes
  always-available tools like manage_context
- tokenType defaults to "unknown" since type cannot be reliably inferred
- Add RegistryManager unit test verifying scope-based tool filtering
@polaz polaz requested review from Copilot and removed request for Copilot January 24, 2026 20:03
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 20 out of 20 changed files in this pull request and generated 3 comments.

…te docs

- getTokenCreationUrl() now catches URL parse errors for schemeless
  baseUrl and falls back to string concatenation
- daysUntilExpiry === 0 now logs "expires today" instead of "has expired"
- Troubleshooting docs updated to match current log format
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 20 out of 20 changed files in this pull request and generated 1 comment.

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 20 out of 20 changed files in this pull request and generated 1 comment.

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 20 out of 20 changed files in this pull request and generated 1 comment.

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 20 out of 20 changed files in this pull request and generated no new comments.

@polaz polaz merged commit 28bab03 into main Jan 24, 2026
25 of 26 checks passed
@polaz polaz deleted the feat/#188-feat-token-scope-detection-at-startup-with-gracefu branch January 24, 2026 21:09
sw-release-bot bot pushed a commit that referenced this pull request Jan 25, 2026
## [6.42.0](v6.41.4...v6.42.0) (2026-01-25)

### Features

* Token scope detection at startup with graceful degradation ([#190](#190)) ([28bab03](28bab03)), closes [#188](#188)

### Bug Fixes

* **workitems:** use two-step approach for timeEstimate on create ([#195](#195)) ([98abf4e](98abf4e)), closes [#193](#193)
@sw-release-bot
Copy link
Copy Markdown

🎉 This PR is included in version 6.42.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

polaz pushed a commit that referenced this pull request Jan 25, 2026
## [6.42.0](v6.41.4...v6.42.0) (2026-01-25)

### Features

* Token scope detection at startup with graceful degradation ([#190](#190)) ([28bab03](28bab03)), closes [#188](#188)

### Bug Fixes

* **workitems:** use two-step approach for timeEstimate on create ([#195](#195)) ([98abf4e](98abf4e)), closes [#193](#193)
polaz added a commit that referenced this pull request Jan 25, 2026
…ck aggregation (#196)

* feat(logging): Implement condensed access log format with request stack aggregation

Implements single-line access log format inspired by nginx/envoy. Instead of
multiple log lines per request, all events are aggregated into a single
condensed entry when the request completes.

Features:
- Request stack aggregation: collect tool, action, GitLab response timing
- Single-line access log format with timestamp, client, session, method, path,
  status, duration, tool/action, GitLab status/duration, and details
- Connection tracking: log session stats (requests, tools, errors) on close
- AsyncLocalStorage for transparent context propagation through async calls
- LOG_FORMAT environment variable: "condensed" (default) or "verbose"

Access log format:
[timestamp] ip session method path status duration | tool action | gitlab_status gitlab_duration | details

Connection close format:
[timestamp] CONN_CLOSE ip session duration reason | reqs=N tools=N errs=N

Closes #194

* chore(release): 6.42.0 [skip ci]

## [6.42.0](v6.41.4...v6.42.0) (2026-01-25)

### Features

* Token scope detection at startup with graceful degradation ([#190](#190)) ([28bab03](28bab03)), closes [#188](#188)

### Bug Fixes

* **workitems:** use two-step approach for timeEstimate on create ([#195](#195)) ([98abf4e](98abf4e)), closes [#193](#193)

* fix(logging): nginx-style alignment, quote escaping, dedupe LogFormat type

- All fields always present with "-" for missing values (nginx-style alignment)
- Escape quotes and backslashes in log values for safe parsing
- Import LogFormat from logging/types.ts to avoid type duplication
- Add comments explaining close handler behavior for new sessions
- Fix flaky test assertion using regex for duration matching

* test(config): add LOG_FORMAT parsing tests

Add unit tests for LOG_FORMAT environment variable parsing:
- Default to 'condensed' when not set
- Parse 'verbose' value
- Case-insensitive parsing
- Invalid values default to 'condensed'

* feat(logging): add context and read-only mode to access logs

- Add ctx (context path) and ro (read-only) fields to access log format
- Capture session context and read-only state during tool execution
- Fix SSE close handling: distinguish normal disconnect from abort
- Fix session request counting: count initial request on session init
- Update session ID on stack after session initialization
- Escape newlines/tabs in log values to maintain single-line format
- Add needsQuoting helper to detect control characters

Access log format now:
[ts] ip session ctx ro method path status dur | tool action | GL:status dur | details

* test(logging): add addDetailsForCurrentRequest context-aware test

* fix(logging): gate per-request INFO logs behind LOG_FORMAT=verbose

In condensed mode, suppress individual "Tool called", "Connection verified",
"Executing tool" INFO logs - these are aggregated into single access log line.
Also fix ConnectionTracker.closeConnection to return early when disabled.

* fix(logging): add request tracking to SSE /messages endpoint

- Gate access log tracking in handlers.ts behind LOG_FORMAT=condensed
- Add request tracking to SSE /messages handler:
  - Call connectionTracker.incrementRequests for each request
  - Update request stack with SSE session ID (query param vs header)
  - Wrap handlePostMessage in runWithRequestContextAsync

* fix(logging): move access log middleware before rate limiter

- Register access logging before rate limiter to capture 429 responses
- Update docstring format to include ctx and ro fields
- Correct comment about session ID availability in first request

* fix(server): use optional chaining for res.locals in SSE handler

Prevents TypeError when res.locals is undefined in test mocks.

* fix(logging): cleanup connections on close even when tracking disabled

Prevents memory leak when setEnabled(false) is called while connections
are still open - connections are now always deleted from the map.

* fix(logging): add session tracking for SSE /sse endpoint

- Update request stack with SSE session ID for access log
- Count initial GET /sse request in connection stats

---------

Co-authored-by: semantic-release-bot <[email protected]>
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: Token scope detection at startup with graceful degradation

3 participants