coherence-mcp-server 0.5.0 → 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",
@@ -273,6 +444,7 @@ TOOLS: list[Tool] = [
273
444
  "task_type": {"type": "string", "description": "Filter by type: spec, test, impl, review, code-review"},
274
445
  "limit": {"type": "number", "description": "Max tasks to return (default 20)", "default": 20},
275
446
  "offset": {"type": "number", "description": "Pagination offset", "default": 0},
447
+ "workspace_id": {"type": "string", "description": "Optional workspace to filter by (via each task's linked idea)."},
276
448
  },
277
449
  },
278
450
  ),
@@ -294,6 +466,7 @@ TOOLS: list[Tool] = [
294
466
  "type": "object",
295
467
  "properties": {
296
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."},
297
470
  },
298
471
  },
299
472
  ),
@@ -306,6 +479,7 @@ TOOLS: list[Tool] = [
306
479
  "properties": {
307
480
  "task_id": {"type": "string", "description": "The task ID"},
308
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."},
309
483
  },
310
484
  },
311
485
  ),
@@ -332,6 +506,7 @@ TOOLS: list[Tool] = [
332
506
  "idea_id": {"type": "string", "description": "Target idea ID"},
333
507
  "task_type": {"type": "string", "description": "Type of task: spec, test, impl, review (default: spec)", "default": "spec"},
334
508
  "direction": {"type": "string", "description": "Optional custom instruction for the task"},
509
+ "workspace_id": {"type": "string", "description": "Optional workspace to scope this task to."},
335
510
  },
336
511
  },
337
512
  ),
@@ -714,7 +889,7 @@ TOOLS: list[Tool] = [
714
889
  "type": "object",
715
890
  "required": ["path"],
716
891
  "properties": {
717
- "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')"},
718
893
  },
719
894
  },
720
895
  ),
@@ -822,6 +997,235 @@ TOOLS: list[Tool] = [
822
997
  },
823
998
  },
824
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
+ ),
825
1229
  ]
826
1230
 
827
1231
  TOOL_MAP: dict[str, Tool] = {t.name: t for t in TOOLS}
@@ -880,10 +1284,8 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
880
1284
  "display_name": args["provider_id"],
881
1285
  })
882
1286
  case "coherence_lookup_identity":
883
- from urllib.parse import quote
884
1287
  return api_get(f"/api/identity/lookup/{quote(args['provider'])}/{quote(args['provider_id'])}")
885
1288
  case "coherence_get_identities":
886
- from urllib.parse import quote
887
1289
  return api_get(f"/api/identity/{quote(args['contributor_id'])}")
888
1290
  # Contributions
889
1291
  case "coherence_record_contribution":
@@ -898,7 +1300,6 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
898
1300
  }.items() if v is not None
899
1301
  })
900
1302
  case "coherence_contributor_ledger":
901
- from urllib.parse import quote
902
1303
  return api_get(f"/api/contributions/ledger/{quote(args['contributor_id'])}")
903
1304
  # Status
904
1305
  case "coherence_status":
@@ -912,24 +1313,84 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
912
1313
  }
913
1314
  case "coherence_friction_report":
914
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")
915
1318
  # Federation
916
1319
  case "coherence_list_federation_nodes":
917
1320
  nodes = api_get("/api/federation/nodes")
918
1321
  caps = api_get("/api/federation/nodes/capabilities")
919
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
+ })
920
1375
  # Tasks
921
1376
  case "coherence_list_tasks":
922
- return api_get("/api/agent/tasks", {
1377
+ params = {
923
1378
  "status": args.get("status"),
924
1379
  "task_type": args.get("task_type"),
925
1380
  "limit": args.get("limit", 20),
926
1381
  "offset": args.get("offset", 0),
927
- })
1382
+ }
1383
+ if args.get("workspace_id"):
1384
+ params["workspace_id"] = args["workspace_id"]
1385
+ return api_get("/api/agent/tasks", params)
928
1386
  case "coherence_get_task":
929
1387
  return api_get(f"/api/agent/tasks/{args['task_id']}")
930
1388
  case "coherence_task_next":
931
1389
  # Claim next available pending task
932
- 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)
933
1394
  tasks = data.get("tasks", []) if isinstance(data, dict) else []
934
1395
  if not tasks:
935
1396
  return {"error": "No pending tasks available"}
@@ -955,14 +1416,17 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
955
1416
  idea_name = idea.get("name", "Unknown Idea") if isinstance(idea, dict) else "Unknown Idea"
956
1417
  task_type = args.get("task_type", "spec")
957
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"]
958
1426
  return api_post("/api/agent/tasks", {
959
1427
  "task_type": task_type,
960
1428
  "direction": direction,
961
- "context": {
962
- "idea_id": idea_id,
963
- "idea_name": idea_name,
964
- "seeded_by": "mcp-agent",
965
- },
1429
+ "context": context,
966
1430
  })
967
1431
  case "coherence_task_events":
968
1432
  return api_get(f"/api/agent/tasks/{args['task_id']}/stream")
@@ -1226,7 +1690,7 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1226
1690
  "navigation": {"idea_file": f"ideas/{entity_id}.md",
1227
1691
  "spec_files": [f"specs/{s}.md" for s in spec_ids],
1228
1692
  "api": f"/api/ideas/{entity_id}",
1229
- "cli": f"cc idea {entity_id}"}}
1693
+ "cli": f"coh idea {entity_id}"}}
1230
1694
  elif entity_type == "spec":
1231
1695
  spec = api_get(f"/api/spec-registry/{entity_id}")
1232
1696
  spec_file = api_get("/api/content/file", {"path": f"specs/{entity_id}.md"})
@@ -1234,7 +1698,7 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1234
1698
  parent_idea = api_get(f"/api/ideas/{idea_id}") if idea_id else None
1235
1699
  nav: dict[str, Any] = {"spec_file": f"specs/{entity_id}.md",
1236
1700
  "api": f"/api/spec-registry/{entity_id}",
1237
- "cli": f"cc spec {entity_id}"}
1701
+ "cli": f"coh spec {entity_id}"}
1238
1702
  if idea_id:
1239
1703
  nav["idea_file"] = f"ideas/{idea_id}.md"
1240
1704
  return {"entity_type": "spec", "spec": spec, "spec_file_content": spec_file,
@@ -1248,9 +1712,135 @@ def dispatch(name: str, args: dict[str, Any]) -> Any:
1248
1712
  return {"entity_type": "task", "task": task, "events": events,
1249
1713
  "parent_idea": parent_idea,
1250
1714
  "navigation": {"api": f"/api/agent/tasks/{entity_id}",
1251
- "cli": f"cc task {entity_id}"}}
1715
+ "cli": f"coh task {entity_id}"}}
1252
1716
  else:
1253
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)})
1254
1844
  case _:
1255
1845
  return {"error": f"Unknown tool: {name}"}
1256
1846
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "coherence-mcp-server",
3
- "version": "0.5.0",
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"