Skip to main content
Glama
jordanburke

joplin-mcp-server

read_notebook

Extract and retrieve contents of a specific notebook by providing its unique ID, enabling efficient data access and management in Joplin MCP Server.

Instructions

Read the contents of a specific notebook

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
notebook_idYesID of the notebook to read

Implementation Reference

  • The ReadNotebook class extending BaseTool implements the core execution logic for the read_notebook tool. The `call` method fetches notebook details, retrieves its notes, sorts them by update time, and formats a response listing all notes with instructions.
    class ReadNotebook extends BaseTool {
      async call(notebookId: string): Promise<string> {
        const validationError = this.validateId(notebookId, "notebook")
        if (validationError) {
          return validationError
        }
    
        try {
          // First, get the notebook details
          const notebook = await this.apiClient.get<JoplinFolder>(`/folders/${notebookId}`, {
            query: { fields: "id,title,parent_id" },
          })
    
          // Validate notebook response
          if (!notebook || typeof notebook !== "object" || !notebook.id) {
            return `Error: Unexpected response format from Joplin API when fetching notebook`
          }
    
          // Get all notes in this notebook
          const notes = await this.apiClient.get<NotebookNotesResponse>(`/folders/${notebookId}/notes`, {
            query: { fields: "id,title,updated_time,is_todo,todo_completed" },
          })
    
          // Validate notes response
          if (!notes || typeof notes !== "object") {
            return `Error: Unexpected response format from Joplin API when fetching notes`
          }
    
          if (!notes.items || !Array.isArray(notes.items) || notes.items.length === 0) {
            return `Notebook "${notebook.title}" (notebook_id: "${notebook.id}") is empty.\n\nTry another notebook ID or use list_notebooks to see all available notebooks.`
          }
    
          // Format the notebook contents
          const resultLines: string[] = []
          resultLines.push(`# Notebook: "${notebook.title}" (notebook_id: "${notebook.id}")`)
          resultLines.push(`Contains ${notes.items.length} notes:\n`)
          resultLines.push(`NOTE: This is showing the contents of notebook "${notebook.title}", not a specific note.\n`)
    
          // If multiple notes were found, add a hint about read_multinote
          if (notes.items.length > 1) {
            const noteIds = notes.items.map((note) => note.id)
            resultLines.push(`TIP: To read all ${notes.items.length} notes at once, use:\n`)
            resultLines.push(`read_multinote note_ids=${JSON.stringify(noteIds)}\n`)
          }
    
          // Sort notes by updated_time (newest first)
          const sortedNotes = [...notes.items].sort((a, b) => b.updated_time - a.updated_time)
    
          sortedNotes.forEach((note) => {
            const updatedDate = this.formatDate(note.updated_time)
    
            // Add checkbox for todos
            if (note.is_todo) {
              const checkboxStatus = note.todo_completed ? "✅" : "☐"
              resultLines.push(`- ${checkboxStatus} Note: "${note.title}" (note_id: "${note.id}")`)
            } else {
              resultLines.push(`- Note: "${note.title}" (note_id: "${note.id}")`)
            }
    
            resultLines.push(`  Updated: ${updatedDate}`)
            resultLines.push(`  To read this note: read_note note_id="${note.id}"`)
            resultLines.push("") // Empty line between notes
          })
    
          return resultLines.join("\n")
        } catch (error: any) {
          if (error.response && error.response.status === 404) {
            return `Notebook with ID "${notebookId}" not found.\n\nThis might happen if:\n1. The ID is incorrect\n2. You're using a note title instead of a notebook ID\n3. The notebook has been deleted\n\nUse list_notebooks to see all available notebooks with their IDs.`
          }
          return (
            this.formatError(error, "reading notebook") +
            `\n\nMake sure you're using a valid notebook ID, not a note title.\nUse list_notebooks to see all available notebooks with their IDs.`
          )
        }
      }
    }
    
    export default ReadNotebook
  • Input schema definition for the read_notebook tool in the stdio MCP server list tools response.
    {
      name: "read_notebook",
      description: "Read the contents of a specific notebook",
      inputSchema: {
        type: "object",
        properties: {
          notebook_id: { type: "string", description: "ID of the notebook to read" },
        },
        required: ["notebook_id"],
      },
  • src/index.ts:235-238 (registration)
    Tool dispatch handler in stdio MCP server that calls manager.readNotebook for read_notebook.
    case "read_notebook": {
      const notebookResult = await manager.readNotebook(args.notebook_id as string)
      return { content: [{ type: "text", text: notebookResult }], isError: false }
    }
  • Registration of read_notebook tool in FastMCP server including schema (Zod) and execute handler calling manager.readNotebook.
    server.addTool({
      name: "read_notebook",
      description: "Read the contents of a specific notebook",
      parameters: z.object({
        notebook_id: z.string().describe("ID of the notebook to read"),
      }),
      execute: async (args) => {
        return await manager.readNotebook(args.notebook_id)
      },
    })
  • Instantiation of ReadNotebook tool instance in JoplinServerManager for use by manager.readNotebook method.
    this.tools = {
      listNotebooks: new ListNotebooks(this.apiClient),
      searchNotes: new SearchNotes(this.apiClient),
      readNotebook: new ReadNotebook(this.apiClient),
      readNote: new ReadNote(this.apiClient),
      readMultiNote: new ReadMultiNote(this.apiClient),
      createNote: new CreateNote(this.apiClient),
      createFolder: new CreateFolder(this.apiClient),
      editNote: new EditNote(this.apiClient),
      editFolder: new EditFolder(this.apiClient),
      deleteNote: new DeleteNote(this.apiClient),
      deleteFolder: new DeleteFolder(this.apiClient),
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It only states the basic action without mentioning permissions needed, whether this is a read-only operation, potential rate limits, or what format/content is returned. For a tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence that efficiently communicates the core function without any wasted words. It's appropriately sized for a simple tool and gets straight to the point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description is too minimal. It doesn't explain what 'contents' means, what format is returned, or any behavioral aspects. Given the lack of structured information elsewhere, the description should provide more context about the operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents the single 'notebook_id' parameter. The description adds no additional parameter information beyond what's in the schema, which is acceptable given the high schema coverage but doesn't provide extra value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Read' and the resource 'contents of a specific notebook', making the purpose immediately understandable. However, it doesn't differentiate this tool from sibling tools like 'read_note' or 'read_multinote', leaving some ambiguity about scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided about when to use this tool versus alternatives like 'read_note' or 'read_multinote'. The description simply states what it does without any context about appropriate use cases or distinctions from similar tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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/jordanburke/joplin-mcp-server'

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