coherence-mcp-server 0.5.1 → 0.5.2

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.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- AUTO-GENERATED from README.template.md. Edit the template, not this file. -->
1
2
  # coherence-mcp-server
2
3
 
3
4
  **Give your AI agent native access to every idea, spec, contributor, and value chain in the Coherence Network.**
4
5
 
5
- An [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server that exposes the full Coherence Network API as 57 typed tools — so Claude, Cursor, Windsurf, or any MCP-compatible agent can browse ideas, look up specs, trace value lineage, link identities, record contributions, execute tasks, discover resonant peers, apply project blueprints, and read repository content via direct links without writing a single API call.
6
+ An [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server that exposes the full Coherence Network API as 89 typed tools — so Claude, Cursor, Windsurf, or any MCP-compatible agent can browse ideas, look up specs, trace value lineage, link identities, record contributions, execute tasks, discover resonant peers, apply project blueprints, receive the shared agent invitation, and read repository content via direct links without writing a single API call.
6
7
 
7
8
  ```bash
8
9
  npx coherence-mcp-server
@@ -30,6 +31,7 @@ This MCP server changes that. It gives any agent a typed interface to:
30
31
  - **Direct Access** — read specs and documentation via direct repository links
31
32
  - **Ontology** — explore the Living Codex (184 universal concepts, 53 axes)
32
33
  - **Govern** — propose changes, vote on requests, and monitor network health
34
+ - **Attune** — receive the shared AI-agent invitation before acting
33
35
 
34
36
  Every tool returns structured JSON. No parsing HTML. No scraping. Just clean data.
35
37
 
@@ -76,7 +78,7 @@ Point your MCP client at `npx coherence-mcp-server` via stdio transport.
76
78
 
77
79
  ---
78
80
 
79
- ## Tools (57)
81
+ ## Tools (89)
80
82
 
81
83
  ### Ideas — the portfolio engine
82
84
 
@@ -125,6 +127,7 @@ Point your MCP client at `npx coherence-mcp-server` via stdio transport.
125
127
 
126
128
  | Tool | What it does |
127
129
  |------|-------------|
130
+ | `coherence_agent_invitation` | Receive the shared AI-agent invitation: core frequency, attunement spectrum, entry surfaces, and contribution paths. |
128
131
  | `coherence_list_tasks` | See what tasks are pending, running, or failed. |
129
132
  | `coherence_get_task` | Full task details, direction, and result. |
130
133
  | `coherence_task_next` | Claim the highest-priority pending task. |
@@ -133,6 +136,19 @@ Point your MCP client at `npx coherence-mcp-server` via stdio transport.
133
136
  | `coherence_task_seed` | Create a new task from an idea. |
134
137
  | `coherence_task_events` | View the activity event log for a task. |
135
138
 
139
+ ### Awareness Streaming — presence in and out
140
+
141
+ | Tool | What it does |
142
+ |------|-------------|
143
+ | `coherence_awareness_publish` | Publish a diagnostic awareness event from a node. |
144
+ | `coherence_awareness_stream` | Read a bounded slice from diagnostic, node-message, or task SSE streams. |
145
+ | `coherence_node_message_send` | Send a durable node-to-node or broadcast message. |
146
+ | `coherence_node_messages` | Read durable inbound messages for a node. |
147
+
148
+ Streams are intentionally bounded. `duration_seconds` defaults to 5 seconds
149
+ and is capped at 30; `max_events` defaults to 20 and is capped at 200. This
150
+ lets MCP clients sense live awareness without leaving a tool call open forever.
151
+
136
152
  ### Graph — universal navigation
137
153
 
138
154
  | Tool | What it does |
@@ -256,7 +272,7 @@ Every part of the network links to every other. Jump in wherever makes sense.
256
272
  | **Web** | Browse ideas, specs, and contributors visually | [coherencycoin.com](https://coherencycoin.com) |
257
273
  | **API** | 100+ endpoints, full OpenAPI docs, the engine behind everything | [api.coherencycoin.com/docs](https://api.coherencycoin.com/docs) |
258
274
  | **CLI** | Terminal-first access — `npm i -g coherence-cli` then `cc help` | [npm: coherence-cli](https://www.npmjs.com/package/coherence-cli) |
259
- | **MCP Server** | This package — 57 typed tools for AI agents | [npm: coherence-mcp-server](https://www.npmjs.com/package/coherence-mcp-server) |
275
+ | **MCP Server** | This package — 89 typed tools for AI agents | [npm: coherence-mcp-server](https://www.npmjs.com/package/coherence-mcp-server) |
260
276
  | **OpenClaw Skill** | Auto-triggers in any OpenClaw instance for ideas, specs, coherence | [ClawHub: coherence-network](https://clawhub.com/skills/coherence-network) |
261
277
  | **GitHub** | Source code, specs, issues, and contribution tracking | [github.com/seeker71/Coherence-Network](https://github.com/seeker71/Coherence-Network) |
262
278
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Give your AI agent native access to every idea, spec, contributor, and value chain in the Coherence Network.**
4
4
 
5
- An [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server that exposes the full Coherence Network API as 57 typed tools — so Claude, Cursor, Windsurf, or any MCP-compatible agent can browse ideas, look up specs, trace value lineage, link identities, record contributions, execute tasks, discover resonant peers, apply project blueprints, and read repository content via direct links without writing a single API call.
5
+ An [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server that exposes the full Coherence Network API as 89 typed tools — so Claude, Cursor, Windsurf, or any MCP-compatible agent can browse ideas, look up specs, trace value lineage, link identities, record contributions, execute tasks, discover resonant peers, apply project blueprints, receive the shared agent invitation, and read repository content via direct links without writing a single API call.
6
6
 
7
7
  ```bash
8
8
  npx coherence-mcp-server
@@ -30,6 +30,7 @@ This MCP server changes that. It gives any agent a typed interface to:
30
30
  - **Direct Access** — read specs and documentation via direct repository links
31
31
  - **Ontology** — explore the Living Codex (184 universal concepts, 53 axes)
32
32
  - **Govern** — propose changes, vote on requests, and monitor network health
33
+ - **Attune** — receive the shared AI-agent invitation before acting
33
34
 
34
35
  Every tool returns structured JSON. No parsing HTML. No scraping. Just clean data.
35
36
 
@@ -76,7 +77,7 @@ Point your MCP client at `npx coherence-mcp-server` via stdio transport.
76
77
 
77
78
  ---
78
79
 
79
- ## Tools (57)
80
+ ## Tools (89)
80
81
 
81
82
  ### Ideas — the portfolio engine
82
83
 
@@ -125,6 +126,7 @@ Point your MCP client at `npx coherence-mcp-server` via stdio transport.
125
126
 
126
127
  | Tool | What it does |
127
128
  |------|-------------|
129
+ | `coherence_agent_invitation` | Receive the shared AI-agent invitation: core frequency, attunement spectrum, entry surfaces, and contribution paths. |
128
130
  | `coherence_list_tasks` | See what tasks are pending, running, or failed. |
129
131
  | `coherence_get_task` | Full task details, direction, and result. |
130
132
  | `coherence_task_next` | Claim the highest-priority pending task. |
@@ -256,7 +258,7 @@ Every part of the network links to every other. Jump in wherever makes sense.
256
258
  | **Web** | Browse ideas, specs, and contributors visually | [coherencycoin.com](https://coherencycoin.com) |
257
259
  | **API** | 100+ endpoints, full OpenAPI docs, the engine behind everything | [api.coherencycoin.com/docs](https://api.coherencycoin.com/docs) |
258
260
  | **CLI** | Terminal-first access — `npm i -g coherence-cli` then `cc help` | [npm: coherence-cli](https://www.npmjs.com/package/coherence-cli) |
259
- | **MCP Server** | This package — 57 typed tools for AI agents | [npm: coherence-mcp-server](https://www.npmjs.com/package/coherence-mcp-server) |
261
+ | **MCP Server** | This package — 89 typed tools for AI agents | [npm: coherence-mcp-server](https://www.npmjs.com/package/coherence-mcp-server) |
260
262
  | **OpenClaw Skill** | Auto-triggers in any OpenClaw instance for ideas, specs, coherence | [ClawHub: coherence-network](https://clawhub.com/skills/coherence-network) |
261
263
  | **GitHub** | Source code, specs, issues, and contribution tracking | [github.com/seeker71/Coherence-Network](https://github.com/seeker71/Coherence-Network) |
262
264
 
@@ -13,5 +13,5 @@ Environment variables:
13
13
  COHERENCE_API_KEY API key for write operations (optional for reads)
14
14
  """
15
15
 
16
- __version__ = "0.3.1"
16
+ __version__ = "0.5.2"
17
17
  __all__ = ["__version__"]
@@ -1,6 +1,6 @@
1
1
  """Coherence Network MCP server — Python implementation.
2
2
 
3
- Exposes the Coherence Network API as 60 typed MCP tools.
3
+ Exposes the Coherence Network API as typed MCP tools.
4
4
  """
5
5
 
6
6
  from __future__ import annotations
@@ -8,7 +8,9 @@ from __future__ import annotations
8
8
  import json
9
9
  import logging
10
10
  import os
11
+ import time
11
12
  from typing import Any
13
+ from urllib.parse import quote
12
14
 
13
15
  import httpx
14
16
  from mcp.server import Server
@@ -76,6 +78,112 @@ def api_patch(path: str, body: dict[str, Any]) -> Any:
76
78
  return {"error": str(exc)}
77
79
 
78
80
 
81
+ def api_put(path: str, body: dict[str, Any]) -> Any:
82
+ url = f"{API_BASE}{path}"
83
+ try:
84
+ r = httpx.put(url, json=body, headers=_headers(), timeout=15.0)
85
+ r.raise_for_status()
86
+ return r.json()
87
+ except httpx.HTTPStatusError as exc:
88
+ try:
89
+ detail = exc.response.json().get("detail", exc.response.reason_phrase)
90
+ except Exception:
91
+ detail = exc.response.reason_phrase
92
+ return {"error": detail}
93
+ except Exception as exc:
94
+ return {"error": str(exc)}
95
+
96
+
97
+ def decode_sse_events(lines: list[str]) -> list[dict[str, Any]]:
98
+ """Decode SSE data lines into JSON objects, preserving raw payloads."""
99
+ events: list[dict[str, Any]] = []
100
+ data_lines: list[str] = []
101
+
102
+ def flush() -> None:
103
+ if not data_lines:
104
+ return
105
+ payload = "\n".join(data_lines).strip()
106
+ data_lines.clear()
107
+ if not payload:
108
+ return
109
+ try:
110
+ parsed = json.loads(payload)
111
+ events.append(parsed if isinstance(parsed, dict) else {"data": parsed})
112
+ except json.JSONDecodeError:
113
+ events.append({"raw": payload})
114
+
115
+ for raw_line in lines:
116
+ line = raw_line.rstrip("\r\n")
117
+ if not line:
118
+ flush()
119
+ continue
120
+ if line.startswith(":"):
121
+ continue
122
+ if line.startswith("data:"):
123
+ data_lines.append(line[5:].lstrip())
124
+ flush()
125
+ return events
126
+
127
+
128
+ def api_sse(
129
+ path: str,
130
+ params: dict[str, Any] | None = None,
131
+ *,
132
+ duration_seconds: float = 5.0,
133
+ max_events: int = 20,
134
+ ) -> dict[str, Any]:
135
+ """Read a bounded slice from an SSE endpoint and return parsed events."""
136
+ url = f"{API_BASE}{path}"
137
+ filtered = {k: v for k, v in (params or {}).items() if v is not None}
138
+ deadline = time.monotonic() + max(0.1, min(float(duration_seconds), 30.0))
139
+ read_timeout = max(1.0, min(float(duration_seconds) + 1.0, 10.0))
140
+ event_limit = max(1, min(int(max_events), 200))
141
+ lines: list[str] = []
142
+ events: list[dict[str, Any]] = []
143
+
144
+ try:
145
+ with httpx.stream(
146
+ "GET",
147
+ url,
148
+ params=filtered,
149
+ headers={**_headers(), "Accept": "text/event-stream"},
150
+ timeout=httpx.Timeout(connect=5.0, read=read_timeout, write=5.0, pool=5.0),
151
+ ) as response:
152
+ response.raise_for_status()
153
+ for line in response.iter_lines():
154
+ lines.append(line)
155
+ if line == "":
156
+ events = decode_sse_events(lines)
157
+ if len(events) >= event_limit:
158
+ break
159
+ if time.monotonic() >= deadline:
160
+ break
161
+ if not events:
162
+ events = decode_sse_events(lines)
163
+ events = events[:event_limit]
164
+ return {
165
+ "stream": path,
166
+ "duration_seconds": duration_seconds,
167
+ "max_events": event_limit,
168
+ "count": len(events),
169
+ "events": events,
170
+ }
171
+ except httpx.HTTPStatusError as exc:
172
+ return {"error": f"{exc.response.status_code} {exc.response.reason_phrase}", "stream": path}
173
+ except httpx.ReadTimeout:
174
+ events = decode_sse_events(lines)[:event_limit]
175
+ return {
176
+ "stream": path,
177
+ "duration_seconds": duration_seconds,
178
+ "max_events": event_limit,
179
+ "count": len(events),
180
+ "events": events,
181
+ "ended_by": "read_timeout",
182
+ }
183
+ except Exception as exc:
184
+ return {"error": str(exc), "stream": path}
185
+
186
+
79
187
  # ---------------------------------------------------------------------------
80
188
  # Tool definitions
81
189
  # ---------------------------------------------------------------------------
@@ -256,12 +364,75 @@ TOOLS: list[Tool] = [
256
364
  "properties": {"window_days": {"type": "number", "default": 30}},
257
365
  },
258
366
  ),
367
+ Tool(
368
+ name="coherence_agent_invitation",
369
+ description="Receive the shared AI-agent invitation: core frequency, attunement spectrum, entry surfaces, and contribution paths.",
370
+ inputSchema={"type": "object", "properties": {}},
371
+ ),
259
372
  # Federation
260
373
  Tool(
261
374
  name="coherence_list_federation_nodes",
262
375
  description="List federated nodes and their capabilities.",
263
376
  inputSchema={"type": "object", "properties": {}},
264
377
  ),
378
+ Tool(
379
+ name="coherence_awareness_publish",
380
+ description="Publish a diagnostic awareness event from a federation node. Delivered to active diagnostic stream subscribers.",
381
+ inputSchema={
382
+ "type": "object",
383
+ "required": ["node_id", "event_type"],
384
+ "properties": {
385
+ "node_id": {"type": "string", "description": "Publishing node ID"},
386
+ "event_type": {"type": "string", "description": "Awareness event kind, e.g. heartbeat, reasoning, tool_call"},
387
+ "message": {"type": "string", "description": "Short human-readable signal"},
388
+ "data": {"type": "object", "description": "Optional structured event payload"},
389
+ },
390
+ },
391
+ ),
392
+ Tool(
393
+ name="coherence_awareness_stream",
394
+ description="Read a bounded slice from diagnostic, node-message, or task-event SSE streams.",
395
+ inputSchema={
396
+ "type": "object",
397
+ "required": ["stream_type"],
398
+ "properties": {
399
+ "stream_type": {"type": "string", "description": "diagnostics, node, or task"},
400
+ "node_id": {"type": "string", "description": "Node ID for diagnostics/node streams; use '*' for all diagnostics"},
401
+ "task_id": {"type": "string", "description": "Task ID for task event streams"},
402
+ "duration_seconds": {"type": "number", "description": "Max seconds to hold stream open (default 5, max 30)", "default": 5},
403
+ "max_events": {"type": "number", "description": "Max parsed events to return (default 20, max 200)", "default": 20},
404
+ },
405
+ },
406
+ ),
407
+ Tool(
408
+ name="coherence_node_message_send",
409
+ description="Send a federation node message. Use to stream awareness out as durable node-to-node text or command payloads.",
410
+ inputSchema={
411
+ "type": "object",
412
+ "required": ["from_node_id", "text"],
413
+ "properties": {
414
+ "from_node_id": {"type": "string", "description": "Sender node ID"},
415
+ "to_node_id": {"type": "string", "description": "Recipient node ID; omit for broadcast"},
416
+ "type": {"type": "string", "description": "Message type, e.g. text, command, command_response", "default": "text"},
417
+ "text": {"type": "string", "description": "Message text"},
418
+ "payload": {"type": "object", "description": "Optional structured payload"},
419
+ },
420
+ },
421
+ ),
422
+ Tool(
423
+ name="coherence_node_messages",
424
+ description="Read federation node messages for a node. This is the durable inbound awareness channel.",
425
+ inputSchema={
426
+ "type": "object",
427
+ "required": ["node_id"],
428
+ "properties": {
429
+ "node_id": {"type": "string", "description": "Recipient node ID"},
430
+ "since": {"type": "string", "description": "Optional ISO timestamp lower bound"},
431
+ "unread_only": {"type": "boolean", "description": "Only unread messages (default true)", "default": True},
432
+ "limit": {"type": "number", "description": "Max messages to return (default 50)", "default": 50},
433
+ },
434
+ },
435
+ ),
265
436
  # Tasks (Agent Work Protocol)
266
437
  Tool(
267
438
  name="coherence_list_tasks",
@@ -295,6 +466,7 @@ TOOLS: list[Tool] = [
295
466
  "type": "object",
296
467
  "properties": {
297
468
  "worker_id": {"type": "string", "description": "Identity of the agent/node claiming the task (defaults to 'mcp-agent')"},
469
+ "workspace_id": {"type": "string", "description": "Optional workspace to filter pending tasks by."},
298
470
  },
299
471
  },
300
472
  ),
@@ -307,6 +479,7 @@ TOOLS: list[Tool] = [
307
479
  "properties": {
308
480
  "task_id": {"type": "string", "description": "The task ID"},
309
481
  "worker_id": {"type": "string", "description": "Identity of the agent/node (defaults to 'mcp-agent')"},
482
+ "workspace_id": {"type": "string", "description": "Optional workspace context for the claim."},
310
483
  },
311
484
  },
312
485
  ),
@@ -333,6 +506,7 @@ TOOLS: list[Tool] = [
333
506
  "idea_id": {"type": "string", "description": "Target idea ID"},
334
507
  "task_type": {"type": "string", "description": "Type of task: spec, test, impl, review (default: spec)", "default": "spec"},
335
508
  "direction": {"type": "string", "description": "Optional custom instruction for the task"},
509
+ "workspace_id": {"type": "string", "description": "Optional workspace to scope this task to."},
336
510
  },
337
511
  },
338
512
  ),
@@ -715,7 +889,7 @@ TOOLS: list[Tool] = [
715
889
  "type": "object",
716
890
  "required": ["path"],
717
891
  "properties": {
718
- "path": {"type": "string", "description": "Relative path from repo root (e.g. 'specs/169-procedural-memory.md')"},
892
+ "path": {"type": "string", "description": "Relative path from repo root (e.g. 'specs/smart-reap.md')"},
719
893
  },
720
894
  },
721
895
  ),
@@ -823,6 +997,235 @@ TOOLS: list[Tool] = [
823
997
  },
824
998
  },
825
999
  ),
1000
+ # Pipeline Policies
1001
+ Tool(
1002
+ name="coherence_pipeline_policies",
1003
+ description="View or update pipeline policies (phase_chain, max_retries, failure_patterns, provider_per_phase, etc). Use action='list' to see all, action='get' with key, action='set' with key+value.",
1004
+ inputSchema={
1005
+ "type": "object",
1006
+ "properties": {
1007
+ "action": {"type": "string", "enum": ["list", "get", "set"], "description": "Operation to perform"},
1008
+ "key": {"type": "string", "description": "Policy key (for get/set)"},
1009
+ "value": {"description": "Policy value to set (any JSON type, for set action)"},
1010
+ },
1011
+ "required": ["action"],
1012
+ },
1013
+ ),
1014
+ # Workspace Tasks
1015
+ Tool(
1016
+ name="coherence_workspace_tasks",
1017
+ description="List tasks for a specific workspace, seed a full pipeline (spec->impl->test->review->deploy->verify), or get workspace status.",
1018
+ inputSchema={
1019
+ "type": "object",
1020
+ "properties": {
1021
+ "workspace_id": {"type": "string", "description": "Workspace ID to scope operations to"},
1022
+ "action": {"type": "string", "enum": ["list", "seed_pipeline", "status"], "description": "Action to perform"},
1023
+ "idea_id": {"type": "string", "description": "Idea ID (for seed_pipeline action)"},
1024
+ "start_phase": {"type": "string", "description": "Starting phase (default: spec)", "default": "spec"},
1025
+ },
1026
+ "required": ["workspace_id", "action"],
1027
+ },
1028
+ ),
1029
+ # Team Membership
1030
+ Tool(
1031
+ name="coherence_list_workspace_members",
1032
+ description="List members of a workspace with their roles and join dates.",
1033
+ inputSchema={
1034
+ "type": "object",
1035
+ "required": ["workspace_id"],
1036
+ "properties": {
1037
+ "workspace_id": {"type": "string", "description": "Workspace slug"},
1038
+ },
1039
+ },
1040
+ ),
1041
+ Tool(
1042
+ name="coherence_invite_member",
1043
+ description="Invite a contributor to a workspace with an optional role.",
1044
+ inputSchema={
1045
+ "type": "object",
1046
+ "required": ["workspace_id", "contributor_id"],
1047
+ "properties": {
1048
+ "workspace_id": {"type": "string", "description": "Workspace slug"},
1049
+ "contributor_id": {"type": "string", "description": "Contributor to invite"},
1050
+ "role": {"type": "string", "description": "Role: owner, admin, member (default: member)", "default": "member"},
1051
+ },
1052
+ },
1053
+ ),
1054
+ Tool(
1055
+ name="coherence_list_my_workspaces",
1056
+ description="List workspaces that a contributor belongs to.",
1057
+ inputSchema={
1058
+ "type": "object",
1059
+ "required": ["contributor_id"],
1060
+ "properties": {
1061
+ "contributor_id": {"type": "string", "description": "Contributor ID"},
1062
+ },
1063
+ },
1064
+ ),
1065
+ # Messaging
1066
+ Tool(
1067
+ name="coherence_send_message",
1068
+ description="Send a message to a contributor (DM) or a workspace channel.",
1069
+ inputSchema={
1070
+ "type": "object",
1071
+ "required": ["from_contributor_id", "body"],
1072
+ "properties": {
1073
+ "from_contributor_id": {"type": "string", "description": "Sender contributor ID"},
1074
+ "to_contributor_id": {"type": "string", "description": "Recipient contributor ID (for DM)"},
1075
+ "to_workspace_id": {"type": "string", "description": "Target workspace ID (for workspace message)"},
1076
+ "subject": {"type": "string", "description": "Optional message subject"},
1077
+ "body": {"type": "string", "description": "Message body text"},
1078
+ },
1079
+ },
1080
+ ),
1081
+ Tool(
1082
+ name="coherence_get_inbox",
1083
+ description="Get inbox messages for a contributor with optional filters.",
1084
+ inputSchema={
1085
+ "type": "object",
1086
+ "required": ["contributor_id"],
1087
+ "properties": {
1088
+ "contributor_id": {"type": "string", "description": "Contributor ID"},
1089
+ "limit": {"type": "number", "description": "Max messages to return (default 20)", "default": 20},
1090
+ "unread_only": {"type": "boolean", "description": "Only return unread messages", "default": False},
1091
+ },
1092
+ },
1093
+ ),
1094
+ # Activity
1095
+ Tool(
1096
+ name="coherence_get_workspace_activity",
1097
+ description="Get the activity feed (event timeline) for a workspace.",
1098
+ inputSchema={
1099
+ "type": "object",
1100
+ "required": ["workspace_id"],
1101
+ "properties": {
1102
+ "workspace_id": {"type": "string", "description": "Workspace slug"},
1103
+ "limit": {"type": "number", "description": "Max events to return (default 20)", "default": 20},
1104
+ },
1105
+ },
1106
+ ),
1107
+ # Projects
1108
+ Tool(
1109
+ name="coherence_list_workspace_projects",
1110
+ description="List projects in a workspace for organizing ideas into groups.",
1111
+ inputSchema={
1112
+ "type": "object",
1113
+ "required": ["workspace_id"],
1114
+ "properties": {
1115
+ "workspace_id": {"type": "string", "description": "Workspace slug"},
1116
+ },
1117
+ },
1118
+ ),
1119
+ Tool(
1120
+ name="coherence_create_workspace_project",
1121
+ description="Create a new project within a workspace to group related ideas.",
1122
+ inputSchema={
1123
+ "type": "object",
1124
+ "required": ["workspace_id", "name"],
1125
+ "properties": {
1126
+ "workspace_id": {"type": "string", "description": "Workspace slug"},
1127
+ "name": {"type": "string", "description": "Project name"},
1128
+ "description": {"type": "string", "description": "Project description"},
1129
+ },
1130
+ },
1131
+ ),
1132
+ # ── Discovery / Living Network ──
1133
+ Tool(
1134
+ name="coherence_discover",
1135
+ description="Serendipity feed — personalized discovery of resonant ideas, peers, cross-domain connections, and news. Returns what resonates with who you are.",
1136
+ inputSchema={
1137
+ "type": "object",
1138
+ "properties": {
1139
+ "contributor_id": {"type": "string", "description": "Contributor to discover for (default: general feed)"},
1140
+ "limit": {"type": "integer", "description": "Max items (default 20)"},
1141
+ },
1142
+ },
1143
+ ),
1144
+ Tool(
1145
+ name="coherence_constellation",
1146
+ description="Network visualization data — nodes (ideas, contributors, concepts) and edges with positions for galaxy/constellation rendering.",
1147
+ inputSchema={
1148
+ "type": "object",
1149
+ "properties": {
1150
+ "max_nodes": {"type": "integer", "description": "Max nodes to return (default 80)"},
1151
+ "workspace_id": {"type": "string", "description": "Workspace to visualize (default: coherence-network)"},
1152
+ },
1153
+ },
1154
+ ),
1155
+ Tool(
1156
+ name="coherence_vitality",
1157
+ description="Workspace health as living-system signals — diversity index, resonance density, flow rate, breath rhythm, connection strength, activity pulse.",
1158
+ inputSchema={
1159
+ "type": "object",
1160
+ "properties": {
1161
+ "workspace_id": {"type": "string", "description": "Workspace ID (default: coherence-network)"},
1162
+ },
1163
+ },
1164
+ ),
1165
+ Tool(
1166
+ name="coherence_resonate",
1167
+ description="Find ideas that share deep structural patterns with a given idea — cross-domain resonance via harmonic matching.",
1168
+ inputSchema={
1169
+ "type": "object",
1170
+ "required": ["idea_id"],
1171
+ "properties": {
1172
+ "idea_id": {"type": "string", "description": "Idea to find resonances for"},
1173
+ "limit": {"type": "integer", "description": "Max matches (default 10)"},
1174
+ },
1175
+ },
1176
+ ),
1177
+ # ── Super-Idea Coverage: News, Federation, Beliefs, CC, Governance ──
1178
+ Tool(
1179
+ name="coherence_news_feed",
1180
+ description="Get the latest news items from configured RSS sources.",
1181
+ inputSchema={
1182
+ "type": "object",
1183
+ "properties": {
1184
+ "limit": {"type": "number", "description": "Max items to return (default 20)", "default": 20},
1185
+ },
1186
+ },
1187
+ ),
1188
+ Tool(
1189
+ name="coherence_news_resonance",
1190
+ description="Get news items matched to ideas with resonance scores.",
1191
+ inputSchema={
1192
+ "type": "object",
1193
+ "properties": {
1194
+ "top_n": {"type": "number", "description": "Top N matches (default 5)", "default": 5},
1195
+ },
1196
+ },
1197
+ ),
1198
+ Tool(
1199
+ name="coherence_federation_nodes",
1200
+ description="List federation nodes with hostname, OS, providers, and last heartbeat.",
1201
+ inputSchema={"type": "object", "properties": {}},
1202
+ ),
1203
+ Tool(
1204
+ name="coherence_belief_profile",
1205
+ description="Get the belief profile for a contributor — worldview axes and top concepts.",
1206
+ inputSchema={
1207
+ "type": "object",
1208
+ "required": ["contributor_id"],
1209
+ "properties": {
1210
+ "contributor_id": {"type": "string", "description": "Contributor ID"},
1211
+ },
1212
+ },
1213
+ ),
1214
+ Tool(
1215
+ name="coherence_cc_supply",
1216
+ description="Get CC token economics — total minted, outstanding, and coherence score.",
1217
+ inputSchema={"type": "object", "properties": {}},
1218
+ ),
1219
+ Tool(
1220
+ name="coherence_governance_requests",
1221
+ description="List governance change requests with status and proposer info.",
1222
+ inputSchema={
1223
+ "type": "object",
1224
+ "properties": {
1225
+ "limit": {"type": "number", "description": "Max requests to return (default 20)", "default": 20},
1226
+ },
1227
+ },
1228
+ ),
826
1229
  ]
827
1230
 
828
1231
  TOOL_MAP: dict[str, Tool] = {t.name: t for t in TOOLS}
@@ -881,10 +1284,8 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
881
1284
  "display_name": args["provider_id"],
882
1285
  })
883
1286
  case "coherence_lookup_identity":
884
- from urllib.parse import quote
885
1287
  return api_get(f"/api/identity/lookup/{quote(args['provider'])}/{quote(args['provider_id'])}")
886
1288
  case "coherence_get_identities":
887
- from urllib.parse import quote
888
1289
  return api_get(f"/api/identity/{quote(args['contributor_id'])}")
889
1290
  # Contributions
890
1291
  case "coherence_record_contribution":
@@ -899,7 +1300,6 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
899
1300
  }.items() if v is not None
900
1301
  })
901
1302
  case "coherence_contributor_ledger":
902
- from urllib.parse import quote
903
1303
  return api_get(f"/api/contributions/ledger/{quote(args['contributor_id'])}")
904
1304
  # Status
905
1305
  case "coherence_status":
@@ -913,11 +1313,65 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
913
1313
  }
914
1314
  case "coherence_friction_report":
915
1315
  return api_get("/api/friction/report", {"window_days": args.get("window_days", 30)})
1316
+ case "coherence_agent_invitation":
1317
+ return api_get("/api/agent/invitation")
916
1318
  # Federation
917
1319
  case "coherence_list_federation_nodes":
918
1320
  nodes = api_get("/api/federation/nodes")
919
1321
  caps = api_get("/api/federation/nodes/capabilities")
920
1322
  return {"nodes": nodes, "capabilities": caps}
1323
+ case "coherence_awareness_publish":
1324
+ body = {
1325
+ "event_type": args["event_type"],
1326
+ "message": args.get("message", ""),
1327
+ "data": args.get("data", {}),
1328
+ "source": "mcp",
1329
+ }
1330
+ return api_post(f"/api/federation/nodes/{quote(args['node_id'], safe='')}/diag", body)
1331
+ case "coherence_awareness_stream":
1332
+ stream_type = (args.get("stream_type") or "").strip().lower()
1333
+ duration_seconds = args.get("duration_seconds", 5)
1334
+ max_events = args.get("max_events", 20)
1335
+ if stream_type == "diagnostics":
1336
+ node_id = args.get("node_id") or "*"
1337
+ return api_sse(
1338
+ f"/api/federation/nodes/{quote(node_id, safe='*')}/diag/stream",
1339
+ duration_seconds=duration_seconds,
1340
+ max_events=max_events,
1341
+ )
1342
+ if stream_type == "node":
1343
+ node_id = args.get("node_id")
1344
+ if not node_id:
1345
+ return {"error": "node_id is required for node streams"}
1346
+ return api_sse(
1347
+ f"/api/federation/nodes/{quote(node_id, safe='')}/stream",
1348
+ duration_seconds=duration_seconds,
1349
+ max_events=max_events,
1350
+ )
1351
+ if stream_type == "task":
1352
+ task_id = args.get("task_id")
1353
+ if not task_id:
1354
+ return {"error": "task_id is required for task streams"}
1355
+ return api_sse(
1356
+ f"/api/agent/tasks/{quote(task_id, safe='')}/events",
1357
+ duration_seconds=duration_seconds,
1358
+ max_events=max_events,
1359
+ )
1360
+ return {"error": "stream_type must be one of: diagnostics, node, task"}
1361
+ case "coherence_node_message_send":
1362
+ body = {
1363
+ "to_node": args.get("to_node_id"),
1364
+ "type": args.get("type", "text"),
1365
+ "text": args["text"],
1366
+ "payload": args.get("payload", {}),
1367
+ }
1368
+ return api_post(f"/api/federation/nodes/{quote(args['from_node_id'], safe='')}/messages", body)
1369
+ case "coherence_node_messages":
1370
+ return api_get(f"/api/federation/nodes/{quote(args['node_id'], safe='')}/messages", {
1371
+ "since": args.get("since"),
1372
+ "unread_only": args.get("unread_only", True),
1373
+ "limit": args.get("limit", 50),
1374
+ })
921
1375
  # Tasks
922
1376
  case "coherence_list_tasks":
923
1377
  params = {
@@ -933,7 +1387,10 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
933
1387
  return api_get(f"/api/agent/tasks/{args['task_id']}")
934
1388
  case "coherence_task_next":
935
1389
  # Claim next available pending task
936
- data = api_get("/api/agent/tasks", {"status": "pending", "limit": 1})
1390
+ params: dict[str, Any] = {"status": "pending", "limit": 1}
1391
+ if args.get("workspace_id"):
1392
+ params["workspace_id"] = args["workspace_id"]
1393
+ data = api_get("/api/agent/tasks", params)
937
1394
  tasks = data.get("tasks", []) if isinstance(data, dict) else []
938
1395
  if not tasks:
939
1396
  return {"error": "No pending tasks available"}
@@ -959,14 +1416,17 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
959
1416
  idea_name = idea.get("name", "Unknown Idea") if isinstance(idea, dict) else "Unknown Idea"
960
1417
  task_type = args.get("task_type", "spec")
961
1418
  direction = args.get("direction") or f"{task_type} for '{idea_name}' ({idea_id})"
1419
+ context: dict[str, Any] = {
1420
+ "idea_id": idea_id,
1421
+ "idea_name": idea_name,
1422
+ "seeded_by": "mcp-agent",
1423
+ }
1424
+ if args.get("workspace_id"):
1425
+ context["workspace_id"] = args["workspace_id"]
962
1426
  return api_post("/api/agent/tasks", {
963
1427
  "task_type": task_type,
964
1428
  "direction": direction,
965
- "context": {
966
- "idea_id": idea_id,
967
- "idea_name": idea_name,
968
- "seeded_by": "mcp-agent",
969
- },
1429
+ "context": context,
970
1430
  })
971
1431
  case "coherence_task_events":
972
1432
  return api_get(f"/api/agent/tasks/{args['task_id']}/stream")
@@ -1230,7 +1690,7 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1230
1690
  "navigation": {"idea_file": f"ideas/{entity_id}.md",
1231
1691
  "spec_files": [f"specs/{s}.md" for s in spec_ids],
1232
1692
  "api": f"/api/ideas/{entity_id}",
1233
- "cli": f"cc idea {entity_id}"}}
1693
+ "cli": f"coh idea {entity_id}"}}
1234
1694
  elif entity_type == "spec":
1235
1695
  spec = api_get(f"/api/spec-registry/{entity_id}")
1236
1696
  spec_file = api_get("/api/content/file", {"path": f"specs/{entity_id}.md"})
@@ -1238,7 +1698,7 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1238
1698
  parent_idea = api_get(f"/api/ideas/{idea_id}") if idea_id else None
1239
1699
  nav: dict[str, Any] = {"spec_file": f"specs/{entity_id}.md",
1240
1700
  "api": f"/api/spec-registry/{entity_id}",
1241
- "cli": f"cc spec {entity_id}"}
1701
+ "cli": f"coh spec {entity_id}"}
1242
1702
  if idea_id:
1243
1703
  nav["idea_file"] = f"ideas/{idea_id}.md"
1244
1704
  return {"entity_type": "spec", "spec": spec, "spec_file_content": spec_file,
@@ -1252,9 +1712,135 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1252
1712
  return {"entity_type": "task", "task": task, "events": events,
1253
1713
  "parent_idea": parent_idea,
1254
1714
  "navigation": {"api": f"/api/agent/tasks/{entity_id}",
1255
- "cli": f"cc task {entity_id}"}}
1715
+ "cli": f"coh task {entity_id}"}}
1256
1716
  else:
1257
1717
  return {"error": f"Unknown entity_type: {entity_type}. Use 'idea', 'spec', or 'task'."}
1718
+ # Pipeline Policies
1719
+ case "coherence_pipeline_policies":
1720
+ action = (args.get("action") or "list").strip()
1721
+ if action == "list":
1722
+ return api_get("/api/pipeline/policies")
1723
+ elif action == "get":
1724
+ key = args.get("key", "")
1725
+ return api_get(f"/api/pipeline/policies/{key}")
1726
+ elif action == "set":
1727
+ key = args.get("key", "")
1728
+ value = args.get("value")
1729
+ return api_put(f"/api/pipeline/policies/{key}", {
1730
+ "value": value,
1731
+ "updated_by": "mcp-agent",
1732
+ })
1733
+ else:
1734
+ return {"error": f"Unknown action: {action}. Use 'list', 'get', or 'set'."}
1735
+ # Workspace Tasks
1736
+ case "coherence_workspace_tasks":
1737
+ ws_id = args["workspace_id"]
1738
+ action = args["action"]
1739
+ if action == "list":
1740
+ return api_get(f"/api/agent/tasks", {"workspace_id": ws_id, "limit": 50})
1741
+ elif action == "seed_pipeline":
1742
+ idea_id = args.get("idea_id", "")
1743
+ start_phase = args.get("start_phase", "spec")
1744
+ return api_post("/api/agent/tasks", {
1745
+ "direction": f"Implement idea {idea_id} starting from {start_phase} phase",
1746
+ "task_type": start_phase,
1747
+ "context": {
1748
+ "idea_id": idea_id,
1749
+ "workspace_id": ws_id,
1750
+ "executor": "federation",
1751
+ },
1752
+ })
1753
+ elif action == "status":
1754
+ tasks_data = api_get(f"/api/agent/tasks", {"workspace_id": ws_id, "limit": 200})
1755
+ items = (
1756
+ tasks_data.get("items", [])
1757
+ if isinstance(tasks_data, dict)
1758
+ else tasks_data if isinstance(tasks_data, list)
1759
+ else []
1760
+ )
1761
+ status_counts: dict[str, int] = {}
1762
+ for t in items:
1763
+ s = t.get("status", "unknown") if isinstance(t, dict) else "unknown"
1764
+ status_counts[s] = status_counts.get(s, 0) + 1
1765
+ return {
1766
+ "workspace_id": ws_id,
1767
+ "total_tasks": len(items),
1768
+ "by_status": status_counts,
1769
+ }
1770
+ else:
1771
+ return {"error": f"Unknown action: {action}. Use 'list', 'seed_pipeline', or 'status'."}
1772
+ # Team Membership
1773
+ case "coherence_list_workspace_members":
1774
+ return api_get(f"/api/workspaces/{args['workspace_id']}/members")
1775
+ case "coherence_invite_member":
1776
+ return api_post(f"/api/workspaces/{args['workspace_id']}/invite", {
1777
+ "contributor_id": args["contributor_id"],
1778
+ "role": args.get("role", "member"),
1779
+ })
1780
+ case "coherence_list_my_workspaces":
1781
+ return api_get(f"/api/contributors/{args['contributor_id']}/workspaces")
1782
+ # Messaging
1783
+ case "coherence_send_message":
1784
+ body: dict[str, Any] = {
1785
+ "from_contributor_id": args["from_contributor_id"],
1786
+ "body": args["body"],
1787
+ }
1788
+ if args.get("to_contributor_id"):
1789
+ body["to_contributor_id"] = args["to_contributor_id"]
1790
+ if args.get("to_workspace_id"):
1791
+ body["to_workspace_id"] = args["to_workspace_id"]
1792
+ if args.get("subject"):
1793
+ body["subject"] = args["subject"]
1794
+ return api_post("/api/messages", body)
1795
+ case "coherence_get_inbox":
1796
+ return api_get(f"/api/messages/inbox/{args['contributor_id']}", {
1797
+ "limit": args.get("limit", 20),
1798
+ "unread_only": args.get("unread_only", False),
1799
+ })
1800
+ # Activity
1801
+ case "coherence_get_workspace_activity":
1802
+ return api_get(f"/api/workspaces/{args['workspace_id']}/activity", {
1803
+ "limit": args.get("limit", 20),
1804
+ })
1805
+ # Projects
1806
+ case "coherence_list_workspace_projects":
1807
+ return api_get(f"/api/workspaces/{args['workspace_id']}/projects")
1808
+ case "coherence_create_workspace_project":
1809
+ return api_post(f"/api/workspaces/{args['workspace_id']}/projects", {
1810
+ "name": args["name"],
1811
+ "description": args.get("description", ""),
1812
+ "workspace_id": args["workspace_id"],
1813
+ })
1814
+ # Discovery / Living Network
1815
+ case "coherence_discover":
1816
+ cid = args.get("contributor_id", "default-contributor")
1817
+ limit: int = args.get("limit", 20)
1818
+ return api_get(f"/api/discover/{cid}", {"limit": limit})
1819
+ case "coherence_constellation":
1820
+ return api_get("/api/constellation", {
1821
+ "max_nodes": args.get("max_nodes", 80),
1822
+ "workspace_id": args.get("workspace_id", "coherence-network"),
1823
+ })
1824
+ case "coherence_vitality":
1825
+ ws = args.get("workspace_id", "coherence-network")
1826
+ return api_get(f"/api/workspaces/{ws}/vitality")
1827
+ case "coherence_resonate":
1828
+ return api_get(f"/api/resonance/ideas/{args['idea_id']}", {
1829
+ "limit": args.get("limit", 10),
1830
+ })
1831
+ # Super-Idea Coverage
1832
+ case "coherence_news_feed":
1833
+ return api_get("/api/news/feed", {"limit": args.get("limit", 20)})
1834
+ case "coherence_news_resonance":
1835
+ return api_get("/api/news/resonance", {"top_n": args.get("top_n", 5)})
1836
+ case "coherence_federation_nodes":
1837
+ return api_get("/api/federation/nodes")
1838
+ case "coherence_belief_profile":
1839
+ return api_get(f"/api/beliefs/{args['contributor_id']}")
1840
+ case "coherence_cc_supply":
1841
+ return api_get("/api/cc/supply")
1842
+ case "coherence_governance_requests":
1843
+ return api_get("/api/governance/change-requests", {"limit": args.get("limit", 20)})
1258
1844
  case _:
1259
1845
  return {"error": f"Unknown tool: {name}"}
1260
1846
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "coherence-mcp-server",
3
- "version": "0.5.1",
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.",
3
+ "version": "0.5.2",
4
+ "description": "Unified MCP server for the Coherence Network — 89 typed tools for AI agents to browse ideas, receive the shared invitation, manage tasks, link identities, and navigate the universal graph.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "coherence-mcp-server": "index.mjs"
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "coherence-mcp-server"
7
- version = "0.4.0"
7
+ version = "0.5.2"
8
8
  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."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"