cubesys/cubecli/cube-notes-mcp: note categories + project threads. notes use doc_type 'note:<category>' for categories and associate to a 'project' record (linked_records) for project threads; cube note add|list|search accept --project/--cat/--since/--until; new 'cube project list' + 'cube project show <coord>' (walk a project's thread). MCP server exposes note_write/list/search/show + project_list/project_show. 4 notes tests pass; workspace check green.
This commit is contained in:
+61
-21
@@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""cube-notes-mcp — MCP (stdio) server exposing the CUBELinux-2 notes CLI as
|
||||
model-facing tools `mcp__cubenotes__note_*`.
|
||||
model-facing tools `mcp__cubenotes__note_*` / `mcp__cubenotes__project_*`.
|
||||
|
||||
Wraps `cube note ...` (the durable WordFlags message-save-path) so the agent
|
||||
can write / list / search / show session notes from the harness. The harness
|
||||
spawns this server via the `mcp-client` plugin (see the profile's
|
||||
cordis.patch.yml); each tool call runs the `cube` binary, which persists to
|
||||
`$CUBE_NOTES_DIR` (default ~/.cubelinux-notes).
|
||||
Wraps `cube note ...` / `cube project ...` (the durable WordFlags
|
||||
message-save-path with categories + project threads). The harness spawns this
|
||||
server via the `mcp-client` plugin; each tool call runs the `cube` binary,
|
||||
which persists to `$CUBE_NOTES_DIR` (default ~/.cubelinux-notes).
|
||||
"""
|
||||
import sys, json, subprocess
|
||||
|
||||
CUBE = "/home/CUBELinux/CUBELinux-2/target/release/cube"
|
||||
SERVER_NAME = "cube-notes-mcp"
|
||||
SERVER_VERSION = "0.1.0"
|
||||
SERVER_VERSION = "0.2.0"
|
||||
PROTOCOL_VERSION = "2024-11-05"
|
||||
|
||||
|
||||
@@ -20,32 +19,52 @@ def tool_schemas():
|
||||
return [
|
||||
{
|
||||
"name": "note_write",
|
||||
"description": "Add a session note to the durable WordFlags CUBE message-save-path "
|
||||
"(per-session/per-day, searchable). Returns the note's C.Z.Y.X coord.",
|
||||
"description": "Add a note to the durable WordFlags message-save-path. "
|
||||
"Optional: project P associates it to a project thread, "
|
||||
"category C categorises it (doc_type note:C). Returns the coord.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"text": {"type": "string", "description": "Note text (subject + body)."}
|
||||
"text": {"type": "string"},
|
||||
"project": {"type": "string", "description": "Project name (thread)."},
|
||||
"category": {"type": "string", "description": "e.g. design, impl, cube."},
|
||||
}, "required": ["text"]},
|
||||
},
|
||||
{
|
||||
"name": "note_list",
|
||||
"description": "List notes (newest first). Optionally filter by session label "
|
||||
"(default: all), or by a date.",
|
||||
"description": "List notes (newest first), optionally filtered by session, project, category, since/until (YYYY-MM-DD).",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"session": {"type": "string", "description": "Session label to filter by."},
|
||||
"session": {"type": "string"},
|
||||
"project": {"type": "string"},
|
||||
"category": {"type": "string"},
|
||||
"since": {"type": "string"},
|
||||
"until": {"type": "string"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"name": "note_search",
|
||||
"description": "Substring-search notes across sessions (case-insensitive).",
|
||||
"description": "Substring-search notes, optionally scoped to a project/category.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"query": {"type": "string", "description": "Substring to find."},
|
||||
"query": {"type": "string"},
|
||||
"project": {"type": "string"},
|
||||
"category": {"type": "string"},
|
||||
}, "required": ["query"]},
|
||||
},
|
||||
{
|
||||
"name": "note_show",
|
||||
"description": "Show one note's subject + body by its C.Z.Y.X coordinate.",
|
||||
"description": "Show one note by C.Z.Y.X.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"coord": {"type": "string", "description": "Note coordinate (C.Z.Y.X)."},
|
||||
"coord": {"type": "string"},
|
||||
}, "required": ["coord"]},
|
||||
},
|
||||
{
|
||||
"name": "project_list",
|
||||
"description": "List all project threads (doc_type=project).",
|
||||
"inputSchema": {"type": "object", "properties": {}},
|
||||
},
|
||||
{
|
||||
"name": "project_show",
|
||||
"description": "Walk a project's associated notes (its thread/timeline) by C.Z.Y.X.",
|
||||
"inputSchema": {"type": "object", "properties": {
|
||||
"coord": {"type": "string"},
|
||||
}, "required": ["coord"]},
|
||||
},
|
||||
]
|
||||
@@ -62,20 +81,41 @@ def run_cube(args):
|
||||
return f"error: {e}"
|
||||
|
||||
|
||||
def opt(flag, val):
|
||||
return [flag, str(val)] if val else []
|
||||
|
||||
|
||||
def dispatch(name, args):
|
||||
args = args or {}
|
||||
if name == "note_write":
|
||||
text = (args.get("text") or "").strip()
|
||||
return run_cube(["note", "add", text]) if text else "error: missing 'text'"
|
||||
if not text:
|
||||
return "error: missing 'text'"
|
||||
return run_cube(["note", "add"] + opt("--project", args.get("project"))
|
||||
+ opt("--cat", args.get("category")) + [text])
|
||||
if name == "note_list":
|
||||
session = args.get("session")
|
||||
return run_cube(["note", "list", session] if session else ["note", "list"])
|
||||
cmd = ["note", "list"]
|
||||
cmd += opt("--project", args.get("project"))
|
||||
cmd += opt("--cat", args.get("category"))
|
||||
cmd += opt("--since", args.get("since"))
|
||||
cmd += opt("--until", args.get("until"))
|
||||
if args.get("session"):
|
||||
cmd.append(args["session"])
|
||||
return run_cube(cmd)
|
||||
if name == "note_search":
|
||||
q = (args.get("query") or "").strip()
|
||||
return run_cube(["note", "search", q]) if q else "error: missing 'query'"
|
||||
if not q:
|
||||
return "error: missing 'query'"
|
||||
return run_cube(["note", "search", q] + opt("--project", args.get("project"))
|
||||
+ opt("--cat", args.get("category")))
|
||||
if name == "note_show":
|
||||
c = (args.get("coord") or "").strip()
|
||||
return run_cube(["note", "show", c]) if c else "error: missing 'coord'"
|
||||
if name == "project_list":
|
||||
return run_cube(["project", "list"])
|
||||
if name == "project_show":
|
||||
c = (args.get("coord") or "").strip()
|
||||
return run_cube(["project", "show", c]) if c else "error: missing 'coord'"
|
||||
return f"error: unknown tool {name}"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user