basic-memory 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

Files changed (75) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/alembic/migrations.py +4 -9
  3. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +106 -0
  4. basic_memory/api/app.py +9 -6
  5. basic_memory/api/routers/__init__.py +2 -1
  6. basic_memory/api/routers/knowledge_router.py +30 -4
  7. basic_memory/api/routers/memory_router.py +3 -2
  8. basic_memory/api/routers/project_info_router.py +275 -0
  9. basic_memory/api/routers/search_router.py +22 -4
  10. basic_memory/cli/app.py +54 -3
  11. basic_memory/cli/commands/__init__.py +15 -2
  12. basic_memory/cli/commands/db.py +9 -13
  13. basic_memory/cli/commands/import_chatgpt.py +26 -30
  14. basic_memory/cli/commands/import_claude_conversations.py +27 -29
  15. basic_memory/cli/commands/import_claude_projects.py +29 -31
  16. basic_memory/cli/commands/import_memory_json.py +26 -28
  17. basic_memory/cli/commands/mcp.py +7 -1
  18. basic_memory/cli/commands/project.py +119 -0
  19. basic_memory/cli/commands/project_info.py +167 -0
  20. basic_memory/cli/commands/status.py +7 -9
  21. basic_memory/cli/commands/sync.py +54 -9
  22. basic_memory/cli/commands/{tools.py → tool.py} +92 -19
  23. basic_memory/cli/main.py +40 -1
  24. basic_memory/config.py +155 -7
  25. basic_memory/db.py +19 -4
  26. basic_memory/deps.py +10 -3
  27. basic_memory/file_utils.py +32 -16
  28. basic_memory/markdown/utils.py +5 -0
  29. basic_memory/mcp/main.py +1 -2
  30. basic_memory/mcp/prompts/__init__.py +6 -2
  31. basic_memory/mcp/prompts/ai_assistant_guide.py +6 -8
  32. basic_memory/mcp/prompts/continue_conversation.py +65 -126
  33. basic_memory/mcp/prompts/recent_activity.py +55 -13
  34. basic_memory/mcp/prompts/search.py +72 -17
  35. basic_memory/mcp/prompts/utils.py +139 -82
  36. basic_memory/mcp/server.py +1 -1
  37. basic_memory/mcp/tools/__init__.py +11 -22
  38. basic_memory/mcp/tools/build_context.py +85 -0
  39. basic_memory/mcp/tools/canvas.py +17 -19
  40. basic_memory/mcp/tools/delete_note.py +28 -0
  41. basic_memory/mcp/tools/project_info.py +51 -0
  42. basic_memory/mcp/tools/{resource.py → read_content.py} +42 -5
  43. basic_memory/mcp/tools/read_note.py +190 -0
  44. basic_memory/mcp/tools/recent_activity.py +100 -0
  45. basic_memory/mcp/tools/search.py +56 -17
  46. basic_memory/mcp/tools/utils.py +245 -17
  47. basic_memory/mcp/tools/write_note.py +124 -0
  48. basic_memory/models/search.py +2 -1
  49. basic_memory/repository/entity_repository.py +3 -2
  50. basic_memory/repository/project_info_repository.py +9 -0
  51. basic_memory/repository/repository.py +23 -6
  52. basic_memory/repository/search_repository.py +33 -10
  53. basic_memory/schemas/__init__.py +12 -0
  54. basic_memory/schemas/memory.py +3 -2
  55. basic_memory/schemas/project_info.py +96 -0
  56. basic_memory/schemas/search.py +27 -32
  57. basic_memory/services/context_service.py +3 -3
  58. basic_memory/services/entity_service.py +8 -2
  59. basic_memory/services/file_service.py +105 -53
  60. basic_memory/services/link_resolver.py +5 -45
  61. basic_memory/services/search_service.py +45 -16
  62. basic_memory/sync/sync_service.py +274 -39
  63. basic_memory/sync/watch_service.py +160 -30
  64. basic_memory/utils.py +40 -40
  65. basic_memory-0.9.0.dist-info/METADATA +736 -0
  66. basic_memory-0.9.0.dist-info/RECORD +99 -0
  67. basic_memory/mcp/prompts/json_canvas_spec.py +0 -25
  68. basic_memory/mcp/tools/knowledge.py +0 -68
  69. basic_memory/mcp/tools/memory.py +0 -177
  70. basic_memory/mcp/tools/notes.py +0 -201
  71. basic_memory-0.8.0.dist-info/METADATA +0 -379
  72. basic_memory-0.8.0.dist-info/RECORD +0 -91
  73. {basic_memory-0.8.0.dist-info → basic_memory-0.9.0.dist-info}/WHEEL +0 -0
  74. {basic_memory-0.8.0.dist-info → basic_memory-0.9.0.dist-info}/entry_points.txt +0 -0
  75. {basic_memory-0.8.0.dist-info → basic_memory-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,95 +4,152 @@ These utilities help format data from various tools into consistent,
4
4
  user-friendly markdown summaries.
5
5
  """
6
6
 
7
- from basic_memory.schemas.memory import GraphContext
7
+ from dataclasses import dataclass
8
+ from textwrap import dedent
9
+ from typing import List
8
10
 
11
+ from basic_memory.schemas.base import TimeFrame
12
+ from basic_memory.schemas.memory import (
13
+ normalize_memory_url,
14
+ EntitySummary,
15
+ RelationSummary,
16
+ ObservationSummary,
17
+ )
9
18
 
10
- def format_context_summary(header: str, context: GraphContext) -> str:
11
- """Format GraphContext as a helpful markdown summary.
12
19
 
13
- This creates a user-friendly markdown response that explains the context
14
- and provides guidance on how to explore further.
20
+ @dataclass
21
+ class PromptContextItem:
22
+ primary_results: List[EntitySummary]
23
+ related_results: List[EntitySummary | RelationSummary | ObservationSummary]
15
24
 
16
- Args:
17
- header: The title to use for the summary
18
- context: The GraphContext object to format
19
25
 
26
+ @dataclass
27
+ class PromptContext:
28
+ timeframe: TimeFrame
29
+ topic: str
30
+ results: List[PromptContextItem]
31
+
32
+
33
+ def format_prompt_context(context: PromptContext) -> str:
34
+ """Format continuation context into a helpful summary.
20
35
  Returns:
21
- Formatted markdown string with the context summary
36
+ Formatted continuation summary
22
37
  """
23
- summary = []
24
-
25
- # Extract URI for reference
26
- uri = context.metadata.uri or "a/permalink-value"
27
-
28
- # Add header
29
- summary.append(f"{header}")
30
- summary.append("")
31
-
32
- # Primary document section
33
- if context.primary_results:
34
- summary.append(f"## Primary Documents ({len(context.primary_results)})")
35
-
36
- for primary in context.primary_results:
37
- summary.append(f"### {primary.title}")
38
- summary.append(f"- **Type**: {primary.type}")
39
- summary.append(f"- **Path**: {primary.file_path}")
40
- summary.append(f"- **Created**: {primary.created_at.strftime('%Y-%m-%d %H:%M')}")
41
- summary.append("")
42
- summary.append(
43
- f'To view this document\'s content: `read_note("{primary.permalink}")` or `read_note("{primary.title}")` '
38
+ if not context.results:
39
+ return dedent(f"""
40
+ # Continuing conversation on: {context.topic}
41
+
42
+ This is a memory retrieval session.
43
+ The supplied query did not return any information specifically on this topic.
44
+
45
+ ## Opportunity to Capture New Knowledge!
46
+
47
+ This is an excellent chance to start documenting this topic:
48
+
49
+ ```python
50
+ await write_note(
51
+ title="{context.topic}",
52
+ content=f'''
53
+ # {context.topic}
54
+
55
+ ## Overview
56
+ [Summary of what we know about {context.topic}]
57
+
58
+ ## Key Points
59
+ [Main aspects or components of {context.topic}]
60
+
61
+ ## Observations
62
+ - [category] [First important observation about {context.topic}]
63
+ - [category] [Second observation about {context.topic}]
64
+
65
+ ## Relations
66
+ - relates_to [[Related Topic]]
67
+ - part_of [[Broader Context]]
68
+ '''
44
69
  )
45
- summary.append("")
46
- else:
47
- summary.append("\nNo primary documents found.")
48
-
49
- # Related documents section
50
- if context.related_results:
51
- summary.append(f"## Related Documents ({len(context.related_results)})")
52
-
53
- # Group by relation type for better organization
54
- relation_types = {}
55
- for rel in context.related_results:
56
- if hasattr(rel, "relation_type"):
57
- rel_type = rel.relation_type # pyright: ignore
58
- if rel_type not in relation_types:
59
- relation_types[rel_type] = []
60
- relation_types[rel_type].append(rel)
61
-
62
- # Display relations grouped by type
63
- for rel_type, relations in relation_types.items():
64
- summary.append(f"### {rel_type.replace('_', ' ').title()} ({len(relations)})")
65
-
66
- for rel in relations:
67
- if hasattr(rel, "to_id") and rel.to_id:
68
- summary.append(f"- **{rel.to_id}**")
69
- summary.append(f' - View document: `read_note("{rel.to_id}")` ')
70
- summary.append(
71
- f' - Explore connections: `build_context("memory://{rel.to_id}")` '
70
+ ```
71
+
72
+ ## Other Options
73
+
74
+ Please use the available basic-memory tools to gather relevant context before responding.
75
+ You can also:
76
+ - Try a different search term
77
+ - Check recent activity with `recent_activity(timeframe="1w")`
78
+ """)
79
+
80
+ # Start building our summary with header - add knowledge capture emphasis
81
+ summary = dedent(f"""
82
+ # Continuing conversation on: {context.topic}
83
+
84
+ This is a memory retrieval session.
85
+
86
+ Please use the available basic-memory tools to gather relevant context before responding.
87
+ Start by executing one of the suggested commands below to retrieve content.
88
+
89
+ Here's what I found from previous conversations:
90
+
91
+ > **Knowledge Capture Recommendation:** As you continue this conversation, actively look for opportunities to record new information, decisions, or insights that emerge. Use `write_note()` to document important context.
92
+ """)
93
+
94
+ # Track what we've added to avoid duplicates
95
+ added_permalinks = set()
96
+ sections = []
97
+
98
+ # Process each context
99
+ for context in context.results: # pyright: ignore
100
+ for primary in context.primary_results: # pyright: ignore
101
+ if primary.permalink not in added_permalinks:
102
+ primary_permalink = primary.permalink
103
+
104
+ added_permalinks.add(primary_permalink)
105
+
106
+ memory_url = normalize_memory_url(primary_permalink)
107
+ section = dedent(f"""
108
+ --- {memory_url}
109
+
110
+ ## {primary.title}
111
+ - **Type**: {primary.type}
112
+ """)
113
+
114
+ # Add creation date
115
+ section += f"- **Created**: {primary.created_at.strftime('%Y-%m-%d %H:%M')}\n"
116
+
117
+ # Add content snippet
118
+ if hasattr(primary, "content") and primary.content: # pyright: ignore
119
+ content = primary.content or "" # pyright: ignore
120
+ if content:
121
+ section += f"\n**Excerpt**:\n{content}\n"
122
+
123
+ section += dedent(f"""
124
+
125
+ You can read this document with: `read_note("{primary_permalink}")`
126
+ """)
127
+ sections.append(section)
128
+
129
+ if context.related_results: # pyright: ignore
130
+ section += dedent( # pyright: ignore
131
+ """
132
+ ## Related Context
133
+ """
134
+ )
135
+
136
+ for related in context.related_results: # pyright: ignore
137
+ section_content = dedent(f"""
138
+ - type: **{related.type}**
139
+ - title: {related.title}
140
+ """)
141
+ if related.permalink:
142
+ section_content += (
143
+ f'You can view this document with: `read_note("{related.permalink}")`'
72
144
  )
73
145
  else:
74
- summary.append(f"- **Unresolved relation**: {rel.permalink}")
75
- summary.append("")
76
-
77
- # Next steps section
78
- summary.append("## Next Steps")
79
- summary.append("Here are some ways to explore further:")
80
-
81
- search_term = uri.split("/")[-1]
82
- summary.append(f'- **Search related topics**: `search({{"text": "{search_term}"}})`')
83
-
84
- summary.append('- **Check recent changes**: `recent_activity(timeframe="3 days")`')
85
- summary.append(f'- **Explore all relations**: `build_context("memory://{uri}/*")`')
86
-
87
- # Tips section
88
- summary.append("")
89
- summary.append("## Tips")
90
- summary.append(
91
- f'- For more specific context, increase depth: `build_context("memory://{uri}", depth=2)`'
92
- )
93
- summary.append(
94
- "- You can follow specific relation types using patterns like: `memory://document/relation-type/*`"
95
- )
96
- summary.append("- Look for connected documents by checking relations between them")
97
-
98
- return "\n".join(summary)
146
+ section_content += (
147
+ f'You can view this file with: `read_file("{related.file_path}")`'
148
+ )
149
+
150
+ section += section_content
151
+ sections.append(section)
152
+
153
+ # Add all sections
154
+ summary += "\n".join(sections)
155
+ return summary
@@ -8,4 +8,4 @@ configure_logging(level="INFO")
8
8
 
9
9
 
10
10
  # Create the shared server instance
11
- mcp = FastMCP("Basic Memory")
11
+ mcp = FastMCP("Basic Memory")
@@ -6,33 +6,22 @@ all tools with the MCP server.
6
6
  """
7
7
 
8
8
  # Import tools to register them with MCP
9
- from basic_memory.mcp.tools.resource import read_resource
10
- from basic_memory.mcp.tools.memory import build_context, recent_activity
11
- from basic_memory.mcp.tools.notes import read_note, write_note
9
+ from basic_memory.mcp.tools.delete_note import delete_note
10
+ from basic_memory.mcp.tools.read_content import read_content
11
+ from basic_memory.mcp.tools.build_context import build_context
12
+ from basic_memory.mcp.tools.recent_activity import recent_activity
13
+ from basic_memory.mcp.tools.read_note import read_note
14
+ from basic_memory.mcp.tools.write_note import write_note
12
15
  from basic_memory.mcp.tools.search import search
13
16
  from basic_memory.mcp.tools.canvas import canvas
14
17
 
15
- from basic_memory.mcp.tools.knowledge import (
16
- delete_entities,
17
- get_entity,
18
- get_entities,
19
- )
20
-
21
18
  __all__ = [
22
- # Knowledge graph tools
23
- "delete_entities",
24
- "get_entity",
25
- "get_entities",
26
- # Search tools
27
- "search",
28
- # memory tools
29
19
  "build_context",
30
- "recent_activity",
31
- # notes
20
+ "canvas",
21
+ "delete_note",
22
+ "read_content",
32
23
  "read_note",
24
+ "recent_activity",
25
+ "search",
33
26
  "write_note",
34
- # files
35
- "read_resource",
36
- # canvas
37
- "canvas",
38
27
  ]
@@ -0,0 +1,85 @@
1
+ """Build context tool for Basic Memory MCP server."""
2
+
3
+ from typing import Optional
4
+
5
+ from loguru import logger
6
+
7
+ from basic_memory.mcp.async_client import client
8
+ from basic_memory.mcp.server import mcp
9
+ from basic_memory.mcp.tools.utils import call_get
10
+ from basic_memory.schemas.base import TimeFrame
11
+ from basic_memory.schemas.memory import (
12
+ GraphContext,
13
+ MemoryUrl,
14
+ memory_url_path,
15
+ normalize_memory_url,
16
+ )
17
+
18
+
19
+ @mcp.tool(
20
+ description="""Build context from a memory:// URI to continue conversations naturally.
21
+
22
+ Use this to follow up on previous discussions or explore related topics.
23
+ Timeframes support natural language like:
24
+ - "2 days ago"
25
+ - "last week"
26
+ - "today"
27
+ - "3 months ago"
28
+ Or standard formats like "7d", "24h"
29
+ """,
30
+ )
31
+ async def build_context(
32
+ url: MemoryUrl,
33
+ depth: Optional[int] = 1,
34
+ timeframe: Optional[TimeFrame] = "7d",
35
+ page: int = 1,
36
+ page_size: int = 10,
37
+ max_related: int = 10,
38
+ ) -> GraphContext:
39
+ """Get context needed to continue a discussion.
40
+
41
+ This tool enables natural continuation of discussions by loading relevant context
42
+ from memory:// URIs. It uses pattern matching to find relevant content and builds
43
+ a rich context graph of related information.
44
+
45
+ Args:
46
+ url: memory:// URI pointing to discussion content (e.g. memory://specs/search)
47
+ depth: How many relation hops to traverse (1-3 recommended for performance)
48
+ timeframe: How far back to look. Supports natural language like "2 days ago", "last week"
49
+ page: Page number of results to return (default: 1)
50
+ page_size: Number of results to return per page (default: 10)
51
+ max_related: Maximum number of related results to return (default: 10)
52
+
53
+ Returns:
54
+ GraphContext containing:
55
+ - primary_results: Content matching the memory:// URI
56
+ - related_results: Connected content via relations
57
+ - metadata: Context building details
58
+
59
+ Examples:
60
+ # Continue a specific discussion
61
+ build_context("memory://specs/search")
62
+
63
+ # Get deeper context about a component
64
+ build_context("memory://components/memory-service", depth=2)
65
+
66
+ # Look at recent changes to a specification
67
+ build_context("memory://specs/document-format", timeframe="today")
68
+
69
+ # Research the history of a feature
70
+ build_context("memory://features/knowledge-graph", timeframe="3 months ago")
71
+ """
72
+ logger.info(f"Building context from {url}")
73
+ url = normalize_memory_url(url)
74
+ response = await call_get(
75
+ client,
76
+ f"/memory/{memory_url_path(url)}",
77
+ params={
78
+ "depth": depth,
79
+ "timeframe": timeframe,
80
+ "page": page,
81
+ "page_size": page_size,
82
+ "max_related": max_related,
83
+ },
84
+ )
85
+ return GraphContext.model_validate(response.json())
@@ -6,7 +6,6 @@ This tool creates Obsidian canvas files (.canvas) using the JSON Canvas 1.0 spec
6
6
  import json
7
7
  from typing import Dict, List, Any
8
8
 
9
- import logfire
10
9
  from loguru import logger
11
10
 
12
11
  from basic_memory.mcp.async_client import client
@@ -73,27 +72,26 @@ async def canvas(
73
72
  }
74
73
  ```
75
74
  """
76
- with logfire.span("Creating canvas", folder=folder, title=title): # type: ignore
77
- # Ensure path has .canvas extension
78
- file_title = title if title.endswith(".canvas") else f"{title}.canvas"
79
- file_path = f"{folder}/{file_title}"
75
+ # Ensure path has .canvas extension
76
+ file_title = title if title.endswith(".canvas") else f"{title}.canvas"
77
+ file_path = f"{folder}/{file_title}"
80
78
 
81
- # Create canvas data structure
82
- canvas_data = {"nodes": nodes, "edges": edges}
79
+ # Create canvas data structure
80
+ canvas_data = {"nodes": nodes, "edges": edges}
83
81
 
84
- # Convert to JSON
85
- canvas_json = json.dumps(canvas_data, indent=2)
82
+ # Convert to JSON
83
+ canvas_json = json.dumps(canvas_data, indent=2)
86
84
 
87
- # Write the file using the resource API
88
- logger.info(f"Creating canvas file: {file_path}")
89
- response = await call_put(client, f"/resource/{file_path}", json=canvas_json)
85
+ # Write the file using the resource API
86
+ logger.info(f"Creating canvas file: {file_path}")
87
+ response = await call_put(client, f"/resource/{file_path}", json=canvas_json)
90
88
 
91
- # Parse response
92
- result = response.json()
93
- logger.debug(result)
89
+ # Parse response
90
+ result = response.json()
91
+ logger.debug(result)
94
92
 
95
- # Build summary
96
- action = "Created" if response.status_code == 201 else "Updated"
97
- summary = [f"# {action}: {file_path}", "\nThe canvas is ready to open in Obsidian."]
93
+ # Build summary
94
+ action = "Created" if response.status_code == 201 else "Updated"
95
+ summary = [f"# {action}: {file_path}", "\nThe canvas is ready to open in Obsidian."]
98
96
 
99
- return "\n".join(summary)
97
+ return "\n".join(summary)
@@ -0,0 +1,28 @@
1
+ from basic_memory.mcp.tools.utils import call_delete
2
+
3
+
4
+ from basic_memory.mcp.server import mcp
5
+ from basic_memory.mcp.async_client import client
6
+ from basic_memory.schemas import DeleteEntitiesResponse
7
+
8
+
9
+ @mcp.tool(description="Delete a note by title or permalink")
10
+ async def delete_note(identifier: str) -> bool:
11
+ """Delete a note from the knowledge base.
12
+
13
+ Args:
14
+ identifier: Note title or permalink
15
+
16
+ Returns:
17
+ True if note was deleted, False otherwise
18
+
19
+ Examples:
20
+ # Delete by title
21
+ delete_note("Meeting Notes: Project Planning")
22
+
23
+ # Delete by permalink
24
+ delete_note("notes/project-planning")
25
+ """
26
+ response = await call_delete(client, f"/knowledge/entities/{identifier}")
27
+ result = DeleteEntitiesResponse.model_validate(response.json())
28
+ return result.deleted
@@ -0,0 +1,51 @@
1
+ """Project info tool for Basic Memory MCP server."""
2
+
3
+ from loguru import logger
4
+
5
+ from basic_memory.mcp.async_client import client
6
+ from basic_memory.mcp.server import mcp
7
+ from basic_memory.mcp.tools.utils import call_get
8
+ from basic_memory.schemas import ProjectInfoResponse
9
+
10
+
11
+ @mcp.tool(
12
+ description="Get information and statistics about the current Basic Memory project.",
13
+ )
14
+ async def project_info() -> ProjectInfoResponse:
15
+ """Get comprehensive information about the current Basic Memory project.
16
+
17
+ This tool provides detailed statistics and status information about your
18
+ Basic Memory project, including:
19
+
20
+ - Project configuration
21
+ - Entity, observation, and relation counts
22
+ - Graph metrics (most connected entities, isolated entities)
23
+ - Recent activity and growth over time
24
+ - System status (database, watch service, version)
25
+
26
+ Use this tool to:
27
+ - Verify your Basic Memory installation is working correctly
28
+ - Get insights into your knowledge base structure
29
+ - Monitor growth and activity over time
30
+ - Identify potential issues like unresolved relations
31
+
32
+ Returns:
33
+ Detailed project information and statistics
34
+
35
+ Examples:
36
+ # Get information about the current project
37
+ info = await project_info()
38
+
39
+ # Check entity counts
40
+ print(f"Total entities: {info.statistics.total_entities}")
41
+
42
+ # Check system status
43
+ print(f"Basic Memory version: {info.system.version}")
44
+ """
45
+ logger.info("Getting project info")
46
+
47
+ # Call the API endpoint
48
+ response = await call_get(client, "/stats/project-info")
49
+
50
+ # Convert response to ProjectInfoResponse
51
+ return ProjectInfoResponse.model_validate(response.json())
@@ -1,3 +1,10 @@
1
+ """File reading tool for Basic Memory MCP server.
2
+
3
+ This module provides tools for reading raw file content directly,
4
+ supporting various file types including text, images, and other binary files.
5
+ Files are read directly without any knowledge graph processing.
6
+ """
7
+
1
8
  from loguru import logger
2
9
 
3
10
  from basic_memory.mcp.server import mcp
@@ -136,10 +143,40 @@ def optimize_image(img, content_length, max_output_bytes=350000):
136
143
  return buf.getvalue()
137
144
 
138
145
 
139
- @mcp.tool(description="Read a single file's content by path or permalink")
140
- async def read_resource(path: str) -> dict:
141
- """Get a file's raw content."""
142
- logger.info("Reading resource", path=path)
146
+ @mcp.tool(description="Read a file's raw content by path or permalink")
147
+ async def read_content(path: str) -> dict:
148
+ """Read a file's raw content by path or permalink.
149
+
150
+ This tool provides direct access to file content in the knowledge base,
151
+ handling different file types appropriately:
152
+ - Text files (markdown, code, etc.) are returned as plain text
153
+ - Images are automatically resized/optimized for display
154
+ - Other binary files are returned as base64 if below size limits
155
+
156
+ Args:
157
+ path: The path or permalink to the file. Can be:
158
+ - A regular file path (docs/example.md)
159
+ - A memory URL (memory://docs/example)
160
+ - A permalink (docs/example)
161
+
162
+ Returns:
163
+ A dictionary with the file content and metadata:
164
+ - For text: {"type": "text", "text": "content", "content_type": "text/markdown", "encoding": "utf-8"}
165
+ - For images: {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "base64_data"}}
166
+ - For other files: {"type": "document", "source": {"type": "base64", "media_type": "content_type", "data": "base64_data"}}
167
+ - For errors: {"type": "error", "error": "error message"}
168
+
169
+ Examples:
170
+ # Read a markdown file
171
+ result = await read_file("docs/project-specs.md")
172
+
173
+ # Read an image
174
+ image_data = await read_file("assets/diagram.png")
175
+
176
+ # Read using memory URL
177
+ content = await read_file("memory://docs/architecture")
178
+ """
179
+ logger.info("Reading file", path=path)
143
180
 
144
181
  url = memory_url_path(path)
145
182
  response = await call_get(client, f"/resource/{url}")
@@ -176,7 +213,7 @@ async def read_resource(path: str) -> dict:
176
213
  # Handle other file types
177
214
  else:
178
215
  logger.debug(f"Processing binary resource content_type {content_type}")
179
- if content_length > 350000:
216
+ if content_length > 350000: # pragma: no cover
180
217
  logger.warning("Document too large for response", size=content_length)
181
218
  return {
182
219
  "type": "error",