Skip to main content
Glama
notebook-resources-spec.md10.6 kB
# Notebook Resources Specification for MCP ## Overview This specification defines how ephemeral notebooks in ClearThought are exposed as MCP (Model Context Protocol) resources. Notebooks can be discovered, listed, and read through standard MCP resource operations, making them accessible to any MCP-compatible client. ## URI Scheme We use a custom URI scheme for notebook resources: ``` notebook://[session-id]/[notebook-id]/[format] ``` ### URI Components - **scheme**: `notebook://` - Custom scheme for notebook resources - **session-id**: The session identifier that owns the notebook - **notebook-id**: Unique identifier for the specific notebook - **format**: Optional format specifier (`srcmd`, `json`, `cells`, or omitted for default) ### URI Examples ``` notebook://session-123/nb-456 # Default notebook view (srcmd) notebook://session-123/nb-456/srcmd # Srcbook markdown format notebook://session-123/nb-456/json # JSON representation notebook://session-123/nb-456/cells # Individual cells as sub-resources notebook://current # Current session's notebook ``` ## Resource Types ### 1. Notebook List Resource **URI**: `notebook://list` **Response**: ```json { "uri": "notebook://list", "name": "Active Notebooks", "description": "List of all active notebooks in the system", "mimeType": "application/json", "contents": [ { "type": "text", "text": "[{\"id\": \"nb-123\", \"sessionId\": \"session-456\", \"cellCount\": 5, \"pattern\": \"tree_of_thought\"}]" } ] } ``` ### 2. Individual Notebook Resource **URI**: `notebook://[session-id]/[notebook-id]` **Response**: ```json { "uri": "notebook://session-123/nb-456", "name": "Tree of Thought Notebook", "description": "Ephemeral notebook for tree_of_thought reasoning pattern", "mimeType": "text/markdown", "metadata": { "sessionId": "session-123", "notebookId": "nb-456", "pattern": "tree_of_thought", "cellCount": 8, "createdAt": "2024-01-15T10:30:00Z", "lastModified": "2024-01-15T10:45:00Z" }, "contents": [ { "type": "text", "text": "# Tree of Thought Reasoning\n\n## Problem Statement\n..." } ] } ``` ### 3. Notebook Cells Resource **URI**: `notebook://[session-id]/[notebook-id]/cells` **Response**: ```json { "uri": "notebook://session-123/nb-456/cells", "name": "Notebook Cells", "description": "Individual cells from the notebook", "mimeType": "application/json", "contents": [ { "type": "text", "text": "[{\"id\": \"cell-1\", \"type\": \"markdown\", \"source\": \"# Title\"}, ...]" } ] } ``` ### 4. Single Cell Resource **URI**: `notebook://[session-id]/[notebook-id]/cells/[cell-id]` **Response**: ```json { "uri": "notebook://session-123/nb-456/cells/cell-789", "name": "Cell: TreeNode Implementation", "description": "Code cell containing TreeNode class", "mimeType": "application/javascript", "metadata": { "cellId": "cell-789", "cellType": "code", "language": "javascript", "filename": "tree-node.js", "status": "idle", "hasOutputs": true }, "contents": [ { "type": "text", "text": "class TreeNode {\n constructor(state, thought, value = null) {\n ...\n }\n}" } ] } ``` ## Implementation ### Resource Registration ```typescript // In the MCP server setup server.setRequestHandler(ListResourcesRequestSchema, async () => { const notebooks = notebookStore.getAllNotebooks(); const resources = []; // Add list resource resources.push({ uri: 'notebook://list', name: 'Active Notebooks', description: `${notebooks.length} active notebooks`, mimeType: 'application/json' }); // Add individual notebook resources for (const notebook of notebooks) { const pattern = notebook.metadata?.pattern || 'generic'; resources.push({ uri: `notebook://${notebook.sessionId}/${notebook.id}`, name: `${pattern} Notebook`, description: `Notebook for session ${notebook.sessionId}`, mimeType: 'text/markdown' }); // Add cells collection resource resources.push({ uri: `notebook://${notebook.sessionId}/${notebook.id}/cells`, name: `${pattern} Notebook Cells`, description: `${notebook.cells.length} cells`, mimeType: 'application/json' }); } return { resources }; }); ``` ### Resource Reading ```typescript server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (!uri.startsWith('notebook://')) { throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`); } const parts = uri.slice('notebook://'.length).split('/'); // Handle notebook://list if (parts[0] === 'list') { const notebooks = notebookStore.getAllNotebooks(); const summary = notebooks.map(nb => ({ id: nb.id, sessionId: nb.sessionId, cellCount: nb.cells.length, pattern: nb.metadata?.pattern, createdAt: new Date(nb.createdAt).toISOString() })); return { contents: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }; } // Handle notebook://current (current session's notebook) if (parts[0] === 'current') { const notebook = notebookStore.getNotebookBySession(sessionState.sessionId); if (!notebook) { throw new McpError(ErrorCode.InvalidParams, 'No notebook for current session'); } const srcmd = notebookStore.exportToSrcMd(notebook.id); return { contents: [{ type: 'text', text: srcmd || 'Empty notebook' }] }; } // Handle notebook://[session-id]/[notebook-id]/[format] const [sessionId, notebookId, ...subpath] = parts; const notebook = notebookStore.getNotebook(notebookId); if (!notebook || notebook.sessionId !== sessionId) { throw new McpError(ErrorCode.InvalidParams, 'Notebook not found'); } // Handle different formats/subpaths if (subpath.length === 0 || subpath[0] === 'srcmd') { // Return srcbook markdown const srcmd = notebookStore.exportToSrcMd(notebookId); return { contents: [{ type: 'text', text: srcmd || 'Empty notebook' }] }; } if (subpath[0] === 'json') { // Return JSON representation const json = notebookStore.exportToJson(notebookId); return { contents: [{ type: 'text', text: JSON.stringify(json, null, 2) }] }; } if (subpath[0] === 'cells') { if (subpath[1]) { // Return specific cell const cell = notebook.cells.find(c => c.id === subpath[1]); if (!cell) { throw new McpError(ErrorCode.InvalidParams, 'Cell not found'); } return { contents: [{ type: 'text', text: cell.source }] }; } // Return all cells return { contents: [{ type: 'text', text: JSON.stringify(notebook.cells, null, 2) }] }; } throw new McpError(ErrorCode.InvalidParams, `Unknown format: ${subpath[0]}`); }); ``` ## Resource Discovery Clients can discover available notebooks through: 1. **List all resources** - Standard MCP `resources/list` request 2. **Filter by URI pattern** - Look for `notebook://` scheme 3. **Read notebook list** - Access `notebook://list` for summary ## Use Cases ### 1. Notebook Inspection ```typescript // Client code const resources = await mcp.listResources(); const notebooks = resources.filter(r => r.uri.startsWith('notebook://')); for (const notebook of notebooks) { const content = await mcp.readResource(notebook.uri); console.log(`Notebook ${notebook.name}:`, content); } ``` ### 2. Export Current Session's Notebook ```typescript const currentNotebook = await mcp.readResource('notebook://current'); fs.writeFileSync('session-notebook.src.md', currentNotebook.contents[0].text); ``` ### 3. Access Specific Cell ```typescript const cellContent = await mcp.readResource( 'notebook://session-123/nb-456/cells/cell-789' ); console.log('Cell source:', cellContent.contents[0].text); ``` ### 4. Monitor Notebook Changes Clients can periodically check the notebook list or specific notebook resources to detect changes: ```typescript // Poll for changes setInterval(async () => { const list = await mcp.readResource('notebook://list'); const notebooks = JSON.parse(list.contents[0].text); console.log(`Active notebooks: ${notebooks.length}`); }, 5000); ``` ## Security Considerations 1. **Session Isolation**: Notebooks are tied to sessions; URIs include session IDs to prevent cross-session access 2. **Read-Only Access**: Resources are read-only through the MCP protocol 3. **No Execution**: Reading notebook resources doesn't trigger cell execution 4. **Ephemeral Nature**: Resources disappear when notebooks are cleaned up after session timeout 5. **Content Sanitization**: Cell outputs are sanitized before being exposed as resources ## Future Enhancements ### 1. Resource Subscriptions Support MCP resource change notifications when notebooks are modified: ```typescript { "method": "notifications/resources/list_changed" } ``` ### 2. Virtual Filesystem URIs Use standard `file://` scheme for exported notebooks: ``` file:///notebooks/session-123/notebook.src.md file:///notebooks/session-123/cells/cell-1.js ``` ### 3. Resource Templates Provide template resources for creating new notebooks: ``` notebook://templates/tree_of_thought notebook://templates/beam_search notebook://templates/mcts ``` ### 4. Execution Results as Resources Expose execution results as separate resources: ``` notebook://session-123/nb-456/executions/exec-789 ``` ## Metadata Schema Resources can include standardized metadata: ```typescript interface NotebookResourceMetadata { sessionId: string; notebookId: string; pattern?: string; cellCount: number; createdAt: string; lastModified: string; lastExecuted?: string; totalExecutions?: number; hasErrors?: boolean; } ``` ## Error Handling Standard MCP error codes apply: - `InvalidParams` - Invalid URI format or notebook not found - `InternalError` - Server error accessing notebook store - `MethodNotFound` - Unsupported resource operation ## Conclusion By exposing notebooks as MCP resources, we enable: - Discovery of active notebooks - Inspection of notebook contents - Export to various formats - Integration with MCP-compatible tools - Monitoring and debugging capabilities This approach maintains the ephemeral, session-based nature of notebooks while providing standard MCP access patterns.

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/waldzellai/clearthought-onepointfive'

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