1313from basic_memory .cli .commands .command_utils import run_with_cleanup
1414from basic_memory .cli .commands .routing import force_routing , validate_routing_flags
1515from basic_memory .config import ConfigManager
16- from basic_memory .mcp .async_client import get_client
1716from basic_memory .mcp .clients import KnowledgeClient , ResourceClient
18- from basic_memory .mcp .project_context import get_active_project
17+ from basic_memory .mcp .project_context import get_project_client
1918from basic_memory .mcp .tools .utils import call_get
2019from basic_memory .schemas .base import Entity , TimeFrame
2120from basic_memory .schemas .memory import GraphContext , MemoryUrl , memory_url_path
2524from basic_memory .mcp .prompts .continue_conversation import (
2625 continue_conversation as mcp_continue_conversation ,
2726)
28- from basic_memory .mcp .prompts .recent_activity import (
29- recent_activity_prompt as recent_activity_prompt ,
30- )
3127from basic_memory .mcp .tools import build_context as mcp_build_context
3228from basic_memory .mcp .tools import edit_note as mcp_edit_note
3329from basic_memory .mcp .tools import read_note as mcp_read_note
@@ -99,15 +95,26 @@ def _parse_opening_frontmatter(content: str) -> tuple[str, dict[str, Any] | None
9995
10096
10197async def _write_note_json (
102- title : str , content : str , folder : str , project_name : Optional [str ], tags : Optional [List [str ]]
98+ title : str ,
99+ content : str ,
100+ folder : str ,
101+ project_name : Optional [str ],
102+ workspace : Optional [str ],
103+ tags : Optional [List [str ]],
103104) -> dict :
104105 """Write a note and return structured JSON metadata."""
105106 # Use the MCP tool to create/update the entity (handles create-or-update logic)
106- await mcp_write_note .fn (title , content , folder , project_name , tags )
107+ await mcp_write_note .fn (
108+ title = title ,
109+ content = content ,
110+ directory = folder ,
111+ project = project_name ,
112+ workspace = workspace ,
113+ tags = tags ,
114+ )
107115
108116 # Resolve the entity to get metadata back
109- async with get_client (project_name = project_name ) as client :
110- active_project = await get_active_project (client , project_name )
117+ async with get_project_client (project_name , workspace ) as (client , active_project ):
111118 knowledge_client = KnowledgeClient (client , active_project .external_id )
112119
113120 entity = Entity (title = title , directory = folder )
@@ -125,11 +132,14 @@ async def _write_note_json(
125132
126133
127134async def _read_note_json (
128- identifier : str , project_name : Optional [str ], page : int , page_size : int
135+ identifier : str ,
136+ project_name : Optional [str ],
137+ workspace : Optional [str ],
138+ page : int ,
139+ page_size : int ,
129140) -> dict :
130141 """Read a note and return structured JSON with content and metadata."""
131- async with get_client (project_name = project_name ) as client :
132- active_project = await get_active_project (client , project_name )
142+ async with get_project_client (project_name , workspace ) as (client , active_project ):
133143 knowledge_client = KnowledgeClient (client , active_project .external_id )
134144 resource_client = ResourceClient (client , active_project .external_id )
135145
@@ -146,7 +156,10 @@ async def _read_note_json(
146156 from basic_memory .mcp .tools .search import search_notes as mcp_search_tool
147157
148158 title_results = await mcp_search_tool .fn (
149- query = identifier , search_type = "title" , project = project_name
159+ query = identifier ,
160+ search_type = "title" ,
161+ project = project_name ,
162+ workspace = workspace ,
150163 )
151164 if title_results and hasattr (title_results , "results" ) and title_results .results :
152165 result = title_results .results [0 ]
@@ -172,13 +185,13 @@ async def _edit_note_json(
172185 operation : str ,
173186 content : str ,
174187 project_name : Optional [str ],
188+ workspace : Optional [str ],
175189 section : Optional [str ],
176190 find_text : Optional [str ],
177191 expected_replacements : int ,
178192) -> dict :
179193 """Edit a note and return structured JSON metadata."""
180- async with get_client (project_name = project_name ) as client :
181- active_project = await get_active_project (client , project_name )
194+ async with get_project_client (project_name , workspace ) as (client , active_project ):
182195 knowledge_client = KnowledgeClient (client , active_project .external_id )
183196
184197 entity_id = await knowledge_client .resolve_entity (identifier )
@@ -227,11 +240,12 @@ async def _recent_activity_json(
227240 depth : Optional [int ],
228241 timeframe : Optional [TimeFrame ],
229242 project_name : Optional [str ] = None ,
243+ workspace : Optional [str ] = None ,
230244 page : int = 1 ,
231245 page_size : int = 50 ,
232246) -> list :
233247 """Get recent activity and return structured JSON list."""
234- async with get_client (project_name = project_name ) as client :
248+ async with get_project_client (project_name , workspace ) as ( client , active_project ) :
235249 # Build query params matching the MCP tool's logic
236250 params : dict = {"page" : page , "page_size" : page_size , "max_related" : 10 }
237251 if depth :
@@ -241,7 +255,6 @@ async def _recent_activity_json(
241255 if type :
242256 params ["type" ] = [t .value for t in type ]
243257
244- active_project = await get_active_project (client , project_name )
245258 response = await call_get (
246259 client ,
247260 f"/v2/projects/{ active_project .external_id } /memory/recent" ,
@@ -275,6 +288,10 @@ def write_note(
275288 help = "The project to write to. If not provided, the default project will be used."
276289 ),
277290 ] = None ,
291+ workspace : Annotated [
292+ Optional [str ],
293+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
294+ ] = None ,
278295 content : Annotated [
279296 Optional [str ],
280297 typer .Option (
@@ -362,12 +379,19 @@ def write_note(
362379 with force_routing (local = local , cloud = cloud ):
363380 if format == "json" :
364381 result = run_with_cleanup (
365- _write_note_json (title , content , folder , project_name , tags )
382+ _write_note_json (title , content , folder , project_name , workspace , tags )
366383 )
367384 print (json .dumps (result , indent = 2 , ensure_ascii = True , default = str ))
368385 else :
369386 note = run_with_cleanup (
370- mcp_write_note .fn (title , content , folder , project_name , tags )
387+ mcp_write_note .fn (
388+ title = title ,
389+ content = content ,
390+ directory = folder ,
391+ project = project_name ,
392+ workspace = workspace ,
393+ tags = tags ,
394+ )
371395 )
372396 rprint (note )
373397 except ValueError as e :
@@ -389,6 +413,10 @@ def read_note(
389413 help = "The project to use for the note. If not provided, the default project will be used."
390414 ),
391415 ] = None ,
416+ workspace : Annotated [
417+ Optional [str ],
418+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
419+ ] = None ,
392420 page : int = 1 ,
393421 page_size : int = 10 ,
394422 format : str = typer .Option ("text" , "--format" , help = "Output format: text or json" ),
@@ -429,15 +457,23 @@ def read_note(
429457 with force_routing (local = local , cloud = cloud ):
430458 if format == "json" :
431459 result = run_with_cleanup (
432- _read_note_json (identifier , project_name , page , page_size )
460+ _read_note_json (identifier , project_name , workspace , page , page_size )
433461 )
434462 stripped_content , parsed_frontmatter = _parse_opening_frontmatter (result ["content" ])
435463 result ["frontmatter" ] = parsed_frontmatter
436464 if strip_frontmatter :
437465 result ["content" ] = stripped_content
438466 print (json .dumps (result , indent = 2 , ensure_ascii = True , default = str ))
439467 else :
440- note = run_with_cleanup (mcp_read_note .fn (identifier , project_name , page , page_size ))
468+ note = run_with_cleanup (
469+ mcp_read_note .fn (
470+ identifier = identifier ,
471+ project = project_name ,
472+ workspace = workspace ,
473+ page = page ,
474+ page_size = page_size ,
475+ )
476+ )
441477 if strip_frontmatter :
442478 note , _ = _parse_opening_frontmatter (note )
443479 rprint (note )
@@ -462,6 +498,10 @@ def edit_note(
462498 help = "The project to edit. If not provided, the default project will be used."
463499 ),
464500 ] = None ,
501+ workspace : Annotated [
502+ Optional [str ],
503+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
504+ ] = None ,
465505 find_text : Annotated [
466506 Optional [str ], typer .Option ("--find-text" , help = "Text to find for find_replace operation" )
467507 ] = None ,
@@ -509,6 +549,7 @@ def edit_note(
509549 operation = operation ,
510550 content = content ,
511551 project_name = project_name ,
552+ workspace = workspace ,
512553 section = section ,
513554 find_text = find_text ,
514555 expected_replacements = expected_replacements ,
@@ -522,6 +563,7 @@ def edit_note(
522563 operation = operation ,
523564 content = content ,
524565 project = project_name ,
566+ workspace = workspace ,
525567 section = section ,
526568 find_text = find_text ,
527569 expected_replacements = expected_replacements ,
@@ -547,6 +589,10 @@ def build_context(
547589 Optional [str ],
548590 typer .Option (help = "The project to use. If not provided, the default project will be used." ),
549591 ] = None ,
592+ workspace : Annotated [
593+ Optional [str ],
594+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
595+ ] = None ,
550596 depth : Optional [int ] = 1 ,
551597 timeframe : Optional [TimeFrame ] = "7d" ,
552598 page : int = 1 ,
@@ -582,6 +628,7 @@ def build_context(
582628 result = run_with_cleanup (
583629 mcp_build_context .fn (
584630 project = project_name ,
631+ workspace = workspace ,
585632 url = url ,
586633 depth = depth ,
587634 timeframe = timeframe ,
@@ -609,6 +656,10 @@ def recent_activity(
609656 Optional [str ],
610657 typer .Option (help = "The project to use. If not provided, the default project will be used." ),
611658 ] = None ,
659+ workspace : Annotated [
660+ Optional [str ],
661+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
662+ ] = None ,
612663 depth : Optional [int ] = 1 ,
613664 timeframe : Optional [TimeFrame ] = "7d" ,
614665 page : int = typer .Option (1 , "--page" , help = "Page number for pagination (JSON format)" ),
@@ -642,7 +693,15 @@ def recent_activity(
642693 with force_routing (local = local , cloud = cloud ):
643694 if format == "json" :
644695 result = run_with_cleanup (
645- _recent_activity_json (type , depth , timeframe , project_name , page , page_size )
696+ _recent_activity_json (
697+ type = type ,
698+ depth = depth ,
699+ timeframe = timeframe ,
700+ project_name = project_name ,
701+ workspace = workspace ,
702+ page = page ,
703+ page_size = page_size ,
704+ )
646705 )
647706 print (json .dumps (result , indent = 2 , ensure_ascii = True , default = str ))
648707 else :
@@ -652,6 +711,7 @@ def recent_activity(
652711 depth = depth ,
653712 timeframe = timeframe ,
654713 project = project_name ,
714+ workspace = workspace ,
655715 )
656716 )
657717 # The tool returns a formatted string directly
@@ -682,6 +742,10 @@ def search_notes(
682742 help = "The project to use for the note. If not provided, the default project will be used."
683743 ),
684744 ] = None ,
745+ workspace : Annotated [
746+ Optional [str ],
747+ typer .Option (help = "Cloud workspace tenant ID or unique name to route this request." ),
748+ ] = None ,
685749 after_date : Annotated [
686750 Optional [str ],
687751 typer .Option ("--after_date" , help = "Search results after date, eg. '2d', '1 week'" ),
@@ -793,8 +857,9 @@ def search_notes(
793857 with force_routing (local = local , cloud = cloud ):
794858 results = run_with_cleanup (
795859 mcp_search .fn (
796- query or "" ,
797- project_name ,
860+ query = query or "" ,
861+ project = project_name ,
862+ workspace = workspace ,
798863 search_type = search_type ,
799864 page = page ,
800865 after_date = after_date ,
@@ -857,22 +922,3 @@ def continue_conversation(
857922 typer .echo (f"Error continuing conversation: { e } " , err = True )
858923 raise typer .Exit (1 )
859924 raise
860-
861-
862- # @tool_app.command(name="show-recent-activity")
863- # def show_recent_activity(
864- # timeframe: Annotated[
865- # str, typer.Option(help="How far back to look for activity")
866- # ] = "7d",
867- # ):
868- # """Prompt to show recent activity."""
869- # try:
870- # # Prompt functions return formatted strings directly
871- # session = asyncio.run(recent_activity_prompt(timeframe=timeframe))
872- # rprint(session)
873- # except Exception as e: # pragma: no cover
874- # if not isinstance(e, typer.Exit):
875- # logger.exception("Error continuing conversation", e)
876- # typer.echo(f"Error continuing conversation: {e}", err=True)
877- # raise typer.Exit(1)
878- # raise
0 commit comments