Compact MCP write command results
This commit is contained in:
@@ -5318,6 +5318,22 @@ def summarize_node(node, view="summary", preview_chars=240):
|
|||||||
return {key: value for key, value in item.items() if value not in (None, "")}
|
return {key: value for key, value in item.items() if value not in (None, "")}
|
||||||
|
|
||||||
|
|
||||||
|
def node_write_reference(node):
|
||||||
|
metadata = node_metadata(node)
|
||||||
|
item = {
|
||||||
|
"id": node.get("id"),
|
||||||
|
"path": node.get("full_path"),
|
||||||
|
"full_path": node.get("full_path"),
|
||||||
|
"type": node.get("type"),
|
||||||
|
"status": node_status(node),
|
||||||
|
"updated_at": node.get("updated_at"),
|
||||||
|
}
|
||||||
|
title = node.get("title") or metadata.get("title")
|
||||||
|
if title:
|
||||||
|
item["title"] = title
|
||||||
|
return {key: value for key, value in item.items() if value not in (None, "")}
|
||||||
|
|
||||||
|
|
||||||
def discovery_envelope(project_id, items, args, *, view=None, next_cursor=None, has_more=None, diagnostics=None, query_analysis=None, warnings=None):
|
def discovery_envelope(project_id, items, args, *, view=None, next_cursor=None, has_more=None, diagnostics=None, query_analysis=None, warnings=None):
|
||||||
view = view or normalize_discovery_view(args)[0]
|
view = view or normalize_discovery_view(args)[0]
|
||||||
limit = normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, discovery_max_limit(view))
|
limit = normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, discovery_max_limit(view))
|
||||||
@@ -7285,7 +7301,25 @@ def guarded_node_patch(args):
|
|||||||
node = ((result.get("data") or {}).get("node") or {}) if isinstance(result, dict) else {}
|
node = ((result.get("data") or {}).get("node") or {}) if isinstance(result, dict) else {}
|
||||||
if node:
|
if node:
|
||||||
index_node(node)
|
index_node(node)
|
||||||
return result
|
if not isinstance(result, dict):
|
||||||
|
return {"ok": True, "changed": True, "path": path}
|
||||||
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
||||||
|
compact = {
|
||||||
|
"ok": bool(result.get("ok", True)),
|
||||||
|
"status": result.get("status") or ("dry_run" if args.get("dry_run") else "updated"),
|
||||||
|
"changed": not bool(data.get("dry_run")),
|
||||||
|
"dry_run": bool(data.get("dry_run")),
|
||||||
|
"path": node.get("full_path") or path,
|
||||||
|
}
|
||||||
|
if data.get("revision"):
|
||||||
|
compact["revision"] = data["revision"]
|
||||||
|
if data.get("read_fingerprint"):
|
||||||
|
compact["read_fingerprint"] = data["read_fingerprint"]
|
||||||
|
if result.get("warnings"):
|
||||||
|
compact["warnings"] = result["warnings"]
|
||||||
|
if result.get("diagnostics"):
|
||||||
|
compact["diagnostics"] = result["diagnostics"]
|
||||||
|
return compact
|
||||||
|
|
||||||
|
|
||||||
def try_get_node_by_path(path, allow_agent=False):
|
def try_get_node_by_path(path, allow_agent=False):
|
||||||
@@ -7343,7 +7377,8 @@ def upsert_node(args):
|
|||||||
existing["full_path"],
|
existing["full_path"],
|
||||||
)
|
)
|
||||||
index_node(updated)
|
index_node(updated)
|
||||||
return {"created": False, "node": decrypt_node_for_return(updated, project_id=project_id)}
|
node = decrypt_node_for_return(updated, project_id=project_id)
|
||||||
|
return {"ok": True, "changed": True, "created": False, "updated": True, **node_write_reference(node)}
|
||||||
write_path = normalize_node_path(args["path"])
|
write_path = normalize_node_path(args["path"])
|
||||||
created = request_project_write(
|
created = request_project_write(
|
||||||
"POST",
|
"POST",
|
||||||
@@ -7355,7 +7390,8 @@ def upsert_node(args):
|
|||||||
write_path,
|
write_path,
|
||||||
)
|
)
|
||||||
index_node(created)
|
index_node(created)
|
||||||
return {"created": True, "node": decrypt_node_for_return(created, project_id=project_id)}
|
node = decrypt_node_for_return(created, project_id=project_id)
|
||||||
|
return {"ok": True, "changed": True, "created": True, **node_write_reference(node)}
|
||||||
|
|
||||||
|
|
||||||
def update_node_by_path(args):
|
def update_node_by_path(args):
|
||||||
@@ -7372,7 +7408,8 @@ def update_node_by_path(args):
|
|||||||
existing["full_path"],
|
existing["full_path"],
|
||||||
)
|
)
|
||||||
index_node(updated)
|
index_node(updated)
|
||||||
return {"updated": True, "node": decrypt_node_for_return(updated, project_id=project_id)}
|
node = decrypt_node_for_return(updated, project_id=project_id)
|
||||||
|
return {"ok": True, "changed": True, "updated": True, **node_write_reference(node)}
|
||||||
|
|
||||||
|
|
||||||
def archive_node_by_path(args):
|
def archive_node_by_path(args):
|
||||||
@@ -7451,11 +7488,12 @@ def link_nodes(args):
|
|||||||
updated = source
|
updated = source
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"changed": created,
|
||||||
"created": created,
|
"created": created,
|
||||||
"relation": relation,
|
"relation": relation,
|
||||||
"source": {"id": source["id"], "full_path": source["full_path"]},
|
"source": {"id": source["id"], "full_path": source["full_path"]},
|
||||||
"target": {"id": target["id"], "full_path": target["full_path"]},
|
"target": {"id": target["id"], "full_path": target["full_path"]},
|
||||||
"updated_node": updated,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -8171,9 +8209,15 @@ def create_kanban_card(args):
|
|||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
index_node(created)
|
index_node(created)
|
||||||
result = {"created": True, "node": decrypt_node_for_return(created, project_id=project_id)}
|
node = decrypt_node_for_return(created, project_id=project_id)
|
||||||
node = result.get("node", result)
|
result = {"created": True, **node_write_reference(node)}
|
||||||
return {"ok": True, "created": bool(result.get("created", True)), "path": node.get("full_path"), "node": kanban_compact_card(node, view="summary")}
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"changed": True,
|
||||||
|
"created": bool(result.get("created", True)),
|
||||||
|
"path": result.get("path") or result.get("full_path"),
|
||||||
|
"column": column,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def update_kanban_card(args):
|
def update_kanban_card(args):
|
||||||
@@ -8196,7 +8240,7 @@ def update_kanban_card(args):
|
|||||||
if "content_text" in args or "body" in args:
|
if "content_text" in args or "body" in args:
|
||||||
payload["content_text"] = args.get("content_text") if "content_text" in args else args.get("body")
|
payload["content_text"] = args.get("content_text") if "content_text" in args else args.get("body")
|
||||||
updated = update_node_by_path(payload)
|
updated = update_node_by_path(payload)
|
||||||
return {"ok": True, "updated": True, "node": kanban_compact_card(updated["node"], view="summary")}
|
return {"ok": True, "changed": True, "updated": True, "path": updated.get("path") or existing["full_path"], "column": kanban_column(existing)}
|
||||||
|
|
||||||
|
|
||||||
def move_kanban_card(args, target_column):
|
def move_kanban_card(args, target_column):
|
||||||
@@ -8224,7 +8268,7 @@ def move_kanban_card(args, target_column):
|
|||||||
remove_indexed_node(existing["id"])
|
remove_indexed_node(existing["id"])
|
||||||
index_node(updated)
|
index_node(updated)
|
||||||
node = decrypt_node_for_return(updated, project_id=project_id)
|
node = decrypt_node_for_return(updated, project_id=project_id)
|
||||||
return {"ok": True, "changed": True, "from": source, "to": node.get("full_path"), "node": kanban_compact_card(node, view="summary")}
|
return {"ok": True, "changed": True, "from": source, "to": node.get("full_path"), "path": node.get("full_path"), "column": target_column}
|
||||||
|
|
||||||
|
|
||||||
def block_kanban_card(args, blocked):
|
def block_kanban_card(args, blocked):
|
||||||
@@ -8426,7 +8470,11 @@ def plan_lifecycle_update(args, *, action, terminal_status, required_target_key,
|
|||||||
|
|
||||||
updated = node
|
updated = node
|
||||||
if planned_changes != ["archive node"]:
|
if planned_changes != ["archive node"]:
|
||||||
updated = update_node_by_path({"path": path, "metadata": metadata, "tags": tags, "links": links}).get("node") or updated
|
update_node_by_path({"path": path, "metadata": metadata, "tags": tags, "links": links})
|
||||||
|
updated = dict(node)
|
||||||
|
updated["metadata"] = metadata
|
||||||
|
updated["tags"] = tags
|
||||||
|
updated["links"] = links
|
||||||
archive_result = archive_node_by_path({"path": path})
|
archive_result = archive_node_by_path({"path": path})
|
||||||
return plan_lifecycle_result(
|
return plan_lifecycle_result(
|
||||||
action,
|
action,
|
||||||
@@ -8839,7 +8887,6 @@ def move_node(args):
|
|||||||
"paths": [updated["full_path"]],
|
"paths": [updated["full_path"]],
|
||||||
"references_rewritten": references_to_rewrite,
|
"references_rewritten": references_to_rewrite,
|
||||||
"links_rewritten": len(references_to_rewrite),
|
"links_rewritten": len(references_to_rewrite),
|
||||||
"node": updated,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -8895,14 +8942,14 @@ def move_folder(args):
|
|||||||
payload = {"folder_path": None if next_folder == "/" else next_folder}
|
payload = {"folder_path": None if next_folder == "/" else next_folder}
|
||||||
saved = request_json("PATCH", f"/v1/projects/{project_id}/nodes/{node['id']}", payload, extra_headers=headers)
|
saved = request_json("PATCH", f"/v1/projects/{project_id}/nodes/{node['id']}", payload, extra_headers=headers)
|
||||||
index_node(saved)
|
index_node(saved)
|
||||||
updated.append(saved)
|
updated.append(node_write_reference(saved))
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
release_project_lock({"lock_id": lock["id"], "lease_token": lock["lease_token"]})
|
release_project_lock({"lock_id": lock["id"], "lease_token": lock["lease_token"]})
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
links_rewritten = sum(len(item["references_to_rewrite"]) for item in reference_plan)
|
links_rewritten = sum(len(item["references_to_rewrite"]) for item in reference_plan)
|
||||||
return {"ok": True, "changed": bool(updated), "dry_run": False, "moved": len(updated), "from_folder": from_folder, "to_folder": to_folder, "items": updated, "reference_plan": reference_plan, "links_rewritten": links_rewritten}
|
return {"ok": True, "changed": bool(updated), "dry_run": False, "moved": len(updated), "from_folder": from_folder, "to_folder": to_folder, "paths": [item.get("path") for item in updated if item.get("path")], "items": updated, "reference_plan": reference_plan, "links_rewritten": links_rewritten}
|
||||||
|
|
||||||
|
|
||||||
def bulk_upsert_nodes(args):
|
def bulk_upsert_nodes(args):
|
||||||
@@ -8932,13 +8979,14 @@ def bulk_upsert_nodes(args):
|
|||||||
saved = request_json("POST", f"/v1/projects/{project_id}/nodes", payload, extra_headers=headers)
|
saved = request_json("POST", f"/v1/projects/{project_id}/nodes", payload, extra_headers=headers)
|
||||||
created = True
|
created = True
|
||||||
index_node(saved)
|
index_node(saved)
|
||||||
results.append({"created": created, "node": decrypt_node_for_return(saved, project_id=project_id)})
|
node = decrypt_node_for_return(saved, project_id=project_id)
|
||||||
|
results.append({"created": created, **node_write_reference(node)})
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
release_project_lock({"lock_id": lock["id"], "lease_token": lock["lease_token"]})
|
release_project_lock({"lock_id": lock["id"], "lease_token": lock["lease_token"]})
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return {"ok": True, "changed": bool(results), "dry_run": False, "count": len(results), "paths": [item["node"].get("full_path") for item in results], "items": results}
|
return {"ok": True, "changed": bool(results), "dry_run": False, "count": len(results), "paths": [item.get("path") for item in results if item.get("path")], "items": results}
|
||||||
|
|
||||||
|
|
||||||
def agent_node_args(args, key="path"):
|
def agent_node_args(args, key="path"):
|
||||||
@@ -8983,7 +9031,7 @@ def upsert_agent_node(args):
|
|||||||
f"update agent node {existing['full_path']}",
|
f"update agent node {existing['full_path']}",
|
||||||
)
|
)
|
||||||
index_node(updated)
|
index_node(updated)
|
||||||
return {"created": False, "node": updated}
|
return {"ok": True, "changed": True, "created": False, "updated": True, **node_write_reference(updated)}
|
||||||
created = request_project_write(
|
created = request_project_write(
|
||||||
"POST",
|
"POST",
|
||||||
f"/v1/projects/{project_id}/nodes",
|
f"/v1/projects/{project_id}/nodes",
|
||||||
@@ -8992,7 +9040,7 @@ def upsert_agent_node(args):
|
|||||||
f"create agent node {scoped['path']}",
|
f"create agent node {scoped['path']}",
|
||||||
)
|
)
|
||||||
index_node(created)
|
index_node(created)
|
||||||
return {"created": True, "node": created}
|
return {"ok": True, "changed": True, "created": True, **node_write_reference(created)}
|
||||||
|
|
||||||
|
|
||||||
def update_agent_node(args):
|
def update_agent_node(args):
|
||||||
@@ -9007,7 +9055,7 @@ def update_agent_node(args):
|
|||||||
f"update agent node {existing['full_path']}",
|
f"update agent node {existing['full_path']}",
|
||||||
)
|
)
|
||||||
index_node(updated)
|
index_node(updated)
|
||||||
return {"updated": True, "node": updated}
|
return {"ok": True, "changed": True, "updated": True, **node_write_reference(updated)}
|
||||||
|
|
||||||
|
|
||||||
def archive_agent_node(args):
|
def archive_agent_node(args):
|
||||||
@@ -9056,11 +9104,12 @@ def link_agent_node(args):
|
|||||||
else:
|
else:
|
||||||
updated = source
|
updated = source
|
||||||
return {
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"changed": created,
|
||||||
"created": created,
|
"created": created,
|
||||||
"relation": relation,
|
"relation": relation,
|
||||||
"source": {"id": source["id"], "full_path": source["full_path"]},
|
"source": {"id": source["id"], "full_path": source["full_path"]},
|
||||||
"target": {"id": target["id"], "full_path": target["full_path"]},
|
"target": {"id": target["id"], "full_path": target["full_path"]},
|
||||||
"updated_node": updated,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9079,8 +9128,8 @@ def create_agent(args):
|
|||||||
"links": [],
|
"links": [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
created.append(result["node"])
|
created.append(result)
|
||||||
return {"agent_slug": slug, "root_path": agent_root_path(slug), "nodes": created}
|
return {"ok": True, "changed": bool(created), "agent_slug": slug, "root_path": agent_root_path(slug), "items": created}
|
||||||
|
|
||||||
|
|
||||||
def show_agent(args):
|
def show_agent(args):
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ def test_kanban_workflow_create_move_and_block(mod):
|
|||||||
|
|
||||||
created = mod.create_kanban_card({"title": "Add MCP commands", "priority": "HIGH"})
|
created = mod.create_kanban_card({"title": "Add MCP commands", "priority": "HIGH"})
|
||||||
assert created["created"] is True
|
assert created["created"] is True
|
||||||
|
assert "node" not in created
|
||||||
assert writes[0][0] == "POST"
|
assert writes[0][0] == "POST"
|
||||||
assert writes[0][2]["folder_path"] == "/kanban/todo"
|
assert writes[0][2]["folder_path"] == "/kanban/todo"
|
||||||
assert writes[0][2]["metadata"]["priority"] == "high"
|
assert writes[0][2]["metadata"]["priority"] == "high"
|
||||||
@@ -378,6 +379,7 @@ def test_kanban_workflow_create_move_and_block(mod):
|
|||||||
|
|
||||||
moved = mod.move_kanban_card({"path": "/kanban/todo/add-mcp-commands"}, "doing")
|
moved = mod.move_kanban_card({"path": "/kanban/todo/add-mcp-commands"}, "doing")
|
||||||
assert moved["to"] == "/kanban/doing/add-mcp-commands"
|
assert moved["to"] == "/kanban/doing/add-mcp-commands"
|
||||||
|
assert "node" not in moved
|
||||||
assert writes[1][0] == "PATCH"
|
assert writes[1][0] == "PATCH"
|
||||||
assert writes[1][2]["folder_path"] == "/kanban/doing"
|
assert writes[1][2]["folder_path"] == "/kanban/doing"
|
||||||
assert writes[1][2]["metadata"]["column"] == "doing"
|
assert writes[1][2]["metadata"]["column"] == "doing"
|
||||||
@@ -385,6 +387,7 @@ def test_kanban_workflow_create_move_and_block(mod):
|
|||||||
|
|
||||||
blocked = mod.block_kanban_card({"path": "/kanban/todo/add-mcp-commands", "blocked_reason": "waiting"}, True)
|
blocked = mod.block_kanban_card({"path": "/kanban/todo/add-mcp-commands", "blocked_reason": "waiting"}, True)
|
||||||
assert blocked["updated"] is True
|
assert blocked["updated"] is True
|
||||||
|
assert "node" not in blocked
|
||||||
assert writes[2][2]["metadata"]["blocked"] is True
|
assert writes[2][2]["metadata"]["blocked"] is True
|
||||||
assert writes[2][2]["metadata"]["blocked_by"] == "waiting"
|
assert writes[2][2]["metadata"]["blocked_by"] == "waiting"
|
||||||
|
|
||||||
@@ -411,6 +414,7 @@ def test_kanban_create_deduplicates_overlapping_titles(mod):
|
|||||||
|
|
||||||
created = mod.create_kanban_card({"title": "Add MCP commands", "column": "todo", "now": "2026-05-10T12:34:56Z"})
|
created = mod.create_kanban_card({"title": "Add MCP commands", "column": "todo", "now": "2026-05-10T12:34:56Z"})
|
||||||
assert created["path"] == "/kanban/todo/add-mcp-commands-20260510123456-2"
|
assert created["path"] == "/kanban/todo/add-mcp-commands-20260510123456-2"
|
||||||
|
assert "node" not in created
|
||||||
assert writes[0][2]["name"] == "add-mcp-commands-20260510123456-2"
|
assert writes[0][2]["name"] == "add-mcp-commands-20260510123456-2"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,11 @@ def run():
|
|||||||
mod.try_get_node_by_path = lambda path, allow_agent=False: None
|
mod.try_get_node_by_path = lambda path, allow_agent=False: None
|
||||||
created = mod.upsert_node({"path": "/notes/beta", "content_text": "tee secret", "metadata": {"private": "tee"}})
|
created = mod.upsert_node({"path": "/notes/beta", "content_text": "tee secret", "metadata": {"private": "tee"}})
|
||||||
tee_body = writes[-1][2]
|
tee_body = writes[-1][2]
|
||||||
assert created["node"]["content_text"] == "tee secret"
|
assert created["ok"] is True
|
||||||
|
assert created["created"] is True
|
||||||
|
assert created["path"] == "/notes/beta"
|
||||||
|
assert "content_text" not in created
|
||||||
|
assert "metadata" not in created
|
||||||
assert tee_body["content_text"] == ""
|
assert tee_body["content_text"] == ""
|
||||||
assert tee_body["metadata"] == {}
|
assert tee_body["metadata"] == {}
|
||||||
assert tee_body["wrapping_epoch"] == 1
|
assert tee_body["wrapping_epoch"] == 1
|
||||||
@@ -363,7 +367,10 @@ def run():
|
|||||||
writes.clear()
|
writes.clear()
|
||||||
patched = mod.update_node_by_path({"path": "/notes/beta", "content_text": "patch secret"})
|
patched = mod.update_node_by_path({"path": "/notes/beta", "content_text": "patch secret"})
|
||||||
patch_body = writes[-1][2]
|
patch_body = writes[-1][2]
|
||||||
assert patched["node"]["content_text"] == "patch secret"
|
assert patched["ok"] is True
|
||||||
|
assert patched["updated"] is True
|
||||||
|
assert patched["path"] == "/notes/beta"
|
||||||
|
assert "content_text" not in patched
|
||||||
assert patch_body["encrypted_content_envelope"]["aad_subject"] == "node_id:node-1"
|
assert patch_body["encrypted_content_envelope"]["aad_subject"] == "node_id:node-1"
|
||||||
|
|
||||||
bulk_writes = []
|
bulk_writes = []
|
||||||
@@ -386,7 +393,9 @@ def run():
|
|||||||
"encrypted_metadata_envelope": payload.get("encrypted_metadata_envelope"),
|
"encrypted_metadata_envelope": payload.get("encrypted_metadata_envelope"),
|
||||||
}
|
}
|
||||||
bulk = mod.bulk_upsert_nodes({"nodes": [{"path": "/bulk/one", "content_text": "bulk secret"}]})
|
bulk = mod.bulk_upsert_nodes({"nodes": [{"path": "/bulk/one", "content_text": "bulk secret"}]})
|
||||||
assert bulk["items"][0]["node"]["content_text"] == "bulk secret"
|
assert bulk["items"][0]["path"] == "/bulk/one"
|
||||||
|
assert "node" not in bulk["items"][0]
|
||||||
|
assert "content_text" not in bulk["items"][0]
|
||||||
assert bulk_writes[0][2]["content_text"] == ""
|
assert bulk_writes[0][2]["content_text"] == ""
|
||||||
assert bulk_writes[0][2]["encrypted_content_envelope"]["aad_subject"].startswith("encryption_id:")
|
assert bulk_writes[0][2]["encrypted_content_envelope"]["aad_subject"].startswith("encryption_id:")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user