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

Skip to content

Conversation

@jamesbhobbs
Copy link
Contributor

@jamesbhobbs jamesbhobbs commented Jan 29, 2026

Reduce tool calls and token usage by:

  • Add includeOutputSummary to deepnote_run (default: true) to inline truncated block outputs, eliminating need for snapshot_load in most cases
  • Add unified deepnote_read tool that combines inspect/stats/lint/dag operations with configurable include parameter
  • Add compact output mode to key tools for minimal JSON formatting
  • Add workflow presets (quickstart, import, polish) to deepnote_workflow for common multi-step operations in a single call
  • Shorten verbose tool descriptions to reduce system prompt tokens

Changes:

  • packages/mcp/src/tools/execution.ts: Add includeOutputSummary, compact params
  • packages/mcp/src/tools/reading.ts: Add deepnote_read, formatOutput helper
  • packages/mcp/src/tools/magic.ts: Add presets to workflow, compact to tools
  • packages/mcp/src/server.ts: Route deepnote_read to reading handler
  • Add reading.test.ts and update tools.test.ts, magic.test.ts with new tests

Summary by CodeRabbit

Release Notes

  • New Features
    • Added unified "Read Notebook" tool for comprehensive notebook analysis with filtering options.
    • Introduced compact output mode across all tools for reduced verbosity and streamlined results.
    • Added workflow presets (quickstart, polish) for common automation patterns.
    • Enhanced execution results with optional output summaries and improved formatting.

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

Reduce tool calls and token usage by:
- Add includeOutputSummary to deepnote_run (default: true) to inline truncated
  block outputs, eliminating need for snapshot_load in most cases
- Add unified deepnote_read tool that combines inspect/stats/lint/dag operations
  with configurable include parameter
- Add compact output mode to key tools for minimal JSON formatting
- Add workflow presets (quickstart, import, polish) to deepnote_workflow for
  common multi-step operations in a single call
- Shorten verbose tool descriptions to reduce system prompt tokens

Changes:
- packages/mcp/src/tools/execution.ts: Add includeOutputSummary, compact params
- packages/mcp/src/tools/reading.ts: Add deepnote_read, formatOutput helper
- packages/mcp/src/tools/magic.ts: Add presets to workflow, compact to tools
- packages/mcp/src/server.ts: Route deepnote_read to reading handler
- Add reading.test.ts and update tools.test.ts, magic.test.ts with new tests
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

The PR adds a new unified deepnote_read tool for notebook analysis, introduces compact output formatting across magic and reading tools, and extends workflow execution with preset support (quickstart, import, polish). New compact and includeOutputSummary parameters control output verbosity and formatting. Routing improvements in the server handle the new read tool. Execution output handling now generates optional summaries and supports flexible payload formatting while preserving existing snapshot behavior.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% 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 reflects the main objectives: optimizing tool invocations (unified deepnote_read, workflow presets) and reducing token usage (compact mode, output summaries).

✏️ 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.

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 (2)

145-175: deepnote_explain missing compact schema property.

Other tools have compact in their input schema, but explain doesn't. The handler also doesn't use it. Inconsistent API surface.


363-411: Schema allows calling with no preset or steps.

The inputSchema has no required array. User could invoke deepnote_workflow with empty args. Lines 2750-2755 handle this, but schema validation should catch it earlier.

Schema improvement
       },
     },
+    required: [],
+    oneOf: [
+      { required: ['preset'] },
+      { required: ['steps'] }
+    ],
   },

Or add a note that one of preset or steps is required.

🤖 Fix all issues with AI agents
In `@packages/mcp/src/tools/execution.ts`:
- Around line 38-51: formatOutput currently only removes empty/null values at
the top level, allowing nested empty objects/arrays to remain; update
formatOutput to perform a deep/recursive filter when compact is true by walking
objects and arrays and removing nulls, empty arrays, and empty objects at any
depth (suggest adding a helper like deepFilterEmpty(value): any that returns
undefined for pruned values and recurses into arrays/objects), then
JSON.stringify the result of that helper when compact is true; ensure the helper
preserves non-empty primitives and avoids infinite recursion on circular refs
(e.g., by skipping or throwing if cycles detected).

In `@packages/mcp/src/tools/magic.ts`:
- Around line 2877-2886: The compact mapping in responseData currently always
sets error: r.error which yields explicit undefined entries; update the mapping
used in the compact branch (the results.map(...) expression) to only include the
error property when r.error is present (e.g., build each object with { step:
r.step, tool: r.tool, success: r.success, ...(r.error ? { error: r.error } : {})
}) so successful steps omit the error key before passing to formatOutput.
- Around line 2813-2820: Replace the dynamic import + .then pattern for the
reading and conversion tools with static top-level imports and direct calls: add
top-level imports for handleReadingTool and handleConversionTool, then in the
switch cases replace the await import(...).then(...) expressions with direct
calls like await handleReadingTool('deepnote_lint', toolArgs) and await
handleConversionTool('deepnote_convert_to', toolArgs) to restore TypeScript type
safety for those functions.

In `@packages/mcp/src/tools/reading.test.ts`:
- Around line 71-80: Add a new test that exercises the notebook filter support
in deepnote_read: call handleReadingTool('deepnote_read', { path:
testNotebookPath, include: ['dag'], notebook: '<notebookName>' }) (use the
scaffolded notebook name, e.g., 'Main'), then call extractResult(response) and
assert result.dag is defined, is an array, and filters to the expected single
entry (expect(dag.length).toBe(1)) and optionally assert that dag[0].notebook
=== '<notebookName>'; place this alongside the existing 'includes dag when
requested' test to cover the notebook filter edge case.

In `@packages/mcp/src/tools/reading.ts`:
- Around line 244-261: Duplicate implementation of formatOutput exists; extract
it into a single shared utility and import it where needed. Create a new module
(e.g., tools/utils.ts) that exports function formatOutput(data: object, compact:
boolean): string with the compact-mode filtering logic (ensure the object-empty
check excludes arrays by using !Array.isArray(v)). Then remove the local
formatOutput implementations and import the shared formatOutput in both places
that currently define it (the files that previously had the duplicate, including
the one named execution.ts), updating any references to call the imported
function.

Comment on lines +38 to +51
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.

🧹 Nitpick | 🔵 Trivial

Shallow filter for nested structures.

formatOutput only filters top-level empty values. Nested empty arrays/objects pass through. Acceptable if intentional, but worth noting.

🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/execution.ts` around lines 38 - 51, formatOutput
currently only removes empty/null values at the top level, allowing nested empty
objects/arrays to remain; update formatOutput to perform a deep/recursive filter
when compact is true by walking objects and arrays and removing nulls, empty
arrays, and empty objects at any depth (suggest adding a helper like
deepFilterEmpty(value): any that returns undefined for pruned values and
recurses into arrays/objects), then JSON.stringify the result of that helper
when compact is true; ensure the helper preserves non-empty primitives and
avoids infinite recursion on circular refs (e.g., by skipping or throwing if
cycles detected).

Comment on lines +2813 to +2820
case 'lint':
// Lint is a reading tool, import it
result = await import('./reading').then(m => m.handleReadingTool('deepnote_lint', toolArgs))
break
case 'convert_to':
// Convert is a conversion tool, import it
result = await import('./conversion').then(m => m.handleConversionTool('deepnote_convert_to', toolArgs))
break
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:

# Check if the exports exist in reading.ts and conversion.ts
rg -n "export.*handleReadingTool" packages/mcp/src/tools/
rg -n "export.*handleConversionTool" packages/mcp/src/tools/

Repository: deepnote/deepnote

Length of output: 341


🏁 Script executed:

# Check the current imports at the top of magic.ts to see what's already imported
head -50 packages/mcp/src/tools/magic.ts | grep -E "^import|^from"

Repository: deepnote/deepnote

Length of output: 494


🏁 Script executed:

# Get more context around the dynamic imports to understand the pattern
sed -n '2800,2830p' packages/mcp/src/tools/magic.ts

Repository: deepnote/deepnote

Length of output: 1122


🏁 Script executed:

# Check if reading.ts or conversion.ts are already imported statically in magic.ts
rg -n "from.*['\"].*reading" packages/mcp/src/tools/magic.ts
rg -n "from.*['\"].*conversion" packages/mcp/src/tools/magic.ts

Repository: deepnote/deepnote

Length of output: 43


🏁 Script executed:

# Check what these modules export and any potential circular dependencies
head -30 packages/mcp/src/tools/reading.ts | grep -E "^import|^export"
head -30 packages/mcp/src/tools/conversion.ts | grep -E "^import|^export"

Repository: deepnote/deepnote

Length of output: 676


🏁 Script executed:

# Verify if reading.ts or conversion.ts import from magic.ts (potential circular deps)
rg -n "from.*magic" packages/mcp/src/tools/reading.ts
rg -n "from.*magic" packages/mcp/src/tools/conversion.ts

Repository: deepnote/deepnote

Length of output: 43


Add type safety to dynamic imports of reading and conversion tools.

The .then() pattern provides no TypeScript type checking. Since no circular dependencies exist, use static imports at the file top:

import { handleReadingTool } from './reading'
import { handleConversionTool } from './conversion'

Then call directly: result = await handleReadingTool('deepnote_lint', toolArgs).

🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/magic.ts` around lines 2813 - 2820, Replace the
dynamic import + .then pattern for the reading and conversion tools with static
top-level imports and direct calls: add top-level imports for handleReadingTool
and handleConversionTool, then in the switch cases replace the await
import(...).then(...) expressions with direct calls like await
handleReadingTool('deepnote_lint', toolArgs) and await
handleConversionTool('deepnote_convert_to', toolArgs) to restore TypeScript type
safety for those functions.

Comment on lines +2877 to +2886
const responseData = {
success: allSuccess,
preset: preset || undefined,
stepsCompleted: results.length,
totalSteps: steps.length,
results: compact ? results.map(r => ({ step: r.step, tool: r.tool, success: r.success, error: r.error })) : results,
}

return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: allSuccess,
stepsCompleted: results.length,
totalSteps: steps.length,
results,
},
null,
2
),
},
],
content: [{ type: 'text', text: formatOutput(responseData, compact || false) }],
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: error: r.error includes undefined values.

In compact mode, the mapped result objects include error: undefined for successful steps. This gets filtered by formatOutput, but explicit would be cleaner.

Cleaner mapping
-    results: compact ? results.map(r => ({ step: r.step, tool: r.tool, success: r.success, error: r.error })) : results,
+    results: compact ? results.map(r => ({ step: r.step, tool: r.tool, success: r.success, ...(r.error && { error: r.error }) })) : results,
🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/magic.ts` around lines 2877 - 2886, The compact
mapping in responseData currently always sets error: r.error which yields
explicit undefined entries; update the mapping used in the compact branch (the
results.map(...) expression) to only include the error property when r.error is
present (e.g., build each object with { step: r.step, tool: r.tool, success:
r.success, ...(r.error ? { error: r.error } : {}) }) so successful steps omit
the error key before passing to formatOutput.

Comment on lines +71 to +80
it('includes dag when requested', async () => {
const response = await handleReadingTool('deepnote_read', {
path: testNotebookPath,
include: ['dag'],
})

const result = extractResult(response)
expect(result.dag).toBeDefined()
expect(Array.isArray(result.dag)).toBe(true)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding notebook filter test.

deepnote_read supports a notebook param for DAG filtering. No test covers it.

💡 Example test
it('filters dag by notebook', async () => {
  const response = await handleReadingTool('deepnote_read', {
    path: testNotebookPath,
    include: ['dag'],
    notebook: 'Main', // or whatever the scaffolded notebook name is
  })

  const result = extractResult(response)
  expect(result.dag).toBeDefined()
  const dag = result.dag as Array<{ notebook: string }>
  expect(dag.length).toBe(1)
})

Based on learnings: "Test edge cases, error handling, and special characters in Vitest tests"

🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/reading.test.ts` around lines 71 - 80, Add a new test
that exercises the notebook filter support in deepnote_read: call
handleReadingTool('deepnote_read', { path: testNotebookPath, include: ['dag'],
notebook: '<notebookName>' }) (use the scaffolded notebook name, e.g., 'Main'),
then call extractResult(response) and assert result.dag is defined, is an array,
and filters to the expected single entry (expect(dag.length).toBe(1)) and
optionally assert that dag[0].notebook === '<notebookName>'; place this
alongside the existing 'includes dag when requested' test to cover the notebook
filter edge case.

Comment on lines +244 to +261
/**
* Format output based on compact mode
*/
function formatOutput(data: object, compact: boolean): string {
if (compact) {
// Filter out null, undefined, and empty arrays/objects
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' && 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.

🧹 Nitpick | 🔵 Trivial

Duplicate formatOutput implementation.

Same function exists in execution.ts (lines 38-51). Extract to shared module.

♻️ Suggested approach

Create packages/mcp/src/tools/utils.ts:

export 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)
}

Then import in both files.

🤖 Prompt for AI Agents
In `@packages/mcp/src/tools/reading.ts` around lines 244 - 261, Duplicate
implementation of formatOutput exists; extract it into a single shared utility
and import it where needed. Create a new module (e.g., tools/utils.ts) that
exports function formatOutput(data: object, compact: boolean): string with the
compact-mode filtering logic (ensure the object-empty check excludes arrays by
using !Array.isArray(v)). Then remove the local formatOutput implementations and
import the shared formatOutput in both places that currently define it (the
files that previously had the duplicate, including the one named execution.ts),
updating any references to call the imported function.

@codecov
Copy link

codecov bot commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 64.31925% with 76 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.83%. Comparing base (a4fbed3) to head (68ca655).

Files with missing lines Patch % Lines
packages/mcp/src/tools/execution.ts 3.70% 52 Missing ⚠️
packages/mcp/src/tools/magic.ts 75.71% 16 Missing and 1 partial ⚠️
packages/mcp/src/tools/reading.ts 93.18% 6 Missing ⚠️
packages/mcp/src/server.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff               @@
##           mcp-skills     #259      +/-   ##
==============================================
+ Coverage       72.81%   74.83%   +2.02%     
==============================================
  Files              96       96              
  Lines            6909     7110     +201     
  Branches         1977     1999      +22     
==============================================
+ Hits             5031     5321     +290     
+ Misses           1877     1788      -89     
  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.

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