Skip to main content
Glama

notebook_read

Extract and display all cells, including code, text, and outputs, from a Jupyter notebook file using its absolute path for comprehensive analysis and review.

Instructions

Reads a Jupyter notebook (.ipynb file) and returns all of the cells with their outputs. Jupyter notebooks are interactive documents that combine code, text, and visualizations, commonly used for data analysis and scientific computing. The notebook_path parameter must be an absolute path, not a relative path.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
notebook_pathYesThe absolute path to the Jupyter notebook file to read (must be absolute, not relative)

Implementation Reference

  • Main handler function that validates the notebook path, reads and parses the .ipynb file using helper methods, formats the cells with outputs, and returns the formatted content.
    @override async def call( self, ctx: MCPContext, **params: Unpack[NotebookReadToolParams], ) -> str: """Execute the tool with the given parameters. Args: ctx: MCP context **params: Tool parameters Returns: Tool result """ tool_ctx = self.create_tool_context(ctx) self.set_tool_context_info(tool_ctx) # Extract parameters notebook_path: NotebookPath = params["notebook_path"] # Validate path parameter path_validation = self.validate_path(notebook_path) if path_validation.is_error: await tool_ctx.error(path_validation.error_message) return f"Error: {path_validation.error_message}" await tool_ctx.info(f"Reading notebook: {notebook_path}") # Check if path is allowed if not self.is_path_allowed(notebook_path): await tool_ctx.error( f"Access denied - path outside allowed directories: {notebook_path}" ) return f"Error: Access denied - path outside allowed directories: {notebook_path}" try: file_path = Path(notebook_path) if not file_path.exists(): await tool_ctx.error(f"File does not exist: {notebook_path}") return f"Error: File does not exist: {notebook_path}" if not file_path.is_file(): await tool_ctx.error(f"Path is not a file: {notebook_path}") return f"Error: Path is not a file: {notebook_path}" # Check file extension if file_path.suffix.lower() != ".ipynb": await tool_ctx.error(f"File is not a Jupyter notebook: {notebook_path}") return f"Error: File is not a Jupyter notebook: {notebook_path}" # Read and parse the notebook try: # This will read the file, so we don't need to read it separately _, processed_cells = await self.parse_notebook(file_path) # Format the notebook content as a readable string result = self.format_notebook_cells(processed_cells) await tool_ctx.info( f"Successfully read notebook: {notebook_path} ({len(processed_cells)} cells)" ) return result except json.JSONDecodeError: await tool_ctx.error(f"Invalid notebook format: {notebook_path}") return f"Error: Invalid notebook format: {notebook_path}" except UnicodeDecodeError: await tool_ctx.error(f"Cannot read notebook file: {notebook_path}") return f"Error: Cannot read notebook file: {notebook_path}" except Exception as e: await tool_ctx.error(f"Error reading notebook: {str(e)}") return f"Error reading notebook: {str(e)}"
  • Input schema defining the `notebook_path` parameter as an absolute path to a .ipynb file.
    NotebookPath = Annotated[ str, Field( description="The absolute path to the Jupyter notebook file to read (must be absolute, not relative)", ), ] class NotebookReadToolParams(TypedDict): """Parameters for the NotebookReadTool. Attributes: notebook_path: The absolute path to the Jupyter notebook file to read (must be absolute, not relative) """ notebook_path: NotebookPath
  • Registers Jupyter tools including NotebookReadTool by creating instances and calling ToolRegistry.register_tools.
    def register_jupyter_tools( mcp_server: FastMCP, permission_manager: PermissionManager, ) -> list[BaseTool]: """Register all Jupyter notebook tools with the MCP server. Args: mcp_server: The FastMCP server instance permission_manager: Permission manager for access control Returns: List of registered tools """ tools = get_jupyter_tools(permission_manager) ToolRegistry.register_tools(mcp_server, tools) return tools
  • Top-level call to register Jupyter tools (including notebook_read) during tools setup.
    jupyter_tools = register_jupyter_tools(mcp_server, permission_manager)
  • Helper method called by the handler to parse the notebook JSON into structured cells with sources, execution counts, and outputs (text, images, errors). Note: excerpt abbreviated.
    async def parse_notebook( self, file_path: Path ) -> tuple[dict[str, Any], list[NotebookCellSource]]: """Parse a Jupyter notebook file. Args: file_path: Path to the notebook file Returns: Tuple of (notebook_data, processed_cells) """ with open(file_path, "r", encoding="utf-8") as f: content = f.read() notebook = json.loads(content) # Get notebook language language = ( notebook.get("metadata", {}).get("language_info", {}).get("name", "python") ) cells = notebook.get("cells", []) processed_cells = [] for i, cell in enumerate(cells): cell_type = cell.get("cell_type", "code") # Skip if not code or markdown if cell_type not in ["code", "markdown"]: continue # Get source source = cell.get("source", "") if isinstance(source, list): source = "".join(source) # Get execution count for code cells execution_count = None if cell_type == "code": execution_count = cell.get("execution_count") # Process outputs for code cells outputs = [] if cell_type == "code" and "outputs" in cell: for output in cell["outputs"]: output_type = output.get("output_type", "") # Process different output types if output_type == "stream": text = output.get("text", "") if isinstance(text, list): text = "".join(text) outputs.append( NotebookCellOutput(output_type="stream", text=text) ) elif output_type in ["execute_result", "display_data"]: # Process text output text = None if "data" in output and "text/plain" in output["data"]: text_data = output["data"]["text/plain"] if isinstance(text_data, list): text = "".join(text_data) else: text = text_data # Process image output image = None if "data" in output: if "image/png" in output["data"]: image = NotebookOutputImage( image_data=output["data"]["image/png"], media_type="image/png", ) elif "image/jpeg" in output["data"]: image = NotebookOutputImage( image_data=output["data"]["image/jpeg"], media_type="image/jpeg", ) outputs.append( NotebookCellOutput( output_type=output_type, text=text, image=image ) ) elif output_type == "error": # Format error traceback ename = output.get("ename", "") evalue = output.get("evalue", "") traceback = output.get("traceback", []) # Handle raw text strings and lists of strings if isinstance(traceback, list): # Clean ANSI escape codes and join the list but preserve the formatting clean_traceback = [ clean_ansi_escapes(line) for line in traceback ] traceback_text = "\n".join(clean_traceback) else: traceback_text = clean_ansi_escapes(str(traceback)) error_text = f"{ename}: {evalue}\n{traceback_text}" outputs.append( NotebookCellOutput(output_type="error", text=error_text) ) # Create cell object processed_cell = NotebookCellSource( cell_index=i, cell_type=cell_type, source=source, language=language, execution_count=execution_count, outputs=outputs, ) processed_cells.append(processed_cell) return notebook, processed_cells

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SDGLBL/mcp-claude-code'

If you have feedback or need assistance with the MCP directory API, please join our Discord server