Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@jamesbhobbs
Copy link
Contributor

@jamesbhobbs jamesbhobbs commented Jan 29, 2026

…tion

Add server mode system that defaults to compact mode for reduced token usage:

  • Hide redundant tools (inspect, stats, lint, dag) in compact mode
  • All handlers default to compact=true output (minimal JSON)
  • Auto-escalation wrapper retries failed operations with verbose output
  • Add deepnote_mode tool to switch between compact/full modes
  • Add compact parameter support to all snapshot tools
  • Update server instructions with performance modes documentation

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Performance Modes: Compact mode (default) for streamlined output and Full mode for comprehensive results.
    • Added automatic escalation that retries failed operations in Full mode.
    • New mode-switching control to toggle between Performance Modes.
    • Added compact output option for snapshot operations.
  • Documentation

    • Updated tool guidance with improved best practices for notebook organization and formatting.
    • Refreshed instructions for consolidated analysis workflows.

✏️ Tip: You can customize this high-level summary in your review settings.

…tion

Add server mode system that defaults to compact mode for reduced token usage:
- Hide redundant tools (inspect, stats, lint, dag) in compact mode
- All handlers default to compact=true output (minimal JSON)
- Auto-escalation wrapper retries failed operations with verbose output
- Add deepnote_mode tool to switch between compact/full modes
- Add compact parameter support to all snapshot tools
- Update server instructions with performance modes documentation
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This PR introduces a two-tier mode system to the MCP server: a default compact mode for streamlined tool output, and a full mode for verbose responses. A new deepnote_mode tool enables mode switching. When executing tools, the server automatically escalates from compact to full mode if an error occurs, retrying the operation. Tool descriptions and formatting logic have been updated across execution, magic, reading, and snapshot tools to use compact as true by default. Workflow instructions and tests document the new behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as Server<br/>(Escalation Handler)
    participant Tool as Tool<br/>(Execution Layer)
    
    Client->>Server: Call Tool (in compact mode)
    Server->>Tool: Execute (compact: true)
    Tool-->>Server: Error Response
    
    alt Escalation Triggered
        Server->>Server: Check: compact enabled?
        Server->>Tool: Retry (compact: false)
        Tool-->>Server: Success with Full Output
        Server-->>Client: Response + Escalation Hint
    else No Escalation
        Server-->>Client: Error Response
    end
Loading
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately captures the core feature: implementing adaptive compact mode with auto-escalation mechanism.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 9.85915% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.31%. Comparing base (68ca655) to head (d1053e1).

Files with missing lines Patch % Lines
packages/mcp/src/tools/snapshots.ts 0.00% 35 Missing ⚠️
packages/mcp/src/server.ts 19.44% 29 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                  @@
##           mcp-skills-optim     #260      +/-   ##
====================================================
- Coverage             74.83%   74.31%   -0.53%     
====================================================
  Files                    96       96              
  Lines                  7110     7164      +54     
  Branches               1999     2017      +18     
====================================================
+ Hits                   5321     5324       +3     
- Misses                 1788     1839      +51     
  Partials                  1        1              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/mcp/src/tools/magic.ts (1)

847-866: Compact default isn’t applied to verbose fields or workflow propagation.

compact now defaults to true, but truthiness checks still treat undefined as false, so hints/sections/results stay verbose by default and workflow drops explicit compact: false. Derive isCompact = compact !== false and pass through explicit booleans.

🛠️ Suggested fix
@@ async function handleScaffold(args: Record<string, unknown>) {
-  const compact = args.compact as boolean | undefined
+  const compact = args.compact as boolean | undefined
+  const isCompact = compact !== false
@@
-    sections: compact
+    sections: isCompact
@@
-    hint: compact
+    hint: isCompact
@@
-        text: formatOutput(responseData, compact !== false),
+        text: formatOutput(responseData, isCompact),
@@ async function handleWorkflow(args: Record<string, unknown>) {
-  const compact = args.compact as boolean | undefined
+  const compact = args.compact as boolean | undefined
+  const isCompact = compact !== false
@@
-        ...(compact ? { compact } : {}),
+        ...(typeof compact === 'boolean' ? { compact } : {}),
@@
-        result: compact ? undefined : parsedResult,
+        result: isCompact ? undefined : parsedResult,
@@
-    results: compact ? results.map(r => ({ step: r.step, tool: r.tool, success: r.success, error: r.error })) : results,
+    results: isCompact ? results.map(r => ({ step: r.step, tool: r.tool, success: r.success, error: r.error })) : results,
@@
-    content: [{ type: 'text', text: formatOutput(responseData, compact !== false) }],
+    content: [{ type: 'text', text: formatOutput(responseData, isCompact) }],

Also applies to: 2722-2750, 2840-2887

packages/mcp/src/tools/execution.ts (1)

475-505: Compact default isn’t applied to execution/results fields.

With compact defaulting to true, execution and full results still show unless compact is explicitly true. Use a derived isCompact = compact !== false consistently.

🛠️ Suggested fix
@@ async function handleRun(args: Record<string, unknown>) {
-  const compact = args.compact as boolean | undefined
+  const compact = args.compact as boolean | undefined
+  const isCompact = compact !== false
@@
-      execution: compact
+      execution: isCompact
         ? undefined
         : {
             startedAt: executionStartedAt,
             finishedAt: executionFinishedAt,
           },
-      results: compact ? results.filter(r => !r.success || r.error) : results,
+      results: isCompact ? results.filter(r => !r.success || r.error) : results,
@@
-          text: formatOutput(responseData, compact !== false),
+          text: formatOutput(responseData, isCompact),
🤖 Fix all issues with AI agents
In `@packages/mcp/src/instructions.ts`:
- Around line 157-175: The documentation still recommends deepnote_lint in the
Compact-mode best-practices; update the guidance to reference deepnote_read with
include=[lint] (or include=[structure,stats,lint,dag] as the combined example)
instead of deepnote_lint, and ensure the Compact-mode bullet explicitly states
that linting should be invoked via deepnote_read include=[lint] while
deepnote_mode mode=full remains the way to enable granular standalone tools.

In `@packages/mcp/src/server.test.ts`:
- Around line 6-43: Add Vitest tests that cover switching to the new deepnote
mode and the auto‑escalation/retry behavior: call resetServerMode() then set
server mode to 'deepnote' (or invoke the API/function that toggles mode), assert
getServerMode() returns 'deepnote', and verify readingTools (and any tool list
provider used in tests) includes the new deepnote-specific tool and excludes
compact-only hidden tools; additionally add a test that simulates a failed
read/operation and asserts the escalation/retry path occurs (e.g., triggers the
escalation code path and checks the output/tool selection after escalation).
Reference resetServerMode, getServerMode, and readingTools when adding these
tests and implement mocks/stubs as needed to simulate failures for the
retry/escalation assertions.

In `@packages/mcp/src/server.ts`:
- Around line 111-144: executeWithEscalation currently retries every failing
tool which can duplicate side effects for non-idempotent tools (writes,
execution, snapshot, conversion); change the retry gate to only allow escalation
when the tool is known idempotent or when the caller explicitly opts in. In
practice: inside executeWithEscalation, consult a property on the handler
invocation (e.g., safeArgs.allowEscalation === true) or a tool metadata flag
(e.g., tool.family or tool.isIdempotent) before performing the retry; if the
tool family is one of writing/execution/snapshot/conversion (or isIdempotent is
false/undefined) do not call handler again and return the original result.
Ensure the new gate is checked both for the initial call and for any other
similar retry logic (e.g., other occurrences around lines 203-260) so
non-idempotent tools never get auto-retried unless explicitly opted-in.
- Around line 87-105: Validate args.mode before changing the global serverMode
inside handleModeSwitch: check that args.mode is one of the allowed ServerMode
values (e.g., using a type guard, enum check, or allowed-values set) and if it
is invalid return an error response (success: false and a descriptive message)
without mutating serverMode or calling getFilteredTools; only assign serverMode
= newMode and proceed to build the success payload when the validation passes.

In `@packages/mcp/src/tools/snapshots.ts`:
- Around line 15-31: The parameter type for formatOutput should be changed from
object to Record<string, unknown> to avoid implicit any from Object.entries;
update the function signature of formatOutput and any related callers to accept
Record<string, unknown>, and when iterating Object.entries(data) treat each
value as unknown and narrow it (e.g., use Array.isArray, v == null, typeof v ===
'object' checks) before accessing properties or Object.keys; specifically adjust
the filtering callback around Object.entries(data) and any type assertions so
the code compiles under noImplicitAny and Biome rules.

Comment on lines +157 to +175
## Performance Modes

The server operates in two modes for optimal performance:

**Compact mode (default):** Optimized for speed and token efficiency
- Responses are minimal single-line JSON
- Redundant tools hidden (use \`deepnote_read\` instead of inspect/stats/lint/dag)
- Use \`deepnote_workflow\` with presets for multi-step operations
- If an error occurs, the server automatically retries with verbose output

**Full mode:** For debugging or when you need more detail
- Call \`deepnote_mode mode=full\` to switch
- All granular tools available (inspect, stats, lint, dag)
- Verbose output with hints and suggestions

**When you see "Escalated to verbose mode":** The operation encountered an issue and was retried with full output for debugging. After resolving, continue using compact mode.

**Recommended:** Use \`deepnote_read\` with \`include=[structure,stats,lint,dag]\` to combine multiple analysis operations in one call.

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Best‑practices bullet still points to deepnote_lint.

Compact mode hides lint, so keep guidance aligned with deepnote_read include=[lint] (or full mode).

📝 Suggested tweak
- - Use `deepnote_lint` to catch issues before running
+ - Use `deepnote_read include=[lint]` (or `deepnote_lint` in full mode) to catch issues before running
🤖 Prompt for AI Agents
In `@packages/mcp/src/instructions.ts` around lines 157 - 175, The documentation
still recommends deepnote_lint in the Compact-mode best-practices; update the
guidance to reference deepnote_read with include=[lint] (or
include=[structure,stats,lint,dag] as the combined example) instead of
deepnote_lint, and ensure the Compact-mode bullet explicitly states that linting
should be invoked via deepnote_read include=[lint] while deepnote_mode mode=full
remains the way to enable granular standalone tools.

Comment on lines +6 to +43
describe('server mode and tool filtering', () => {
beforeEach(() => {
resetServerMode()
})

afterEach(() => {
resetServerMode()
})

describe('server mode state', () => {
it('defaults to compact mode', () => {
expect(getServerMode()).toBe('compact')
})

it('can be reset to compact', () => {
// This test verifies resetServerMode works
resetServerMode()
expect(getServerMode()).toBe('compact')
})
})

describe('compact mode hidden tools', () => {
const COMPACT_HIDDEN_TOOLS = ['deepnote_inspect', 'deepnote_stats', 'deepnote_lint', 'deepnote_dag']

it('reading tools include the tools that should be hidden in compact mode', () => {
const names = readingTools.map(t => t.name)
// Verify these tools exist in reading tools (they get filtered by server in compact mode)
for (const hiddenTool of COMPACT_HIDDEN_TOOLS) {
expect(names).toContain(hiddenTool)
}
})

it('reading tools include deepnote_read as replacement', () => {
const names = readingTools.map(t => t.name)
expect(names).toContain('deepnote_read')
})
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing tests for deepnote_mode switching and auto‑escalation.

Current coverage verifies defaults and filtering but not the new mode tool or retry/escalation behavior. Add tests to flip modes and validate tool lists/output after escalation. As per coding guidelines Create comprehensive tests for all new features using Vitest as the testing framework.

🤖 Prompt for AI Agents
In `@packages/mcp/src/server.test.ts` around lines 6 - 43, Add Vitest tests that
cover switching to the new deepnote mode and the auto‑escalation/retry behavior:
call resetServerMode() then set server mode to 'deepnote' (or invoke the
API/function that toggles mode), assert getServerMode() returns 'deepnote', and
verify readingTools (and any tool list provider used in tests) includes the new
deepnote-specific tool and excludes compact-only hidden tools; additionally add
a test that simulates a failed read/operation and asserts the escalation/retry
path occurs (e.g., triggers the escalation code path and checks the output/tool
selection after escalation). Reference resetServerMode, getServerMode, and
readingTools when adding these tests and implement mocks/stubs as needed to
simulate failures for the retry/escalation assertions.

Comment on lines +87 to +105
function handleModeSwitch(args: Record<string, unknown>) {
const newMode = args.mode as ServerMode
const oldMode = serverMode
serverMode = newMode

return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
previousMode: oldMode,
currentMode: newMode,
toolsAvailable: getFilteredTools().length,
hint:
newMode === 'full'
? 'Full mode enabled. Switch back to compact mode for faster responses.'
: 'Compact mode enabled. Use deepnote_read for inspect/stats/lint/dag.',
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate mode input before mutating global state.
args.mode is blindly cast; invalid values will leave the server in an undefined mode and bypass compact filtering. Guard and return an error.

Suggested fix
 function handleModeSwitch(args: Record<string, unknown>) {
-  const newMode = args.mode as ServerMode
+  const newMode = args.mode
+  if (newMode !== 'compact' && newMode !== 'full') {
+    return {
+      content: [{ type: 'text', text: JSON.stringify({ error: 'mode must be "compact" or "full"' }) }],
+      isError: true,
+    }
+  }
   const oldMode = serverMode
   serverMode = newMode
🤖 Prompt for AI Agents
In `@packages/mcp/src/server.ts` around lines 87 - 105, Validate args.mode before
changing the global serverMode inside handleModeSwitch: check that args.mode is
one of the allowed ServerMode values (e.g., using a type guard, enum check, or
allowed-values set) and if it is invalid return an error response (success:
false and a descriptive message) without mutating serverMode or calling
getFilteredTools; only assign serverMode = newMode and proceed to build the
success payload when the validation passes.

Comment on lines +111 to +144
/**
* Execute a tool with auto-escalation: if compact mode fails, retry with verbose output
*/
async function executeWithEscalation(
handler: (
name: string,
args: Record<string, unknown> | undefined
) => Promise<{
content: Array<{ type: string; text?: string }>
isError?: boolean
}>,
name: string,
args: Record<string, unknown> | undefined
) {
const safeArgs = args || {}

// First attempt - uses default compact=true (since we changed defaults)
const result = await handler(name, safeArgs)

// If it failed and compact wasn't explicitly disabled, retry with verbose output
if (result.isError && safeArgs.compact !== false) {
const fullResult = await handler(name, { ...safeArgs, compact: false })

// Add de-escalation hint to the response
fullResult.content.push({
type: 'text',
text: '\n---\n[Escalated to verbose mode for debugging. Use compact=true or omit for faster responses.]',
})

return fullResult
}

return result
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Auto-retry can duplicate side effects on non-idempotent tools.
All tool families are retried on error; writing/execution/snapshot/conversion can mutate workspace or trigger runs. Gate retries to idempotent/read-only tools or require explicit opt-in.

Suggested fix
-async function executeWithEscalation(
+async function executeWithEscalation(
   handler: (
     name: string,
     args: Record<string, unknown> | undefined
   ) => Promise<{
     content: Array<{ type: string; text?: string }>
     isError?: boolean
   }>,
   name: string,
-  args: Record<string, unknown> | undefined
+  args: Record<string, unknown> | undefined,
+  retryOnError = true
 ) {
   const safeArgs = args || {}

   // First attempt - uses default compact=true (since we changed defaults)
   const result = await handler(name, safeArgs)

   // If it failed and compact wasn't explicitly disabled, retry with verbose output
-  if (result.isError && safeArgs.compact !== false) {
+  if (retryOnError && result.isError && safeArgs.compact !== false) {
     const fullResult = await handler(name, { ...safeArgs, compact: false })
@@
-        return await executeWithEscalation(handleWritingTool, name, args)
+        return await executeWithEscalation(handleWritingTool, name, args, false)
@@
-        return await executeWithEscalation(handleConversionTool, name, args)
+        return await executeWithEscalation(handleConversionTool, name, args, false)
@@
-        return await executeWithEscalation(handleExecutionTool, name, args)
+        return await executeWithEscalation(handleExecutionTool, name, args, false)
@@
-        return await executeWithEscalation(handleSnapshotTool, name, args)
+        return await executeWithEscalation(handleSnapshotTool, name, args, false)

Also applies to: 203-260

🤖 Prompt for AI Agents
In `@packages/mcp/src/server.ts` around lines 111 - 144, executeWithEscalation
currently retries every failing tool which can duplicate side effects for
non-idempotent tools (writes, execution, snapshot, conversion); change the retry
gate to only allow escalation when the tool is known idempotent or when the
caller explicitly opts in. In practice: inside executeWithEscalation, consult a
property on the handler invocation (e.g., safeArgs.allowEscalation === true) or
a tool metadata flag (e.g., tool.family or tool.isIdempotent) before performing
the retry; if the tool family is one of writing/execution/snapshot/conversion
(or isIdempotent is false/undefined) do not call handler again and return the
original result. Ensure the new gate is checked both for the initial call and
for any other similar retry logic (e.g., other occurrences around lines 203-260)
so non-idempotent tools never get auto-retried unless explicitly opted-in.

Comment on lines +15 to +31
/**
* Format output based on compact mode - omit null/empty, use single-line JSON
*/
function formatOutput(data: object, compact: boolean): string {
if (compact) {
const filtered = Object.fromEntries(
Object.entries(data).filter(([_, v]) => {
if (v == null) return false
if (Array.isArray(v) && v.length === 0) return false
if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) return false
return true
})
)
return JSON.stringify(filtered)
}
return JSON.stringify(data, null, 2)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the full context of the file to understand how formatOutput is used
cat -n packages/mcp/src/tools/snapshots.ts | head -50

Repository: deepnote/deepnote

Length of output: 2034


🏁 Script executed:

# Check if formatOutput is called elsewhere and what data types are passed
rg 'formatOutput' packages/mcp/src/tools/snapshots.ts -B 2 -A 2

Repository: deepnote/deepnote

Length of output: 862


🏁 Script executed:

# Let's check the biome configuration to understand linting rules
cat biome.json | head -100

Repository: deepnote/deepnote

Length of output: 3258


🏁 Script executed:

# Check if there are TypeScript config settings relevant to typing strictness
if [ -f tsconfig.json ]; then cat tsconfig.json | head -50; fi

Repository: deepnote/deepnote

Length of output: 793


🏁 Script executed:

# Verify TypeScript's Object.entries typing behavior by checking what the actual types resolve to
cat > /tmp/test-entries.ts << 'EOF'
// Test how Object.entries types with different parameter types
function testObject(data: object) {
  const entries = Object.entries(data);
  // entries should be [string, any][]
  entries.forEach(([k, v]) => {
    // v should be any here
  });
}

function testRecord(data: Record<string, unknown>) {
  const entries = Object.entries(data);
  // entries should be [string, unknown][]
  entries.forEach(([k, v]) => {
    // v should be unknown here
  });
}
EOF
cat /tmp/test-entries.ts

Repository: deepnote/deepnote

Length of output: 516


🏁 Script executed:

# Check the actual usage context - what types are passed to formatOutput
rg 'responseData' packages/mcp/src/tools/snapshots.ts -B 3 -A 1 | head -40

Repository: deepnote/deepnote

Length of output: 832


Use Record<string, unknown> instead of object to avoid implicit any.

Object.entries(data) with data: object resolves to [string, any][], violating project's noImplicitAny and Biome's noExplicitAny rules. Since formatOutput processes external/runtime data from tools, Record<string, unknown> maintains type safety.

Suggested change
-function formatOutput(data: object, compact: boolean): string {
+function formatOutput(data: Record<string, unknown>, compact: boolean): string {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Format output based on compact mode - omit null/empty, use single-line JSON
*/
function formatOutput(data: object, compact: boolean): string {
if (compact) {
const filtered = Object.fromEntries(
Object.entries(data).filter(([_, v]) => {
if (v == null) return false
if (Array.isArray(v) && v.length === 0) return false
if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) return false
return true
})
)
return JSON.stringify(filtered)
}
return JSON.stringify(data, null, 2)
}
/**
* Format output based on compact mode - omit null/empty, use single-line JSON
*/
function formatOutput(data: Record<string, unknown>, compact: boolean): string {
if (compact) {
const filtered = Object.fromEntries(
Object.entries(data).filter(([_, v]) => {
if (v == null) return false
if (Array.isArray(v) && v.length === 0) return false
if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) return false
return true
})
)
return JSON.stringify(filtered)
}
return JSON.stringify(data, null, 2)
}
🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/snapshots.ts` around lines 15 - 31, The parameter type
for formatOutput should be changed from object to Record<string, unknown> to
avoid implicit any from Object.entries; update the function signature of
formatOutput and any related callers to accept Record<string, unknown>, and when
iterating Object.entries(data) treat each value as unknown and narrow it (e.g.,
use Array.isArray, v == null, typeof v === 'object' checks) before accessing
properties or Object.keys; specifically adjust the filtering callback around
Object.entries(data) and any type assertions so the code compiles under
noImplicitAny and Biome rules.

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