Skip to content

Commit e14ba92

Browse files
groksrcclaude
andauthored
fix: resolve MCP prompt rendering errors (#524)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9d98892 commit e14ba92

6 files changed

Lines changed: 119 additions & 228 deletions

File tree

src/basic_memory/mcp/prompts/continue_conversation.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from basic_memory.mcp.async_client import get_client
1414
from basic_memory.mcp.server import mcp
1515
from basic_memory.mcp.tools.utils import call_post
16-
from basic_memory.schemas.base import TimeFrame
1716
from basic_memory.schemas.prompt import ContinueConversationRequest
1817

1918

@@ -24,7 +23,7 @@
2423
async def continue_conversation(
2524
topic: Annotated[Optional[str], Field(description="Topic or keyword to search for")] = None,
2625
timeframe: Annotated[
27-
Optional[TimeFrame],
26+
Optional[str],
2827
Field(description="How far back to look for activity (e.g. '1d', '1 week')"),
2928
] = None,
3029
) -> str:

src/basic_memory/mcp/prompts/recent_activity.py

Lines changed: 40 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
These prompts help users see what has changed in their knowledge base recently.
44
"""
55

6+
from textwrap import dedent
67
from typing import Annotated, Optional
78

89
from loguru import logger
910
from pydantic import Field
1011

11-
from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
1212
from basic_memory.mcp.server import mcp
1313
from basic_memory.mcp.tools.recent_activity import recent_activity
14-
from basic_memory.schemas.base import TimeFrame
15-
from basic_memory.schemas.memory import GraphContext, ProjectActivitySummary
16-
from basic_memory.schemas.search import SearchItemType
1714

1815

1916
@mcp.prompt(
@@ -22,7 +19,7 @@
2219
)
2320
async def recent_activity_prompt(
2421
timeframe: Annotated[
25-
TimeFrame,
22+
str,
2623
Field(description="How far back to look for activity (e.g. '1d', '1 week')"),
2724
] = "7d",
2825
project: Annotated[
@@ -47,142 +44,57 @@ async def recent_activity_prompt(
4744
"""
4845
logger.info(f"Getting recent activity, timeframe: {timeframe}, project: {project}")
4946

50-
recent = await recent_activity.fn(
51-
project=project, timeframe=timeframe, type=[SearchItemType.ENTITY]
47+
# Call the tool function - it returns a well-formatted string
48+
# Pass type as string values (not enum) to match the tool's expected input
49+
activity_summary = await recent_activity.fn(
50+
project=project, timeframe=timeframe, type="entity"
5251
)
5352

54-
# Extract primary results from the hierarchical structure
55-
primary_results = []
56-
related_results = []
57-
58-
if isinstance(recent, ProjectActivitySummary):
59-
# Discovery mode - extract results from all projects
60-
for _, project_activity in recent.projects.items():
61-
if project_activity.activity.results:
62-
# Take up to 2 primary results per project
63-
for item in project_activity.activity.results[:2]:
64-
primary_results.append(item.primary_result)
65-
# Add up to 1 related result per primary item
66-
if item.related_results:
67-
related_results.extend(item.related_results[:1]) # pragma: no cover
68-
69-
# Limit total results for readability
70-
primary_results = primary_results[:8]
71-
related_results = related_results[:6]
72-
73-
elif isinstance(recent, GraphContext):
74-
# Project-specific mode - use existing logic
75-
if recent.results:
76-
# Take up to 5 primary results
77-
for item in recent.results[:5]:
78-
primary_results.append(item.primary_result)
79-
# Add up to 2 related results per primary item
80-
if item.related_results:
81-
related_results.extend(item.related_results[:2]) # pragma: no cover
82-
83-
# Set topic based on mode
84-
if project:
85-
topic = f"Recent Activity in {project} ({timeframe})"
86-
else:
87-
topic = f"Recent Activity Across All Projects ({timeframe})"
88-
89-
prompt_context = format_prompt_context(
90-
PromptContext(
91-
topic=topic,
92-
timeframe=timeframe,
93-
results=[
94-
PromptContextItem(
95-
primary_results=primary_results,
96-
related_results=related_results[:10], # Limit total related results
97-
)
98-
],
99-
)
100-
)
101-
102-
# Add mode-specific suggestions
103-
first_title = "Recent Topic"
104-
if primary_results and len(primary_results) > 0:
105-
first_title = primary_results[0].title
106-
107-
if project:
108-
# Project-specific suggestions
109-
capture_suggestions = f"""
110-
## Opportunity to Capture Activity Summary
111-
112-
Consider creating a summary note of recent activity in {project}:
113-
114-
```python
115-
await write_note(
116-
"{project}",
117-
title="Activity Summary {timeframe}",
118-
content='''
119-
# Activity Summary for {project} ({timeframe})
120-
121-
## Overview
122-
[Summary of key changes and developments in this project over this period]
53+
# Build the prompt response
54+
# The tool already returns formatted markdown, so we use it directly
55+
# and add prompt-specific guidance
56+
target = project if project else "all projects"
12357

124-
## Key Updates
125-
[List main updates and their significance within this project]
58+
prompt_guidance = dedent(f"""
59+
# Recent Activity Context
12660
127-
## Observations
128-
- [trend] [Observation about patterns in recent activity]
129-
- [insight] [Connection between different activities]
61+
This is a memory retrieval session showing recent activity from {target}.
13062
131-
## Relations
132-
- summarizes [[{first_title}]]
133-
- relates_to [[{project} Overview]]
134-
''',
135-
folder="summaries"
136-
)
137-
```
63+
{activity_summary}
13864
139-
Summarizing periodic activity helps create high-level insights and connections within the project.
140-
"""
141-
else:
142-
# Discovery mode suggestions
143-
project_count = len(recent.projects) if isinstance(recent, ProjectActivitySummary) else 0
144-
most_active = (
145-
getattr(recent.summary, "most_active_project", "Unknown")
146-
if isinstance(recent, ProjectActivitySummary)
147-
else "Unknown"
148-
)
65+
---
14966
150-
capture_suggestions = f"""
151-
## Cross-Project Activity Discovery
67+
## Next Steps
15268
153-
Found activity across {project_count} projects. Most active: **{most_active}**
69+
Based on this activity, you can:
15470
155-
Consider creating a cross-project summary:
71+
1. **Explore specific items** - Use `read_note("permalink")` to dive deeper into any item
72+
2. **Search for related content** - Use `search_notes("topic")` to find connected knowledge
73+
3. **Build context** - Use `build_context("memory://path")` to see relationships
15674
157-
```python
158-
await write_note(
159-
"{most_active if most_active != "Unknown" else "main"}",
160-
title="Cross-Project Activity Summary {timeframe}",
161-
content='''
162-
# Cross-Project Activity Summary ({timeframe})
75+
## Capture Opportunity
16376
164-
## Overview
165-
Activity found across {project_count} projects, with {most_active} showing the most activity.
77+
If you notice patterns or insights from this activity, consider documenting them:
16678
167-
## Key Developments
168-
[Summarize important changes across all projects]
79+
```python
80+
write_note(
81+
title="Activity Insights - {timeframe}",
82+
content='''
83+
# Activity Insights
16984
170-
## Project Insights
171-
[Note patterns or connections between projects]
85+
## Patterns Observed
86+
- [trend] [Pattern you noticed in the activity]
17287
173-
## Observations
174-
- [trend] [Cross-project patterns observed]
175-
- [insight] [Connections between different project activities]
88+
## Key Developments
89+
- [insight] [Important development worth tracking]
17690
177-
## Relations
178-
- summarizes [[{first_title}]]
179-
- relates_to [[Project Portfolio Overview]]
180-
''',
181-
folder="summaries"
182-
)
183-
```
184-
185-
Cross-project summaries help identify broader trends and project interconnections.
186-
"""
91+
## Relations
92+
- summarizes [[Recent Work]]
93+
''',
94+
folder="insights",
95+
project="{project or 'default'}"
96+
)
97+
```
98+
""")
18799

188-
return prompt_context + capture_suggestions
100+
return prompt_guidance

src/basic_memory/mcp/prompts/search.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from basic_memory.mcp.async_client import get_client
1313
from basic_memory.mcp.server import mcp
1414
from basic_memory.mcp.tools.utils import call_post
15-
from basic_memory.schemas.base import TimeFrame
1615
from basic_memory.schemas.prompt import SearchPromptRequest
1716

1817

@@ -23,7 +22,7 @@
2322
async def search_prompt(
2423
query: str,
2524
timeframe: Annotated[
26-
Optional[TimeFrame],
25+
Optional[str],
2726
Field(description="How far back to search (e.g. '1d', '1 week')"),
2827
] = None,
2928
) -> str:

src/basic_memory/mcp/prompts/utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from textwrap import dedent
99
from typing import List
1010

11-
from basic_memory.schemas.base import TimeFrame
1211
from basic_memory.schemas.memory import (
1312
normalize_memory_url,
1413
EntitySummary,
@@ -25,7 +24,7 @@ class PromptContextItem:
2524

2625
@dataclass
2726
class PromptContext:
28-
timeframe: TimeFrame
27+
timeframe: str
2928
topic: str
3029
results: List[PromptContextItem]
3130

tests/mcp/test_prompts.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ async def test_recent_activity_prompt_discovery_mode(client, test_project, test_
153153
result = await recent_activity_prompt.fn(timeframe="1w") # pyright: ignore [reportGeneralTypeIssues]
154154

155155
# Check the response contains expected discovery mode content
156-
assert "Recent Activity Across All Projects" in result # pyright: ignore [reportOperatorIssue]
157-
assert "Cross-Project Activity Discovery" in result # pyright: ignore [reportOperatorIssue]
156+
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]
157+
assert "all projects" in result # pyright: ignore [reportOperatorIssue]
158+
assert "Next Steps" in result # pyright: ignore [reportOperatorIssue]
158159
assert "write_note" in result # pyright: ignore [reportOperatorIssue]
159160

160161

@@ -165,9 +166,9 @@ async def test_recent_activity_prompt_project_specific(client, test_project, tes
165166
result = await recent_activity_prompt.fn(timeframe="1w", project=test_project.name) # pyright: ignore [reportGeneralTypeIssues]
166167

167168
# Check the response contains expected project-specific content
168-
assert f"Recent Activity in {test_project.name}" in result # pyright: ignore [reportOperatorIssue]
169-
assert "Opportunity to Capture Activity Summary" in result # pyright: ignore [reportOperatorIssue]
170-
assert f"recent activity in {test_project.name}" in result # pyright: ignore [reportOperatorIssue]
169+
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]
170+
assert test_project.name in result # pyright: ignore [reportOperatorIssue]
171+
assert "Next Steps" in result # pyright: ignore [reportOperatorIssue]
171172
assert "write_note" in result # pyright: ignore [reportOperatorIssue]
172173

173174

@@ -178,4 +179,5 @@ async def test_recent_activity_prompt_with_custom_timeframe(client, test_project
178179
result = await recent_activity_prompt.fn(timeframe="1d") # pyright: ignore [reportGeneralTypeIssues]
179180

180181
# Check the response includes the custom timeframe
181-
assert "Recent Activity Across All Projects (1d)" in result # pyright: ignore [reportOperatorIssue]
182+
assert "1d" in result # pyright: ignore [reportOperatorIssue]
183+
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]

0 commit comments

Comments
 (0)