Skip to content

fix(mcp): reorder enforceScopes guard before MCP_TOOL_MAP lookup, add scopes to all dynamic tool definitions - #2958

Merged
diegosouzapw merged 2 commits into
diegosouzapw:mainfrom
branben:upstream/fix/r1-scope-enforcement
May 31, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:mainfrom
branben:upstream/fix/r1-scope-enforcement

Conversation

@branben

@branben branben commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes scope enforcement for dynamic MCP tool groups (memory, skills, plugins, compression, gamification). The evaluateToolScopes() guard was checking MCP_TOOL_MAP before checking dynamic inline scopes, causing dynamic tools to always fail scope checks.

Changes

  • scopeEnforcement.ts — reordered guard logic in evaluateToolScopes(): inline scopes checked first, MCP_TOOL_MAP fallback only when no inline scopes provided
  • server.ts — withScopeEnforcement() now accepts optional toolScopes parameter; all dynamic tool groups pass their declared scopes
  • compressionTools.ts, gamificationTools.ts, memoryTools.ts, pluginTools.ts, skillTools.ts — added scopes field to all dynamic tool definitions

Testing

node --import tsx/esm --test tests/unit/t08-mcp-scope-enforcement.test.ts

7 scope-enforcement tests pass.

…to all dynamic tool definitions

- Move !enforceScopes guard before MCP_TOOL_MAP lookup in evaluateToolScopes()
- Add inlineScopes parameter for dynamic tool scope resolution
- Add scopes to all 33 dynamic tool definitions across 5 tool files
- Wire toolDef.scopes through withScopeEnforcement in server.ts
- Preserves existing behavior: tool_definition_missing returned when
  enforceScopes=true and no scopes found anywhere
@branben
branben requested a review from diegosouzapw as a code owner May 30, 2026 23:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the MCP server's scope enforcement mechanism to support inline scope definitions across various tools, including compression, gamification, memory, plugin, and skill tools. It updates the scope evaluation logic to accept an optional inlineScopes parameter. A critical issue was identified in the scope evaluation logic where tools that explicitly require no scopes (an empty scopes array) are incorrectly blocked and flagged as having a missing tool definition. A code suggestion was provided to resolve this by checking if the tool scopes are undefined instead of checking if the required scopes array is empty.

Comment on lines +111 to 122
const toolScopes = inlineScopes ?? MCP_TOOL_MAP[toolName]?.scopes;
const required = Array.isArray(toolScopes) ? Array.from(toolScopes) : [];

if (required.length === 0) {
return {
allowed: false,
required: [],
provided: Array.from(callerScopes),
provided,
missing: [],
reason: "tool_definition_missing",
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The current implementation treats any tool with an empty scopes list (required.length === 0) as having a missing tool definition, returning allowed: false with reason: "tool_definition_missing". This blocks any tool that explicitly requires no scopes (e.g., scopes: []).

Instead, we should check if toolScopes is undefined to determine if the tool definition is missing. If toolScopes is defined (even if it is an empty array), and no scopes are required, the tool should be allowed.

Suggested change
const toolScopes = inlineScopes ?? MCP_TOOL_MAP[toolName]?.scopes;
const required = Array.isArray(toolScopes) ? Array.from(toolScopes) : [];
if (required.length === 0) {
return {
allowed: false,
required: [],
provided: Array.from(callerScopes),
provided,
missing: [],
reason: "tool_definition_missing",
};
}
const toolScopes = inlineScopes ?? MCP_TOOL_MAP[toolName]?.scopes;
if (toolScopes === undefined) {
return {
allowed: false,
required: [],
provided,
missing: [],
reason: "tool_definition_missing",
};
}
const required = Array.isArray(toolScopes) ? Array.from(toolScopes) : [];

@branben

branben commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

PR Reviewer Guide 🔍

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Key issues to review

Code Smell
The added line defining docs is extremely long (hundreds of characters) and contains a massive inline object with many import statements. This violates typical line‑length and readability guidelines, may cause linting failures, and makes future maintenance harder. Consider extracting the object into a separate constant or formatting it across multiple lines.

Possible Bug
The updated evaluateToolScopes now returns a provided field even when the tool definition is missing (reason tool_definition_missing). Callers currently only inspect allowed, missing, and reason. Ensure that any logic relying on the shape of the result (e.g., logging or audit) correctly handles the new provided property and does not assume it is always present.

@kilo-code-bot

kilo-code-bot Bot commented May 30, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The PR implements a critical fix for MCP scope enforcement on dynamic tools. The changes are well-structured:

  1. scopeEnforcement.ts (lines 99-135): Correctly reorders the !enforceScopes check to execute before MCP_TOOL_MAP lookup. This ensures dynamic tools that don't exist in the static map still properly enforce their inline scopes when enforceScopes is enabled.

  2. server.ts: Updates withScopeEnforcement() calls for memory, skills, plugins, compression, and gamification tools to pass their toolDef.scopes as the third parameter, enabling scope validation for these 24 previously unscoped tools.

  3. Tool files: All 24 dynamic tools now declare appropriate scopes (read:memory, write:memory, read:skills, write:skills, execute:skills, read:compression, write:compression, read:gamification, write:gamification, read:plugins, write:plugins).

  4. Documentation: MCP-SERVER.md correctly reflects the new tool counts (43 vs 37) and the scope assignments for the newly-scoped tool categories.

  5. CHANGELOG: Properly documents the fix.

The existing comment on line 122 of scopeEnforcement.ts appears incomplete/corrupted and does not match any visible issue in the current code. The logic at line 114 (if (required.length === 0)) correctly returns allowed: false when neither inline scopes nor MCP_TOOL_MAP provides scopes for a tool, while the early return for !enforceScopes ensures tools work when scope enforcement is disabled.

Files Reviewed (6 files)
  • open-sse/mcp-server/scopeEnforcement.ts - scope logic fix
  • open-sse/mcp-server/server.ts - tool registration updates
  • open-sse/mcp-server/tools/compressionTools.ts - scope declarations
  • open-sse/mcp-server/tools/memoryTools.ts - scope declarations
  • open-sse/mcp-server/tools/skillTools.ts - scope declarations
  • open-sse/mcp-server/tools/gamificationTools.ts - scope declarations
  • open-sse/mcp-server/tools/pluginTools.ts - scope declarations
  • docs/frameworks/MCP-SERVER.md - documentation
  • CHANGELOG.md - changelog entry

Reviewed by laguna-m.1-20260312:free · 786,427 tokens

@branben branben closed this May 30, 2026
@branben
branben deleted the upstream/fix/r1-scope-enforcement branch May 30, 2026 23:56
@branben
branben restored the upstream/fix/r1-scope-enforcement branch May 30, 2026 23:59
@branben branben reopened this May 30, 2026
@diegosouzapw

Copy link
Copy Markdown
Owner

Thank you for your contribution! This PR has been reviewed and integrated into the upcoming v3.8.8 release. 🎉

@diegosouzapw
diegosouzapw merged commit 38221f2 into diegosouzapw:main May 31, 2026
3 checks passed
diegosouzapw added a commit that referenced this pull request Jun 2, 2026
…add contributor hall

Audited all 687 commits / 60 release/v3.8.8 PRs since v3.8.7 against the CHANGELOG:

- Added 5 missing Fixed entries: #3052 (heap-pressure auto-calibration),
  #3051/#3048 (proxy fail-closed + registry assignments, @terence71-glitch),
  #3049/#3046 (session-pool fingerprint rotation + claude-web cf_clearance, @oyi77).
- Credited previously-uncredited contributors: @branben (#2958 scope fix, #2959
  Notion context source) and @JxnLexn (per-API-key stream default mode).
- Added an Added entry for the per-API-key stream default mode feature.
- Added the "🏆 Contributors" hall (24 contributors), matching the v3.8.6 format.

Maintainer fix-PRs (#2966–#3030) are intentionally referenced by their original
issue numbers in the body rather than the fix-PR number; @diegosouzapw is in the hall.
This was referenced Jun 2, 2026
diegosouzapw added a commit that referenced this pull request Jun 5, 2026
…s + add contributor hall

Consolidate the split [Unreleased]/[3.8.11] sections into one, add entries for
every merged contributor PR that was missing credit (#3170/#3171/#3172 @pizzav-xyz,
#3185/#3195 @zhiru, #3188 @xz-dev, #3189/#3203/#3204/#3241 @wilsonicdev, #3191 @bypanghu,
#3206 @juandisay, #3217 @oyi77, #3226 @miracuves, #3187/#3200 maintainer), drop stale
v3.8.8 leftovers (#2958/#2959 already shipped) and 3 empty v3.8.10 stub headers.
wilsonicdev pushed a commit to wilsonicdev/OmniRoute that referenced this pull request Jun 6, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…add contributor hall

Audited all 687 commits / 60 release/v3.8.8 PRs since v3.8.7 against the CHANGELOG:

- Added 5 missing Fixed entries: diegosouzapw#3052 (heap-pressure auto-calibration),
  diegosouzapw#3051/diegosouzapw#3048 (proxy fail-closed + registry assignments, @terence71-glitch),
  diegosouzapw#3049/diegosouzapw#3046 (session-pool fingerprint rotation + claude-web cf_clearance, @oyi77).
- Credited previously-uncredited contributors: @branben (diegosouzapw#2958 scope fix, diegosouzapw#2959
  Notion context source) and @JxnLexn (per-API-key stream default mode).
- Added an Added entry for the per-API-key stream default mode feature.
- Added the "🏆 Contributors" hall (24 contributors), matching the v3.8.6 format.

Maintainer fix-PRs (diegosouzapw#2966–diegosouzapw#3030) are intentionally referenced by their original
issue numbers in the body rather than the fix-PR number; @diegosouzapw is in the hall.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
… scopes to all dynamic tool definitions (diegosouzapw#2958)

Integrated into release/v3.8.8
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…add contributor hall

Audited all 687 commits / 60 release/v3.8.8 PRs since v3.8.7 against the CHANGELOG:

- Added 5 missing Fixed entries: diegosouzapw#3052 (heap-pressure auto-calibration),
  diegosouzapw#3051/diegosouzapw#3048 (proxy fail-closed + registry assignments, @terence71-glitch),
  diegosouzapw#3049/diegosouzapw#3046 (session-pool fingerprint rotation + claude-web cf_clearance, @oyi77).
- Credited previously-uncredited contributors: @branben (diegosouzapw#2958 scope fix, diegosouzapw#2959
  Notion context source) and @JxnLexn (per-API-key stream default mode).
- Added an Added entry for the per-API-key stream default mode feature.
- Added the "🏆 Contributors" hall (24 contributors), matching the v3.8.6 format.

Maintainer fix-PRs (diegosouzapw#2966–diegosouzapw#3030) are intentionally referenced by their original
issue numbers in the body rather than the fix-PR number; @diegosouzapw is in the hall.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
… scopes to all dynamic tool definitions (diegosouzapw#2958)

Integrated into release/v3.8.8
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…add contributor hall

Audited all 687 commits / 60 release/v3.8.8 PRs since v3.8.7 against the CHANGELOG:

- Added 5 missing Fixed entries: diegosouzapw#3052 (heap-pressure auto-calibration),
  diegosouzapw#3051/diegosouzapw#3048 (proxy fail-closed + registry assignments, @terence71-glitch),
  diegosouzapw#3049/diegosouzapw#3046 (session-pool fingerprint rotation + claude-web cf_clearance, @oyi77).
- Credited previously-uncredited contributors: @branben (diegosouzapw#2958 scope fix, diegosouzapw#2959
  Notion context source) and @JxnLexn (per-API-key stream default mode).
- Added an Added entry for the per-API-key stream default mode feature.
- Added the "🏆 Contributors" hall (24 contributors), matching the v3.8.6 format.

Maintainer fix-PRs (diegosouzapw#2966–diegosouzapw#3030) are intentionally referenced by their original
issue numbers in the body rather than the fix-PR number; @diegosouzapw is in the hall.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… scopes to all dynamic tool definitions (diegosouzapw#2958)

Integrated into release/v3.8.8
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
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