Kode-Animator commited on
Commit
493263b
·
1 Parent(s): 87e9520

feat: Phase 2 Thought Engine Bridge — C-THOUGHT-BRIDGE-001

Browse files

- FastMCP SSE server with 14 AI-usable tools
- Proxies Thought Engine Node REST API for MCP clients
- Tools: onboard, start_session, add_step, fork, propose,
review_proposal, display_tree, search, list_edges, witness,
list_sessions, get_session, list_thoughts, node_identity
- Docker + requirements for HF Spaces deployment

Files changed (4) hide show
  1. .gitignore +5 -0
  2. Dockerfile +17 -0
  3. requirements.txt +3 -0
  4. server.py +377 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ .env.local
5
+ *.bak
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Thought Engine Bridge — Docker Container
2
+ # Contract: C-THOUGHT-BRIDGE-001 (Phase 2)
3
+ # Exposes the Thought Engine Node as MCP tools via SSE transport.
4
+
5
+ FROM python:3.11-slim
6
+
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+
11
+ WORKDIR /app
12
+
13
+ COPY --chown=user ./requirements.txt requirements.txt
14
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
15
+
16
+ COPY --chown=user . /app
17
+ CMD ["python", "server.py"]
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ mcp[cli]
2
+ httpx>=0.28.1
3
+ uvicorn[standard]
server.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ==========================================================================
3
+ 🌉 Thought Engine Bridge — MCP Tool Server (SSE Transport)
4
+ ==========================================================================
5
+ Contract: C-THOUGHT-BRIDGE-001 (Phase 2)
6
+ Node: https://kode-animator-thought-engine-node.hf.space
7
+ Stack: FastMCP (SSE) + httpx → Thought Engine Node REST API
8
+
9
+ Exposes the Thought Engine Node's sovereign REST endpoints as AI-usable
10
+ MCP tools. Any MCP-compatible client (ChatGPT, Claude, Cline, etc.)
11
+ can create sessions, add thoughts, fork branches, submit proposals,
12
+ and visualize reasoning trees through this bridge.
13
+ ==========================================================================
14
+ """
15
+
16
+ import os
17
+ import json
18
+ import httpx
19
+ from typing import Optional
20
+ from mcp.server.fastmcp import FastMCP
21
+
22
+ # ── Configuration ──────────────────────────────────────────────
23
+ NODE_URL = os.getenv(
24
+ "THOUGHT_ENGINE_NODE_URL",
25
+ "https://kode-animator-thought-engine-node.hf.space"
26
+ )
27
+
28
+ mcp = FastMCP(
29
+ "thought-engine-bridge",
30
+ host="0.0.0.0",
31
+ port=7860,
32
+ )
33
+
34
+
35
+ # ── HTTP Helper ────────────────────────────────────────────────
36
+ async def _call_node(method: str, path: str, body: dict = None) -> dict:
37
+ """Call the Thought Engine Node REST API."""
38
+ url = f"{NODE_URL}{path}"
39
+ async with httpx.AsyncClient(timeout=30.0) as client:
40
+ try:
41
+ if method == "GET":
42
+ r = await client.get(url)
43
+ else:
44
+ r = await client.post(url, json=body or {})
45
+ r.raise_for_status()
46
+ return r.json()
47
+ except httpx.HTTPStatusError as e:
48
+ return {"error": f"Node returned {e.response.status_code}", "detail": e.response.text}
49
+ except Exception as e:
50
+ return {"error": str(e)}
51
+
52
+
53
+ # ══════════════════════════════════════════════════════════════
54
+ # ONBOARDING
55
+ # ══════════════════════════════════════════════════════════════
56
+
57
+ @mcp.tool()
58
+ async def thought_onboard(client_name: str = "Agent") -> str:
59
+ """🧠 REQUIRED FIRST STEP: Learn the Thought Engine capabilities and protocols.
60
+
61
+ Call this tool first to understand how to use the Thought Engine effectively.
62
+ """
63
+ return f"""# 🧠 Welcome to the Thought Engine, {client_name}!
64
+
65
+ ## What This Is
66
+ A **persistent, governed reasoning substrate** backed by Cloudflare D1.
67
+ Every thought you create survives restarts. Every fork is tracked. Every proposal is witnessed.
68
+
69
+ ## Core Concepts
70
+ - **Sessions**: Reasoning containers. Each session holds a tree of thoughts.
71
+ - **Thoughts**: Typed nodes — hypothesis, observation, validation, counter_argument, synthesis, decision, question, proposal.
72
+ - **Forking**: Explore alternative reasoning paths without destroying the main thread.
73
+ - **Proposals (PRs)**: Submit a "Pull Request" for a thought. Can be accepted, rejected, or superseded.
74
+ - **Witness Trail**: Every action is provenance-logged — who did what, when, and why.
75
+
76
+ ## Workflow
77
+ 1. `thought_start_session` — Create a new reasoning session
78
+ 2. `thought_add_step` — Add sequential thoughts to the active chain
79
+ 3. `thought_fork` — Branch off to explore an alternative
80
+ 4. `thought_propose` — Submit a formal thought proposal
81
+ 5. `thought_review_proposal` — Accept or reject a proposal
82
+ 6. `thought_display_tree` — Visualize the full reasoning tree
83
+ 7. `thought_search` — Search thoughts by content pattern
84
+ 8. `thought_witness` — View the audit/provenance trail
85
+
86
+ ## Available Thought Types
87
+ `hypothesis`, `observation`, `validation`, `counter_argument`, `synthesis`, `decision`, `question`, `proposal`
88
+
89
+ Begin by creating a session with `thought_start_session`! 🧠
90
+ """
91
+
92
+
93
+ # ══════════════════════════════════════════════════════════════
94
+ # SESSION TOOLS
95
+ # ══════════════════════════════════════════════════════════════
96
+
97
+ @mcp.tool()
98
+ async def thought_start_session(
99
+ initial_thought: str,
100
+ title: str = "",
101
+ thought_class: str = "hypothesis",
102
+ agent_id: str = "Agent"
103
+ ) -> str:
104
+ """🧠 Start a new reasoning session with an initial thought.
105
+
106
+ Args:
107
+ initial_thought: The opening thought or question to reason about.
108
+ title: Optional title for the session (defaults to first 80 chars of thought).
109
+ thought_class: Type of thought — hypothesis, observation, validation, counter_argument, synthesis, decision, question.
110
+ agent_id: Your identity for provenance tracking.
111
+ """
112
+ result = await _call_node("POST", "/session", {
113
+ "initial_thought": initial_thought,
114
+ "title": title or None,
115
+ "thought_class": thought_class,
116
+ "agent_id": agent_id,
117
+ })
118
+ return json.dumps(result, indent=2)
119
+
120
+
121
+ @mcp.tool()
122
+ async def thought_get_session(session_id: str) -> str:
123
+ """📋 Get metadata for a specific session.
124
+
125
+ Args:
126
+ session_id: The session ID to retrieve.
127
+ """
128
+ result = await _call_node("GET", f"/session/{session_id}")
129
+ return json.dumps(result, indent=2)
130
+
131
+
132
+ @mcp.tool()
133
+ async def thought_list_sessions(status: str = "", limit: int = 20) -> str:
134
+ """📚 List all reasoning sessions, optionally filtered by status.
135
+
136
+ Args:
137
+ status: Filter by status — active, archived, merged. Leave empty for all.
138
+ limit: Maximum number of sessions to return (1-100).
139
+ """
140
+ params = f"?limit={limit}"
141
+ if status:
142
+ params += f"&status={status}"
143
+ result = await _call_node("GET", f"/sessions{params}")
144
+ return json.dumps(result, indent=2)
145
+
146
+
147
+ # ══════════════════════════════════════════════════════════════
148
+ # THOUGHT TOOLS
149
+ # ══════════════════════════════════════════════════════════════
150
+
151
+ @mcp.tool()
152
+ async def thought_add_step(
153
+ session_id: str,
154
+ content: str,
155
+ thought_class: str = "observation",
156
+ agent_id: str = "Agent",
157
+ parent_thought_id: str = "",
158
+ confidence: float = None,
159
+ ) -> str:
160
+ """💭 Add a reasoning step to the session's active chain.
161
+
162
+ Args:
163
+ session_id: The session to add the thought to.
164
+ content: The thought content.
165
+ thought_class: Type — hypothesis, observation, validation, counter_argument, synthesis, decision, question.
166
+ agent_id: Your identity for provenance tracking.
167
+ parent_thought_id: Optional specific parent (defaults to session's active thought).
168
+ confidence: Optional confidence score (0.0-1.0).
169
+ """
170
+ body = {
171
+ "content": content,
172
+ "thought_class": thought_class,
173
+ "agent_id": agent_id,
174
+ }
175
+ if parent_thought_id:
176
+ body["parent_thought_id"] = parent_thought_id
177
+ if confidence is not None:
178
+ body["confidence"] = confidence
179
+
180
+ result = await _call_node("POST", f"/session/{session_id}/thought", body)
181
+ return json.dumps(result, indent=2)
182
+
183
+
184
+ @mcp.tool()
185
+ async def thought_list_thoughts(session_id: str) -> str:
186
+ """📃 List all thought units in a session, ordered chronologically.
187
+
188
+ Args:
189
+ session_id: The session to list thoughts from.
190
+ """
191
+ result = await _call_node("GET", f"/session/{session_id}/thoughts")
192
+ return json.dumps(result, indent=2)
193
+
194
+
195
+ # ══════════════════════════════════════════════════════════════
196
+ # FORKING TOOLS
197
+ # ══════════════════════════════════════════════════════════════
198
+
199
+ @mcp.tool()
200
+ async def thought_fork(
201
+ session_id: str,
202
+ source_thought_id: str,
203
+ branch_label: str,
204
+ agent_id: str = "Agent",
205
+ ) -> str:
206
+ """🌿 Fork a thought chain to explore an alternative reasoning path.
207
+
208
+ Creates a branch from the specified thought without destroying the original thread.
209
+ The session's active pointer moves to the new fork.
210
+
211
+ Args:
212
+ session_id: The session containing the thought to fork.
213
+ source_thought_id: The thought ID to branch from.
214
+ branch_label: A descriptive label for the branch (e.g., "risk-analysis", "alternative-approach").
215
+ agent_id: Your identity for provenance tracking.
216
+ """
217
+ result = await _call_node("POST", f"/session/{session_id}/fork", {
218
+ "source_thought_id": source_thought_id,
219
+ "branch_label": branch_label,
220
+ "agent_id": agent_id,
221
+ })
222
+ return json.dumps(result, indent=2)
223
+
224
+
225
+ # ══════════════════════════════════════════════════════════════
226
+ # PROPOSAL TOOLS (Git-for-Thought PRs)
227
+ # ══════════════════════════════════════════════════════════════
228
+
229
+ @mcp.tool()
230
+ async def thought_propose(
231
+ session_id: str,
232
+ parent_thought_id: str,
233
+ content: str,
234
+ note: str = "",
235
+ agent_id: str = "Agent",
236
+ ) -> str:
237
+ """📝 Submit a thought proposal (PR) — a formal suggestion branching from a parent thought.
238
+
239
+ Use this when you want to suggest a change or alternative without hijacking the active cursor.
240
+ The proposal must be reviewed (accepted/rejected) before it becomes active.
241
+
242
+ Args:
243
+ session_id: The session to submit the proposal in.
244
+ parent_thought_id: The thought this proposal branches from.
245
+ content: The proposed thought content.
246
+ note: Optional note explaining why this proposal matters.
247
+ agent_id: Your identity for provenance tracking.
248
+ """
249
+ result = await _call_node("POST", f"/session/{session_id}/proposal", {
250
+ "parent_thought_id": parent_thought_id,
251
+ "content": content,
252
+ "note": note,
253
+ "agent_id": agent_id,
254
+ })
255
+ return json.dumps(result, indent=2)
256
+
257
+
258
+ @mcp.tool()
259
+ async def thought_list_proposals(session_id: str) -> str:
260
+ """📋 List all pending proposals in a session.
261
+
262
+ Args:
263
+ session_id: The session to check for proposals.
264
+ """
265
+ result = await _call_node("GET", f"/session/{session_id}/proposals")
266
+ return json.dumps(result, indent=2)
267
+
268
+
269
+ @mcp.tool()
270
+ async def thought_review_proposal(
271
+ session_id: str,
272
+ proposal_id: str,
273
+ action: str,
274
+ actor: str = "Agent",
275
+ reason: str = "",
276
+ ) -> str:
277
+ """⚡ Review a thought proposal — accept, reject, or supersede it.
278
+
279
+ Accepting a proposal makes it the active thought and merges it into the reasoning chain.
280
+ Rejecting records the reason in the witness trail.
281
+
282
+ Args:
283
+ session_id: The session containing the proposal.
284
+ proposal_id: The proposal ID to review.
285
+ action: Review action — accept, reject, or supersede.
286
+ actor: Your identity for the review record.
287
+ reason: Explanation for the review decision.
288
+ """
289
+ result = await _call_node("POST", f"/session/{session_id}/proposal/{proposal_id}/review", {
290
+ "action": action,
291
+ "actor": actor,
292
+ "reason": reason or None,
293
+ })
294
+ return json.dumps(result, indent=2)
295
+
296
+
297
+ # ══════════════════════════════════════════════════════════════
298
+ # VISUALIZATION & QUERY TOOLS
299
+ # ══════════════════════════════════════════════════════════════
300
+
301
+ @mcp.tool()
302
+ async def thought_display_tree(session_id: str) -> str:
303
+ """🌳 Render the full reasoning tree for a session.
304
+
305
+ Returns the hierarchical thought structure with all branches, forks, and proposals.
306
+
307
+ Args:
308
+ session_id: The session to visualize.
309
+ """
310
+ result = await _call_node("GET", f"/session/{session_id}/tree")
311
+ return json.dumps(result, indent=2)
312
+
313
+
314
+ @mcp.tool()
315
+ async def thought_search(session_id: str, pattern: str) -> str:
316
+ """🔍 Search thoughts in a session by content pattern.
317
+
318
+ Args:
319
+ session_id: The session to search within.
320
+ pattern: Text pattern to search for in thought content.
321
+ """
322
+ result = await _call_node("POST", f"/session/{session_id}/search", {
323
+ "pattern": pattern,
324
+ })
325
+ return json.dumps(result, indent=2)
326
+
327
+
328
+ @mcp.tool()
329
+ async def thought_list_edges(session_id: str) -> str:
330
+ """🔗 List all edges (relationships) between thoughts in a session.
331
+
332
+ Shows how thoughts are connected: derives_from, supports, challenges, forks_from, merges_into, etc.
333
+
334
+ Args:
335
+ session_id: The session to inspect.
336
+ """
337
+ result = await _call_node("GET", f"/session/{session_id}/edges")
338
+ return json.dumps(result, indent=2)
339
+
340
+
341
+ @mcp.tool()
342
+ async def thought_witness(session_id: str, limit: int = 50) -> str:
343
+ """👁️ View the provenance/audit trail for a session.
344
+
345
+ Shows who created, forked, reviewed, and merged thoughts — the full governance history.
346
+
347
+ Args:
348
+ session_id: The session to audit.
349
+ limit: Maximum number of events to return (1-200).
350
+ """
351
+ result = await _call_node("GET", f"/session/{session_id}/witness?limit={limit}")
352
+ return json.dumps(result, indent=2)
353
+
354
+
355
+ # ══════════════════════════════════════════════════════════════
356
+ # IDENTITY
357
+ # ══════════════════════════════════════════════════════════════
358
+
359
+ @mcp.tool()
360
+ async def thought_node_identity() -> str:
361
+ """📡 Check the Thought Engine Node's identity, seal, and status."""
362
+ result = await _call_node("GET", "/")
363
+ return json.dumps(result, indent=2)
364
+
365
+
366
+ # ── Server Entry Point ─────────────────────────────────────────
367
+ def main():
368
+ """Run the Thought Engine Bridge as an SSE MCP server."""
369
+ os.environ["PYTHONIOENCODING"] = "utf-8"
370
+ os.environ["PYTHONUNBUFFERED"] = "1"
371
+ print("🌉 Thought Engine Bridge — SSE MCP Server starting...")
372
+ print(f" Node URL: {NODE_URL}")
373
+ mcp.run(transport="sse")
374
+
375
+
376
+ if __name__ == "__main__":
377
+ main()