coherence-mcp-server 0.4.5 → 0.5.0

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.
@@ -1,6 +1,6 @@
1
1
  """Coherence Network MCP server — Python implementation.
2
2
 
3
- Exposes the Coherence Network API as 22 typed MCP tools.
3
+ Exposes the Coherence Network API as 60 typed MCP tools.
4
4
  """
5
5
 
6
6
  from __future__ import annotations
@@ -90,6 +90,9 @@ TOOLS: list[Tool] = [
90
90
  "properties": {
91
91
  "limit": {"type": "number", "description": "Max ideas to return (default 20)", "default": 20},
92
92
  "search": {"type": "string", "description": "Search keyword to filter ideas"},
93
+ "workspace_id": {"type": "string", "description": "Optional workspace filter. Defaults to all workspaces."},
94
+ "pillar": {"type": "string", "description": "Optional pillar filter."},
95
+ "curated_only": {"type": "boolean", "description": "If true, only return curated super-ideas."},
93
96
  },
94
97
  },
95
98
  ),
@@ -142,6 +145,7 @@ TOOLS: list[Tool] = [
142
145
  "properties": {
143
146
  "limit": {"type": "number", "default": 20},
144
147
  "search": {"type": "string", "description": "Search keyword"},
148
+ "workspace_id": {"type": "string", "description": "Optional workspace filter. Defaults to all workspaces."},
145
149
  },
146
150
  },
147
151
  ),
@@ -252,12 +256,6 @@ TOOLS: list[Tool] = [
252
256
  "properties": {"window_days": {"type": "number", "default": 30}},
253
257
  },
254
258
  ),
255
- # Governance
256
- Tool(
257
- name="coherence_list_change_requests",
258
- description="List governance change requests.",
259
- inputSchema={"type": "object", "properties": {}},
260
- ),
261
259
  # Federation
262
260
  Tool(
263
261
  name="coherence_list_federation_nodes",
@@ -351,7 +349,7 @@ TOOLS: list[Tool] = [
351
349
  # Ideas (Lifecycle)
352
350
  Tool(
353
351
  name="coherence_create_idea",
354
- description="Create a new idea in the portfolio.",
352
+ description="Create a new idea in the portfolio. Scoped to a workspace (default: coherence-network).",
355
353
  inputSchema={
356
354
  "type": "object",
357
355
  "required": ["id", "name", "description"],
@@ -361,8 +359,54 @@ TOOLS: list[Tool] = [
361
359
  "description": {"type": "string", "description": "Detailed vision and value proposition"},
362
360
  "potential_value": {"type": "number", "description": "Estimated CC value (0-1000)", "default": 100},
363
361
  "estimated_cost": {"type": "number", "description": "Estimated CC cost (0-1000)", "default": 50},
364
- "parent_idea_id": {"type": "string", "description": "Optional parent idea for hierarchy"},
362
+ "parent_idea_id": {"type": "string", "description": "Optional parent idea for hierarchy. Must belong to the same workspace."},
365
363
  "tags": {"type": "array", "items": {"type": "string"}, "description": "List of tags"},
364
+ "workspace_id": {"type": "string", "description": "Owning workspace slug. Defaults to 'coherence-network'."},
365
+ "pillar": {"type": "string", "description": "Top-level pillar (must be one of the workspace's declared pillars)."},
366
+ },
367
+ },
368
+ ),
369
+ # Workspaces (Tenant primitive)
370
+ Tool(
371
+ name="coherence_list_workspaces",
372
+ description="List all workspaces (tenants). Each workspace owns its own ideas, specs, pillars, and agent personas.",
373
+ inputSchema={"type": "object", "properties": {}},
374
+ ),
375
+ Tool(
376
+ name="coherence_get_workspace",
377
+ description="Get a workspace's full manifest (name, description, pillars, visibility).",
378
+ inputSchema={
379
+ "type": "object",
380
+ "required": ["workspace_id"],
381
+ "properties": {
382
+ "workspace_id": {"type": "string", "description": "Workspace slug (e.g. 'coherence-network')"},
383
+ },
384
+ },
385
+ ),
386
+ Tool(
387
+ name="coherence_create_workspace",
388
+ description="Create a new workspace (tenant) with its own pillar taxonomy. Use this to onboard a contributor team with isolated ideas/specs/agents.",
389
+ inputSchema={
390
+ "type": "object",
391
+ "required": ["id", "name"],
392
+ "properties": {
393
+ "id": {"type": "string", "description": "Slug: lowercase + hyphens (e.g. 'my-startup')"},
394
+ "name": {"type": "string", "description": "Human name"},
395
+ "description": {"type": "string", "description": "Workspace purpose"},
396
+ "pillars": {"type": "array", "items": {"type": "string"}, "description": "Top-level taxonomy for grouping ideas"},
397
+ "visibility": {"type": "string", "description": "public | federation | private", "default": "public"},
398
+ "owner_contributor_id": {"type": "string", "description": "Contributor who owns this workspace"},
399
+ },
400
+ },
401
+ ),
402
+ Tool(
403
+ name="coherence_get_workspace_pillars",
404
+ description="Get the pillar taxonomy declared by a workspace. Use this before setting `pillar` on a new idea.",
405
+ inputSchema={
406
+ "type": "object",
407
+ "required": ["workspace_id"],
408
+ "properties": {
409
+ "workspace_id": {"type": "string"},
366
410
  },
367
411
  },
368
412
  ),
@@ -713,6 +757,71 @@ TOOLS: list[Tool] = [
713
757
  },
714
758
  },
715
759
  ),
760
+ # Spec CRUD
761
+ Tool(
762
+ name="coherence_create_spec",
763
+ description="Create a new spec in the registry. Requires spec_id (slug), title, summary, and idea_id.",
764
+ inputSchema={
765
+ "type": "object",
766
+ "required": ["spec_id", "title", "summary"],
767
+ "properties": {
768
+ "spec_id": {"type": "string", "description": "Unique spec slug (e.g. '170-my-feature')"},
769
+ "title": {"type": "string", "description": "Spec title"},
770
+ "summary": {"type": "string", "description": "Spec summary"},
771
+ "idea_id": {"type": "string", "description": "Parent idea ID"},
772
+ "potential_value": {"type": "number", "description": "Estimated CC value", "default": 0},
773
+ "estimated_cost": {"type": "number", "description": "Estimated CC cost", "default": 0},
774
+ "content_path": {"type": "string", "description": "Path to spec content file"},
775
+ "implementation_summary": {"type": "string", "description": "Implementation summary text"},
776
+ },
777
+ },
778
+ ),
779
+ Tool(
780
+ name="coherence_update_spec",
781
+ description="Update an existing spec's properties (title, summary, value, cost, implementation summary).",
782
+ inputSchema={
783
+ "type": "object",
784
+ "required": ["spec_id"],
785
+ "properties": {
786
+ "spec_id": {"type": "string", "description": "The spec ID"},
787
+ "title": {"type": "string"},
788
+ "summary": {"type": "string"},
789
+ "potential_value": {"type": "number"},
790
+ "estimated_cost": {"type": "number"},
791
+ "actual_value": {"type": "number"},
792
+ "actual_cost": {"type": "number"},
793
+ "idea_id": {"type": "string"},
794
+ "implementation_summary": {"type": "string"},
795
+ "process_summary": {"type": "string"},
796
+ "content_path": {"type": "string"},
797
+ },
798
+ },
799
+ ),
800
+ # Workflow
801
+ Tool(
802
+ name="coherence_advance_idea",
803
+ description="Check prerequisites and advance an idea to its next lifecycle stage. Returns current state, prerequisite check results, and whether advancement succeeded.",
804
+ inputSchema={
805
+ "type": "object",
806
+ "required": ["idea_id"],
807
+ "properties": {
808
+ "idea_id": {"type": "string", "description": "The idea ID to advance"},
809
+ "force": {"type": "boolean", "description": "Skip prerequisite checks", "default": False},
810
+ },
811
+ },
812
+ ),
813
+ Tool(
814
+ name="coherence_trace",
815
+ description="Trace the full lineage of any entity — from idea to specs to tasks to source files. Provides navigation breadcrumbs across the entire system.",
816
+ inputSchema={
817
+ "type": "object",
818
+ "required": ["entity_id"],
819
+ "properties": {
820
+ "entity_id": {"type": "string", "description": "An idea slug, spec slug, or task ID"},
821
+ "entity_type": {"type": "string", "description": "idea, spec, or task", "default": "idea"},
822
+ },
823
+ },
824
+ ),
716
825
  ]
717
826
 
718
827
  TOOL_MAP: dict[str, Tool] = {t.name: t for t in TOOLS}
@@ -727,7 +836,14 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
727
836
  case "coherence_list_ideas":
728
837
  if args.get("search"):
729
838
  return api_get("/api/ideas/cards", {"search": args["search"], "limit": args.get("limit", 20)})
730
- return api_get("/api/ideas", {"limit": args.get("limit", 20)})
839
+ params = {"limit": args.get("limit", 20)}
840
+ if args.get("workspace_id"):
841
+ params["workspace_id"] = args["workspace_id"]
842
+ if args.get("pillar"):
843
+ params["pillar"] = args["pillar"]
844
+ if args.get("curated_only"):
845
+ params["curated_only"] = "true"
846
+ return api_get("/api/ideas", params)
731
847
  case "coherence_get_idea":
732
848
  return api_get(f"/api/ideas/{args['idea_id']}")
733
849
  case "coherence_idea_progress":
@@ -742,7 +858,10 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
742
858
  case "coherence_list_specs":
743
859
  if args.get("search"):
744
860
  return api_get("/api/spec-registry/cards", {"search": args["search"], "limit": args.get("limit", 20)})
745
- return api_get("/api/spec-registry", {"limit": args.get("limit", 20)})
861
+ params = {"limit": args.get("limit", 20)}
862
+ if args.get("workspace_id"):
863
+ params["workspace_id"] = args["workspace_id"]
864
+ return api_get("/api/spec-registry", params)
746
865
  case "coherence_get_spec":
747
866
  return api_get(f"/api/spec-registry/{args['spec_id']}")
748
867
  # Lineage
@@ -793,9 +912,6 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
793
912
  }
794
913
  case "coherence_friction_report":
795
914
  return api_get("/api/friction/report", {"window_days": args.get("window_days", 30)})
796
- # Governance
797
- case "coherence_list_change_requests":
798
- return api_get("/api/governance/change-requests")
799
915
  # Federation
800
916
  case "coherence_list_federation_nodes":
801
917
  nodes = api_get("/api/federation/nodes")
@@ -860,7 +976,25 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
860
976
  "estimated_cost": args.get("estimated_cost", 50),
861
977
  "parent_idea_id": args.get("parent_idea_id"),
862
978
  "tags": args.get("tags"),
979
+ "workspace_id": args.get("workspace_id"),
980
+ "pillar": args.get("pillar"),
863
981
  })
982
+ # Workspaces
983
+ case "coherence_list_workspaces":
984
+ return api_get("/api/workspaces")
985
+ case "coherence_get_workspace":
986
+ return api_get(f"/api/workspaces/{args['workspace_id']}")
987
+ case "coherence_create_workspace":
988
+ return api_post("/api/workspaces", {
989
+ "id": args["id"],
990
+ "name": args["name"],
991
+ "description": args.get("description", ""),
992
+ "pillars": args.get("pillars", []),
993
+ "visibility": args.get("visibility", "public"),
994
+ "owner_contributor_id": args.get("owner_contributor_id"),
995
+ })
996
+ case "coherence_get_workspace_pillars":
997
+ return api_get(f"/api/workspaces/{args['workspace_id']}/pillars")
864
998
  case "coherence_update_idea":
865
999
  return api_patch(f"/api/ideas/{args['idea_id']}", {
866
1000
  "name": args.get("name"),
@@ -1000,6 +1134,123 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1000
1134
  "relationship_type": args["relationship_type"],
1001
1135
  "created_by": args.get("created_by", "mcp"),
1002
1136
  })
1137
+ # Spec CRUD
1138
+ case "coherence_create_spec":
1139
+ return api_post("/api/spec-registry", {
1140
+ "spec_id": args["spec_id"],
1141
+ "title": args["title"],
1142
+ "summary": args["summary"],
1143
+ "idea_id": args.get("idea_id"),
1144
+ "potential_value": args.get("potential_value", 0),
1145
+ "estimated_cost": args.get("estimated_cost", 0),
1146
+ "content_path": args.get("content_path"),
1147
+ "implementation_summary": args.get("implementation_summary"),
1148
+ })
1149
+ case "coherence_update_spec":
1150
+ body = {
1151
+ k: v for k, v in {
1152
+ "title": args.get("title"),
1153
+ "summary": args.get("summary"),
1154
+ "potential_value": args.get("potential_value"),
1155
+ "estimated_cost": args.get("estimated_cost"),
1156
+ "actual_value": args.get("actual_value"),
1157
+ "actual_cost": args.get("actual_cost"),
1158
+ "idea_id": args.get("idea_id"),
1159
+ "implementation_summary": args.get("implementation_summary"),
1160
+ "process_summary": args.get("process_summary"),
1161
+ "content_path": args.get("content_path"),
1162
+ }.items() if v is not None
1163
+ }
1164
+ return api_patch(f"/api/spec-registry/{args['spec_id']}", body)
1165
+ # Workflow
1166
+ case "coherence_advance_idea":
1167
+ idea_id = args["idea_id"]
1168
+ force = args.get("force", False)
1169
+ idea = api_get(f"/api/ideas/{idea_id}")
1170
+ if isinstance(idea, dict) and "error" in idea:
1171
+ return idea
1172
+ progress = api_get(f"/api/ideas/{idea_id}/progress")
1173
+ current_stage = idea.get("stage", "none") if isinstance(idea, dict) else "none"
1174
+ stage_order = ["none", "specced", "implementing", "testing", "reviewing", "complete"]
1175
+ idx = stage_order.index(current_stage) if current_stage in stage_order else 0
1176
+ if idx >= len(stage_order) - 1:
1177
+ return {"idea_id": idea_id, "current_stage": current_stage, "next_stage": None,
1178
+ "prerequisites_met": False, "prerequisite_details": {"reason": "already at final stage"},
1179
+ "advanced": False, "result": None}
1180
+ next_stage = stage_order[idx + 1]
1181
+ # Check prerequisites from progress tasks
1182
+ tasks_by_phase = progress.get("tasks_by_phase", {}) if isinstance(progress, dict) else {}
1183
+ prereq_checks = {
1184
+ "specced": ("spec", "at least 1 spec task completed"),
1185
+ "implementing": ("impl", "at least 1 impl task exists"),
1186
+ "testing": ("impl", "at least 1 impl task completed"),
1187
+ "reviewing": ("test", "at least 1 test task completed"),
1188
+ "complete": ("review", "at least 1 review task completed"),
1189
+ }
1190
+ prereqs_met = True
1191
+ prereq_details: dict[str, Any] = {}
1192
+ if next_stage in prereq_checks:
1193
+ phase_key, description = prereq_checks[next_stage]
1194
+ phase_tasks = tasks_by_phase.get(phase_key, [])
1195
+ if next_stage == "implementing":
1196
+ prereqs_met = len(phase_tasks) > 0
1197
+ prereq_details = {"check": description, "phase": phase_key, "task_count": len(phase_tasks)}
1198
+ else:
1199
+ completed = [t for t in phase_tasks if isinstance(t, dict) and t.get("status") == "completed"]
1200
+ prereqs_met = len(completed) > 0
1201
+ prereq_details = {"check": description, "phase": phase_key,
1202
+ "completed_count": len(completed), "total_count": len(phase_tasks)}
1203
+ advanced = False
1204
+ result = None
1205
+ if prereqs_met or force:
1206
+ result = api_patch(f"/api/ideas/{idea_id}", {"stage": next_stage})
1207
+ advanced = not (isinstance(result, dict) and "error" in result)
1208
+ return {"idea_id": idea_id, "current_stage": current_stage, "next_stage": next_stage,
1209
+ "prerequisites_met": prereqs_met, "prerequisite_details": prereq_details,
1210
+ "advanced": advanced, "result": result}
1211
+ case "coherence_trace":
1212
+ entity_id = args["entity_id"]
1213
+ entity_type = args.get("entity_type", "idea")
1214
+ if entity_type == "idea":
1215
+ idea = api_get(f"/api/ideas/{entity_id}")
1216
+ progress = api_get(f"/api/ideas/{entity_id}/progress")
1217
+ specs = api_get("/api/spec-registry/cards", {"q": entity_id, "limit": 50})
1218
+ all_tasks = api_get("/api/agent/tasks", {"status": None, "limit": 50})
1219
+ task_list = all_tasks.get("tasks", []) if isinstance(all_tasks, dict) else []
1220
+ idea_tasks = [t for t in task_list if isinstance(t, dict)
1221
+ and isinstance(t.get("context"), dict) and t["context"].get("idea_id") == entity_id]
1222
+ spec_items = specs if isinstance(specs, list) else specs.get("items", []) if isinstance(specs, dict) else []
1223
+ spec_ids = [s.get("spec_id", s.get("id", "")) for s in spec_items if isinstance(s, dict)]
1224
+ return {"entity_type": "idea", "idea": idea, "progress": progress,
1225
+ "specs": spec_items, "tasks": idea_tasks,
1226
+ "navigation": {"idea_file": f"ideas/{entity_id}.md",
1227
+ "spec_files": [f"specs/{s}.md" for s in spec_ids],
1228
+ "api": f"/api/ideas/{entity_id}",
1229
+ "cli": f"cc idea {entity_id}"}}
1230
+ elif entity_type == "spec":
1231
+ spec = api_get(f"/api/spec-registry/{entity_id}")
1232
+ spec_file = api_get("/api/content/file", {"path": f"specs/{entity_id}.md"})
1233
+ idea_id = spec.get("idea_id") if isinstance(spec, dict) else None
1234
+ parent_idea = api_get(f"/api/ideas/{idea_id}") if idea_id else None
1235
+ nav: dict[str, Any] = {"spec_file": f"specs/{entity_id}.md",
1236
+ "api": f"/api/spec-registry/{entity_id}",
1237
+ "cli": f"cc spec {entity_id}"}
1238
+ if idea_id:
1239
+ nav["idea_file"] = f"ideas/{idea_id}.md"
1240
+ return {"entity_type": "spec", "spec": spec, "spec_file_content": spec_file,
1241
+ "parent_idea": parent_idea, "navigation": nav}
1242
+ elif entity_type == "task":
1243
+ task = api_get(f"/api/agent/tasks/{entity_id}")
1244
+ events = api_get(f"/api/agent/tasks/{entity_id}/stream")
1245
+ context = task.get("context", {}) if isinstance(task, dict) else {}
1246
+ idea_id = context.get("idea_id") if isinstance(context, dict) else None
1247
+ parent_idea = api_get(f"/api/ideas/{idea_id}") if idea_id else None
1248
+ return {"entity_type": "task", "task": task, "events": events,
1249
+ "parent_idea": parent_idea,
1250
+ "navigation": {"api": f"/api/agent/tasks/{entity_id}",
1251
+ "cli": f"cc task {entity_id}"}}
1252
+ else:
1253
+ return {"error": f"Unknown entity_type: {entity_type}. Use 'idea', 'spec', or 'task'."}
1003
1254
  case _:
1004
1255
  return {"error": f"Unknown tool: {name}"}
1005
1256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coherence-mcp-server",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "description": "Unified MCP server for the Coherence Network — 51 typed tools for AI agents to browse ideas, manage tasks, link identities, and navigate the universal graph.",
5
5
  "type": "module",
6
6
  "bin": {