hegelion 0.4.0__py3-none-any.whl
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.
- hegelion/__init__.py +45 -0
- hegelion/core/__init__.py +29 -0
- hegelion/core/agent.py +166 -0
- hegelion/core/autocoding_state.py +293 -0
- hegelion/core/backends.py +442 -0
- hegelion/core/cache.py +92 -0
- hegelion/core/config.py +276 -0
- hegelion/core/core.py +649 -0
- hegelion/core/engine.py +865 -0
- hegelion/core/logging_utils.py +67 -0
- hegelion/core/models.py +293 -0
- hegelion/core/parsing.py +271 -0
- hegelion/core/personas.py +81 -0
- hegelion/core/prompt_autocoding.py +353 -0
- hegelion/core/prompt_dialectic.py +414 -0
- hegelion/core/prompts.py +127 -0
- hegelion/core/schema.py +67 -0
- hegelion/core/validation.py +68 -0
- hegelion/council.py +254 -0
- hegelion/examples_data/__init__.py +6 -0
- hegelion/examples_data/glm4_6_examples.jsonl +2 -0
- hegelion/judge.py +230 -0
- hegelion/mcp/__init__.py +3 -0
- hegelion/mcp/server.py +918 -0
- hegelion/scripts/hegelion_agent_cli.py +90 -0
- hegelion/scripts/hegelion_bench.py +117 -0
- hegelion/scripts/hegelion_cli.py +497 -0
- hegelion/scripts/hegelion_dataset.py +99 -0
- hegelion/scripts/hegelion_eval.py +137 -0
- hegelion/scripts/mcp_setup.py +150 -0
- hegelion/search_providers.py +151 -0
- hegelion/training/__init__.py +7 -0
- hegelion/training/datasets.py +123 -0
- hegelion/training/generator.py +232 -0
- hegelion/training/mlx_scu_trainer.py +379 -0
- hegelion/training/mlx_trainer.py +181 -0
- hegelion/training/unsloth_trainer.py +136 -0
- hegelion-0.4.0.dist-info/METADATA +295 -0
- hegelion-0.4.0.dist-info/RECORD +43 -0
- hegelion-0.4.0.dist-info/WHEEL +5 -0
- hegelion-0.4.0.dist-info/entry_points.txt +8 -0
- hegelion-0.4.0.dist-info/licenses/LICENSE +21 -0
- hegelion-0.4.0.dist-info/top_level.txt +1 -0
hegelion/mcp/server.py
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
"""Model-agnostic MCP server for dialectical reasoning.
|
|
2
|
+
|
|
3
|
+
This version works with whatever LLM is calling the MCP server,
|
|
4
|
+
rather than making its own API calls. Perfect for Cursor, Claude Desktop,
|
|
5
|
+
VS Code, or any MCP-compatible environment.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
from mcp.server import Server
|
|
15
|
+
from mcp.server.stdio import stdio_server
|
|
16
|
+
from mcp.types import CallToolResult, TextContent, Tool
|
|
17
|
+
import anyio
|
|
18
|
+
|
|
19
|
+
from hegelion.core.prompt_dialectic import (
|
|
20
|
+
create_dialectical_workflow,
|
|
21
|
+
create_single_shot_dialectic_prompt,
|
|
22
|
+
)
|
|
23
|
+
from hegelion.core.autocoding_state import AutocodingState, save_session, load_session
|
|
24
|
+
from hegelion.core.prompt_autocoding import PromptDrivenAutocoding
|
|
25
|
+
|
|
26
|
+
app = Server("hegelion-server")
|
|
27
|
+
|
|
28
|
+
# Compatibility: older anyio versions expose create_memory_object_stream as a plain function
|
|
29
|
+
# which cannot be subscripted (newer typing style uses subscripting). Patch a lightweight
|
|
30
|
+
# wrapper so the MCP server session setup does not crash when subscripting is attempted.
|
|
31
|
+
if not hasattr(anyio.create_memory_object_stream, "__getitem__"):
|
|
32
|
+
_create_stream = anyio.create_memory_object_stream
|
|
33
|
+
|
|
34
|
+
class _CreateStreamWrapper:
|
|
35
|
+
def __call__(self, *args, **kwargs):
|
|
36
|
+
return _create_stream(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
def __getitem__(self, _):
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
anyio.create_memory_object_stream = _CreateStreamWrapper() # type: ignore[assignment]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.list_tools()
|
|
45
|
+
async def list_tools() -> list[Tool]:
|
|
46
|
+
"""Return dialectical reasoning tools that work with any LLM."""
|
|
47
|
+
tools = [
|
|
48
|
+
Tool(
|
|
49
|
+
name="dialectical_workflow",
|
|
50
|
+
description=(
|
|
51
|
+
"Step-by-step prompts for dialectical reasoning (thesis ā antithesis ā synthesis). "
|
|
52
|
+
"Set response_style to control output: 'json' (structured, agent-friendly), "
|
|
53
|
+
"'sections' (full text), or 'synthesis_only' (just the resolution). "
|
|
54
|
+
"Example: {'query': 'Should AI be regulated?', 'response_style': 'json'}."
|
|
55
|
+
),
|
|
56
|
+
inputSchema={
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"query": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "The question or topic to analyze dialectically",
|
|
62
|
+
},
|
|
63
|
+
"use_search": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"description": "Include instructions to use search tools for real-world grounding",
|
|
66
|
+
"default": False,
|
|
67
|
+
},
|
|
68
|
+
"use_council": {
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"description": "Enable multi-perspective council critiques (Logician, Empiricist, Ethicist)",
|
|
71
|
+
"default": False,
|
|
72
|
+
},
|
|
73
|
+
"use_judge": {
|
|
74
|
+
"type": "boolean",
|
|
75
|
+
"description": "Include quality evaluation step",
|
|
76
|
+
"default": False,
|
|
77
|
+
},
|
|
78
|
+
"format": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": ["workflow", "single_prompt"],
|
|
81
|
+
"description": "Return structured workflow or single comprehensive prompt",
|
|
82
|
+
"default": "workflow",
|
|
83
|
+
},
|
|
84
|
+
"response_style": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": [
|
|
87
|
+
"sections",
|
|
88
|
+
"synthesis_only",
|
|
89
|
+
"json",
|
|
90
|
+
"conversational",
|
|
91
|
+
"bullet_points",
|
|
92
|
+
],
|
|
93
|
+
"description": (
|
|
94
|
+
"Shape of the final output you want from the LLM: full thesis/antithesis/synthesis sections,"
|
|
95
|
+
" synthesis-only, or JSON with all fields."
|
|
96
|
+
),
|
|
97
|
+
"default": "sections",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
"required": ["query"],
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
Tool(
|
|
104
|
+
name="dialectical_single_shot",
|
|
105
|
+
description=(
|
|
106
|
+
"One comprehensive prompt that makes the LLM do thesis ā antithesis ā synthesis in one go. "
|
|
107
|
+
"Use response_style: 'json' (structured), 'sections' (full text), or 'synthesis_only' (just the resolution)."
|
|
108
|
+
),
|
|
109
|
+
inputSchema={
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {
|
|
112
|
+
"query": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "The question or topic to analyze dialectically",
|
|
115
|
+
},
|
|
116
|
+
"use_search": {
|
|
117
|
+
"type": "boolean",
|
|
118
|
+
"description": "Include instructions to use search tools",
|
|
119
|
+
"default": False,
|
|
120
|
+
},
|
|
121
|
+
"use_council": {
|
|
122
|
+
"type": "boolean",
|
|
123
|
+
"description": "Enable multi-perspective council critiques",
|
|
124
|
+
"default": False,
|
|
125
|
+
},
|
|
126
|
+
"response_style": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"enum": [
|
|
129
|
+
"sections",
|
|
130
|
+
"synthesis_only",
|
|
131
|
+
"json",
|
|
132
|
+
"conversational",
|
|
133
|
+
"bullet_points",
|
|
134
|
+
],
|
|
135
|
+
"description": (
|
|
136
|
+
"Format you want the model to return: full sections, synthesis-only, or JSON with thesis/antithesis/synthesis."
|
|
137
|
+
),
|
|
138
|
+
"default": "sections",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
"required": ["query"],
|
|
142
|
+
},
|
|
143
|
+
),
|
|
144
|
+
Tool(
|
|
145
|
+
name="thesis_prompt",
|
|
146
|
+
description=(
|
|
147
|
+
"Generate just the thesis prompt for dialectical reasoning. "
|
|
148
|
+
"Use this when you want to execute dialectical reasoning step-by-step."
|
|
149
|
+
),
|
|
150
|
+
inputSchema={
|
|
151
|
+
"type": "object",
|
|
152
|
+
"properties": {
|
|
153
|
+
"query": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"description": "The question or topic to analyze dialectically",
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"required": ["query"],
|
|
159
|
+
},
|
|
160
|
+
),
|
|
161
|
+
Tool(
|
|
162
|
+
name="antithesis_prompt",
|
|
163
|
+
description=(
|
|
164
|
+
"Generate the antithesis prompt for dialectical reasoning. "
|
|
165
|
+
"Requires the thesis output from a previous step."
|
|
166
|
+
),
|
|
167
|
+
inputSchema={
|
|
168
|
+
"type": "object",
|
|
169
|
+
"properties": {
|
|
170
|
+
"query": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"description": "The original question or topic",
|
|
173
|
+
},
|
|
174
|
+
"thesis": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"description": "The thesis output to critique",
|
|
177
|
+
},
|
|
178
|
+
"use_search": {
|
|
179
|
+
"type": "boolean",
|
|
180
|
+
"description": "Include instructions to use search tools",
|
|
181
|
+
"default": False,
|
|
182
|
+
},
|
|
183
|
+
"use_council": {
|
|
184
|
+
"type": "boolean",
|
|
185
|
+
"description": "Use council-based multi-perspective critique",
|
|
186
|
+
"default": False,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
"required": ["query", "thesis"],
|
|
190
|
+
},
|
|
191
|
+
),
|
|
192
|
+
Tool(
|
|
193
|
+
name="synthesis_prompt",
|
|
194
|
+
description=(
|
|
195
|
+
"Generate the synthesis prompt for dialectical reasoning. "
|
|
196
|
+
"Requires thesis and antithesis outputs from previous steps."
|
|
197
|
+
),
|
|
198
|
+
inputSchema={
|
|
199
|
+
"type": "object",
|
|
200
|
+
"properties": {
|
|
201
|
+
"query": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"description": "The original question or topic",
|
|
204
|
+
},
|
|
205
|
+
"thesis": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "The thesis output",
|
|
208
|
+
},
|
|
209
|
+
"antithesis": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"description": "The antithesis critique output",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
"required": ["query", "thesis", "antithesis"],
|
|
215
|
+
},
|
|
216
|
+
),
|
|
217
|
+
# === AUTOCODING TOOLS (based on g3 paper) ===
|
|
218
|
+
Tool(
|
|
219
|
+
name="autocoding_init",
|
|
220
|
+
description=(
|
|
221
|
+
"Initialize a dialectical autocoding session with requirements. "
|
|
222
|
+
"Returns session state to pass to subsequent tool calls. "
|
|
223
|
+
"Based on the g3 paper's coach-player adversarial cooperation paradigm."
|
|
224
|
+
),
|
|
225
|
+
inputSchema={
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"requirements": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"description": "The requirements document (source of truth). Should be structured as a checklist.",
|
|
231
|
+
},
|
|
232
|
+
"max_turns": {
|
|
233
|
+
"type": "integer",
|
|
234
|
+
"description": "Maximum turns before timeout (default: 10)",
|
|
235
|
+
"default": 10,
|
|
236
|
+
},
|
|
237
|
+
"approval_threshold": {
|
|
238
|
+
"type": "number",
|
|
239
|
+
"description": "Minimum compliance score for approval (0-1, default: 0.9)",
|
|
240
|
+
"default": 0.9,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
"required": ["requirements"],
|
|
244
|
+
},
|
|
245
|
+
),
|
|
246
|
+
Tool(
|
|
247
|
+
name="player_prompt",
|
|
248
|
+
description=(
|
|
249
|
+
"Generate the implementation prompt for the player agent in autocoding. "
|
|
250
|
+
"The player focuses on implementing requirements, NOT declaring success."
|
|
251
|
+
),
|
|
252
|
+
inputSchema={
|
|
253
|
+
"type": "object",
|
|
254
|
+
"properties": {
|
|
255
|
+
"state": {
|
|
256
|
+
"type": "object",
|
|
257
|
+
"description": "AutocodingState dict from autocoding_init or autocoding_advance",
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
"required": ["state"],
|
|
261
|
+
},
|
|
262
|
+
),
|
|
263
|
+
Tool(
|
|
264
|
+
name="coach_prompt",
|
|
265
|
+
description=(
|
|
266
|
+
"Generate the validation prompt for the coach agent in autocoding. "
|
|
267
|
+
"The coach verifies implementation against requirements, ignoring player's self-assessment."
|
|
268
|
+
),
|
|
269
|
+
inputSchema={
|
|
270
|
+
"type": "object",
|
|
271
|
+
"properties": {
|
|
272
|
+
"state": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"description": "AutocodingState dict from player phase",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
"required": ["state"],
|
|
278
|
+
},
|
|
279
|
+
),
|
|
280
|
+
Tool(
|
|
281
|
+
name="autocoding_advance",
|
|
282
|
+
description=(
|
|
283
|
+
"Advance autocoding state after coach review. "
|
|
284
|
+
"Updates turn count, records feedback, and determines next phase."
|
|
285
|
+
),
|
|
286
|
+
inputSchema={
|
|
287
|
+
"type": "object",
|
|
288
|
+
"properties": {
|
|
289
|
+
"state": {
|
|
290
|
+
"type": "object",
|
|
291
|
+
"description": "AutocodingState dict from coach phase",
|
|
292
|
+
},
|
|
293
|
+
"coach_feedback": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"description": "The coach's feedback text (compliance checklist and actions needed)",
|
|
296
|
+
},
|
|
297
|
+
"approved": {
|
|
298
|
+
"type": "boolean",
|
|
299
|
+
"description": "Whether the coach approved the implementation (look for 'COACH APPROVED')",
|
|
300
|
+
},
|
|
301
|
+
"compliance_score": {
|
|
302
|
+
"type": "number",
|
|
303
|
+
"description": "Optional compliance score (0-1) based on checklist items satisfied",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
"required": ["state", "coach_feedback", "approved"],
|
|
307
|
+
},
|
|
308
|
+
),
|
|
309
|
+
Tool(
|
|
310
|
+
name="autocoding_single_shot",
|
|
311
|
+
description=(
|
|
312
|
+
"Single comprehensive prompt for self-directed autocoding. "
|
|
313
|
+
"Combines player and coach roles with iterative implementation and self-verification."
|
|
314
|
+
),
|
|
315
|
+
inputSchema={
|
|
316
|
+
"type": "object",
|
|
317
|
+
"properties": {
|
|
318
|
+
"requirements": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"description": "The requirements document (source of truth)",
|
|
321
|
+
},
|
|
322
|
+
"max_turns": {
|
|
323
|
+
"type": "integer",
|
|
324
|
+
"description": "Maximum iterations to attempt (default: 10)",
|
|
325
|
+
"default": 10,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
"required": ["requirements"],
|
|
329
|
+
},
|
|
330
|
+
),
|
|
331
|
+
Tool(
|
|
332
|
+
name="autocoding_save",
|
|
333
|
+
description=(
|
|
334
|
+
"Save an autocoding session to a JSON file. "
|
|
335
|
+
"Use this to persist session state for later resumption."
|
|
336
|
+
),
|
|
337
|
+
inputSchema={
|
|
338
|
+
"type": "object",
|
|
339
|
+
"properties": {
|
|
340
|
+
"state": {
|
|
341
|
+
"type": "object",
|
|
342
|
+
"description": "AutocodingState dict to save",
|
|
343
|
+
},
|
|
344
|
+
"filepath": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"description": "Path to save the session JSON file",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
"required": ["state", "filepath"],
|
|
350
|
+
},
|
|
351
|
+
),
|
|
352
|
+
Tool(
|
|
353
|
+
name="autocoding_load",
|
|
354
|
+
description=(
|
|
355
|
+
"Load an autocoding session from a JSON file. "
|
|
356
|
+
"Use this to resume a previously saved session."
|
|
357
|
+
),
|
|
358
|
+
inputSchema={
|
|
359
|
+
"type": "object",
|
|
360
|
+
"properties": {
|
|
361
|
+
"filepath": {
|
|
362
|
+
"type": "string",
|
|
363
|
+
"description": "Path to the session JSON file to load",
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
"required": ["filepath"],
|
|
367
|
+
},
|
|
368
|
+
),
|
|
369
|
+
]
|
|
370
|
+
return tools
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _response_style_summary(style: str) -> str:
|
|
374
|
+
"""Short human-readable description of response style."""
|
|
375
|
+
match style:
|
|
376
|
+
case "json":
|
|
377
|
+
return "LLM should return a JSON object with thesis/antithesis/synthesis fields."
|
|
378
|
+
case "synthesis_only":
|
|
379
|
+
return "LLM should only return the synthesis (no thesis/antithesis sections)."
|
|
380
|
+
case "conversational":
|
|
381
|
+
return "LLM should return a natural, conversational response."
|
|
382
|
+
case "bullet_points":
|
|
383
|
+
return "LLM should return a concise bulleted list."
|
|
384
|
+
case _:
|
|
385
|
+
return "LLM should return full Thesis ā Antithesis ā Synthesis sections."
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
async def _send_progress(message: str, progress: float, total: float = 3.0) -> None:
|
|
389
|
+
"""Send a progress notification if a progress token is available."""
|
|
390
|
+
try:
|
|
391
|
+
ctx = app.request_context
|
|
392
|
+
if ctx.meta and ctx.meta.progressToken:
|
|
393
|
+
await ctx.session.send_progress_notification(
|
|
394
|
+
ctx.meta.progressToken,
|
|
395
|
+
progress,
|
|
396
|
+
total=total,
|
|
397
|
+
message=message,
|
|
398
|
+
)
|
|
399
|
+
except (LookupError, AttributeError):
|
|
400
|
+
# No request context or progress token available
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@app.call_tool()
|
|
405
|
+
async def call_tool(name: str, arguments: Dict[str, Any]):
|
|
406
|
+
"""Execute dialectical reasoning tools."""
|
|
407
|
+
|
|
408
|
+
if name == "dialectical_workflow":
|
|
409
|
+
query = arguments["query"]
|
|
410
|
+
use_search = arguments.get("use_search", False)
|
|
411
|
+
use_council = arguments.get("use_council", False)
|
|
412
|
+
use_judge = arguments.get("use_judge", False)
|
|
413
|
+
format_type = arguments.get("format", "workflow")
|
|
414
|
+
response_style = arguments.get("response_style", "sections")
|
|
415
|
+
|
|
416
|
+
# Send progress notification
|
|
417
|
+
await _send_progress("āāā Preparing dialectical workflow āāā", 1.0)
|
|
418
|
+
|
|
419
|
+
if format_type == "single_prompt":
|
|
420
|
+
await _send_progress("āāā Generating single-shot prompt āāā", 2.0)
|
|
421
|
+
prompt = create_single_shot_dialectic_prompt(
|
|
422
|
+
query=query,
|
|
423
|
+
use_search=use_search,
|
|
424
|
+
use_council=use_council,
|
|
425
|
+
response_style=response_style,
|
|
426
|
+
)
|
|
427
|
+
await _send_progress("āāā Prompt ready āāā", 3.0)
|
|
428
|
+
structured = {
|
|
429
|
+
"query": query,
|
|
430
|
+
"format": "single_prompt",
|
|
431
|
+
"use_search": use_search,
|
|
432
|
+
"use_council": use_council,
|
|
433
|
+
"response_style": response_style,
|
|
434
|
+
"prompt": prompt,
|
|
435
|
+
}
|
|
436
|
+
return ([TextContent(type="text", text=prompt)], structured)
|
|
437
|
+
else:
|
|
438
|
+
await _send_progress("āāā THESIS prompt ready āāā", 1.0)
|
|
439
|
+
await _send_progress("āāā ANTITHESIS prompt ready āāā", 2.0)
|
|
440
|
+
await _send_progress("āāā SYNTHESIS prompt ready āāā", 3.0)
|
|
441
|
+
workflow = create_dialectical_workflow(
|
|
442
|
+
query=query,
|
|
443
|
+
use_search=use_search,
|
|
444
|
+
use_council=use_council,
|
|
445
|
+
use_judge=use_judge,
|
|
446
|
+
)
|
|
447
|
+
workflow.setdefault("instructions", {})
|
|
448
|
+
workflow["instructions"]["response_style"] = response_style
|
|
449
|
+
workflow["instructions"]["response_style_note"] = _response_style_summary(
|
|
450
|
+
response_style
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
serialized = json.dumps(workflow, indent=2)
|
|
454
|
+
summary = (
|
|
455
|
+
"Hegelion dialectical workflow ready. Agents should read the structuredContent JSON. "
|
|
456
|
+
f"Human-readable summary: query='{query}', response_style='{response_style}'."
|
|
457
|
+
)
|
|
458
|
+
contents: List[TextContent] = [
|
|
459
|
+
TextContent(type="text", text=summary),
|
|
460
|
+
TextContent(type="text", text=serialized),
|
|
461
|
+
]
|
|
462
|
+
return (contents, workflow)
|
|
463
|
+
|
|
464
|
+
elif name == "dialectical_single_shot":
|
|
465
|
+
query = arguments["query"]
|
|
466
|
+
use_search = arguments.get("use_search", False)
|
|
467
|
+
use_council = arguments.get("use_council", False)
|
|
468
|
+
|
|
469
|
+
response_style = arguments.get("response_style", "sections")
|
|
470
|
+
prompt = create_single_shot_dialectic_prompt(
|
|
471
|
+
query=query,
|
|
472
|
+
use_search=use_search,
|
|
473
|
+
use_council=use_council,
|
|
474
|
+
response_style=response_style,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
structured = {
|
|
478
|
+
"query": query,
|
|
479
|
+
"use_search": use_search,
|
|
480
|
+
"use_council": use_council,
|
|
481
|
+
"response_style": response_style,
|
|
482
|
+
"prompt": prompt,
|
|
483
|
+
}
|
|
484
|
+
note = _response_style_summary(response_style)
|
|
485
|
+
contents = [
|
|
486
|
+
TextContent(type="text", text=f"{note}\n\n{prompt}"),
|
|
487
|
+
]
|
|
488
|
+
return (contents, structured)
|
|
489
|
+
|
|
490
|
+
elif name == "thesis_prompt":
|
|
491
|
+
await _send_progress("āāā THESIS āāā Generating prompt...", 1.0, 1.0)
|
|
492
|
+
from hegelion.core.prompt_dialectic import PromptDrivenDialectic
|
|
493
|
+
|
|
494
|
+
query = arguments["query"]
|
|
495
|
+
dialectic = PromptDrivenDialectic()
|
|
496
|
+
prompt_obj = dialectic.generate_thesis_prompt(query)
|
|
497
|
+
|
|
498
|
+
structured = {
|
|
499
|
+
"phase": prompt_obj.phase,
|
|
500
|
+
"prompt": prompt_obj.prompt,
|
|
501
|
+
"instructions": prompt_obj.instructions,
|
|
502
|
+
"expected_format": prompt_obj.expected_format,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
response = f"""# THESIS PROMPT
|
|
506
|
+
|
|
507
|
+
{prompt_obj.prompt}
|
|
508
|
+
|
|
509
|
+
**Instructions:** {prompt_obj.instructions}
|
|
510
|
+
**Expected Format:** {prompt_obj.expected_format}"""
|
|
511
|
+
|
|
512
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
513
|
+
|
|
514
|
+
elif name == "antithesis_prompt":
|
|
515
|
+
await _send_progress("āāā ANTITHESIS āāā Generating prompt...", 1.0, 1.0)
|
|
516
|
+
from hegelion.core.prompt_dialectic import PromptDrivenDialectic
|
|
517
|
+
|
|
518
|
+
query = arguments["query"]
|
|
519
|
+
thesis = arguments["thesis"]
|
|
520
|
+
use_search = arguments.get("use_search", False)
|
|
521
|
+
use_council = arguments.get("use_council", False)
|
|
522
|
+
|
|
523
|
+
dialectic = PromptDrivenDialectic()
|
|
524
|
+
|
|
525
|
+
if use_council:
|
|
526
|
+
council_prompts = dialectic.generate_council_prompts(query, thesis)
|
|
527
|
+
response_parts = ["# COUNCIL ANTITHESIS PROMPTS\n"]
|
|
528
|
+
structured_prompts = []
|
|
529
|
+
|
|
530
|
+
for prompt_obj in council_prompts:
|
|
531
|
+
response_parts.append(f"## {prompt_obj.phase.replace('_', ' ').title()}")
|
|
532
|
+
response_parts.append(prompt_obj.prompt)
|
|
533
|
+
response_parts.append(f"**Instructions:** {prompt_obj.instructions}")
|
|
534
|
+
response_parts.append("")
|
|
535
|
+
structured_prompts.append(
|
|
536
|
+
{
|
|
537
|
+
"phase": prompt_obj.phase,
|
|
538
|
+
"prompt": prompt_obj.prompt,
|
|
539
|
+
"instructions": prompt_obj.instructions,
|
|
540
|
+
"expected_format": prompt_obj.expected_format,
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
structured = {"prompts": structured_prompts, "phase": "antithesis_council"}
|
|
545
|
+
response = "\n".join(response_parts)
|
|
546
|
+
else:
|
|
547
|
+
prompt_obj = dialectic.generate_antithesis_prompt(query, thesis, use_search)
|
|
548
|
+
structured = {
|
|
549
|
+
"phase": prompt_obj.phase,
|
|
550
|
+
"prompt": prompt_obj.prompt,
|
|
551
|
+
"instructions": prompt_obj.instructions,
|
|
552
|
+
"expected_format": prompt_obj.expected_format,
|
|
553
|
+
}
|
|
554
|
+
response = f"""# ANTITHESIS PROMPT
|
|
555
|
+
|
|
556
|
+
{prompt_obj.prompt}
|
|
557
|
+
|
|
558
|
+
**Instructions:** {prompt_obj.instructions}
|
|
559
|
+
**Expected Format:** {prompt_obj.expected_format}"""
|
|
560
|
+
|
|
561
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
562
|
+
|
|
563
|
+
elif name == "synthesis_prompt":
|
|
564
|
+
await _send_progress("āāā SYNTHESIS āāā Generating prompt...", 1.0, 1.0)
|
|
565
|
+
from hegelion.core.prompt_dialectic import PromptDrivenDialectic
|
|
566
|
+
|
|
567
|
+
query = arguments["query"]
|
|
568
|
+
thesis = arguments["thesis"]
|
|
569
|
+
antithesis = arguments["antithesis"]
|
|
570
|
+
|
|
571
|
+
dialectic = PromptDrivenDialectic()
|
|
572
|
+
prompt_obj = dialectic.generate_synthesis_prompt(query, thesis, antithesis)
|
|
573
|
+
|
|
574
|
+
structured = {
|
|
575
|
+
"phase": prompt_obj.phase,
|
|
576
|
+
"prompt": prompt_obj.prompt,
|
|
577
|
+
"instructions": prompt_obj.instructions,
|
|
578
|
+
"expected_format": prompt_obj.expected_format,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
response = f"""# SYNTHESIS PROMPT
|
|
582
|
+
|
|
583
|
+
{prompt_obj.prompt}
|
|
584
|
+
|
|
585
|
+
**Instructions:** {prompt_obj.instructions}
|
|
586
|
+
**Expected Format:** {prompt_obj.expected_format}"""
|
|
587
|
+
|
|
588
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
589
|
+
|
|
590
|
+
# === AUTOCODING TOOL HANDLERS ===
|
|
591
|
+
|
|
592
|
+
elif name == "autocoding_init":
|
|
593
|
+
await _send_progress("Initializing autocoding session...", 1.0, 2.0)
|
|
594
|
+
|
|
595
|
+
requirements = arguments["requirements"]
|
|
596
|
+
max_turns = arguments.get("max_turns", 10)
|
|
597
|
+
approval_threshold = arguments.get("approval_threshold", 0.9)
|
|
598
|
+
|
|
599
|
+
state = AutocodingState.create(
|
|
600
|
+
requirements=requirements,
|
|
601
|
+
max_turns=max_turns,
|
|
602
|
+
approval_threshold=approval_threshold,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
await _send_progress("Session initialized", 2.0, 2.0)
|
|
606
|
+
|
|
607
|
+
structured = state.to_dict()
|
|
608
|
+
response = f"""# AUTOCODING SESSION INITIALIZED
|
|
609
|
+
|
|
610
|
+
**Session ID:** {state.session_id[:8]}...
|
|
611
|
+
**Max Turns:** {max_turns}
|
|
612
|
+
**Approval Threshold:** {approval_threshold:.0%}
|
|
613
|
+
|
|
614
|
+
The session is ready. Next step: call `player_prompt` with the returned state.
|
|
615
|
+
|
|
616
|
+
**Workflow:**
|
|
617
|
+
1. Call `player_prompt` with state -> Execute returned prompt
|
|
618
|
+
2. Call `coach_prompt` with state -> Execute returned prompt
|
|
619
|
+
3. Call `autocoding_advance` with coach feedback
|
|
620
|
+
4. Repeat until COACH APPROVED or timeout"""
|
|
621
|
+
|
|
622
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
623
|
+
|
|
624
|
+
elif name == "player_prompt":
|
|
625
|
+
await _send_progress("Generating player prompt...", 1.0, 1.0)
|
|
626
|
+
|
|
627
|
+
state_dict = arguments["state"]
|
|
628
|
+
state = AutocodingState.from_dict(state_dict)
|
|
629
|
+
|
|
630
|
+
if state.phase != "player":
|
|
631
|
+
return CallToolResult(
|
|
632
|
+
content=[
|
|
633
|
+
TextContent(
|
|
634
|
+
type="text", text=f"Error: Expected player phase, got {state.phase}"
|
|
635
|
+
)
|
|
636
|
+
],
|
|
637
|
+
structuredContent={"error": f"Invalid phase: {state.phase}", "expected": "player"},
|
|
638
|
+
isError=True,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
autocoding = PromptDrivenAutocoding()
|
|
642
|
+
prompt_obj = autocoding.generate_player_prompt(
|
|
643
|
+
requirements=state.requirements,
|
|
644
|
+
coach_feedback=state.last_coach_feedback,
|
|
645
|
+
turn_number=state.current_turn + 1,
|
|
646
|
+
max_turns=state.max_turns,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# Advance state to coach phase for next call
|
|
650
|
+
new_state = state.advance_to_coach()
|
|
651
|
+
|
|
652
|
+
structured = {
|
|
653
|
+
**prompt_obj.to_dict(),
|
|
654
|
+
"state": new_state.to_dict(),
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
response = f"""# PLAYER PROMPT (Turn {state.current_turn + 1}/{state.max_turns})
|
|
658
|
+
|
|
659
|
+
{prompt_obj.prompt}
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
**Instructions:** {prompt_obj.instructions}
|
|
663
|
+
**Next Step:** After executing this prompt, call `coach_prompt` with the updated state."""
|
|
664
|
+
|
|
665
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
666
|
+
|
|
667
|
+
elif name == "coach_prompt":
|
|
668
|
+
await _send_progress("Generating coach prompt...", 1.0, 1.0)
|
|
669
|
+
|
|
670
|
+
state_dict = arguments["state"]
|
|
671
|
+
state = AutocodingState.from_dict(state_dict)
|
|
672
|
+
|
|
673
|
+
if state.phase != "coach":
|
|
674
|
+
return CallToolResult(
|
|
675
|
+
content=[
|
|
676
|
+
TextContent(type="text", text=f"Error: Expected coach phase, got {state.phase}")
|
|
677
|
+
],
|
|
678
|
+
structuredContent={"error": f"Invalid phase: {state.phase}", "expected": "coach"},
|
|
679
|
+
isError=True,
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
autocoding = PromptDrivenAutocoding()
|
|
683
|
+
prompt_obj = autocoding.generate_coach_prompt(
|
|
684
|
+
requirements=state.requirements,
|
|
685
|
+
turn_number=state.current_turn + 1,
|
|
686
|
+
max_turns=state.max_turns,
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
structured = {
|
|
690
|
+
**prompt_obj.to_dict(),
|
|
691
|
+
"state": state.to_dict(),
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
response = f"""# COACH PROMPT (Turn {state.current_turn + 1}/{state.max_turns})
|
|
695
|
+
|
|
696
|
+
{prompt_obj.prompt}
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
**Instructions:** {prompt_obj.instructions}
|
|
700
|
+
**Next Step:** After executing this prompt, call `autocoding_advance` with the coach's feedback."""
|
|
701
|
+
|
|
702
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
703
|
+
|
|
704
|
+
elif name == "autocoding_advance":
|
|
705
|
+
await _send_progress("Advancing autocoding state...", 1.0, 2.0)
|
|
706
|
+
|
|
707
|
+
state_dict = arguments["state"]
|
|
708
|
+
coach_feedback = arguments["coach_feedback"]
|
|
709
|
+
approved = arguments["approved"]
|
|
710
|
+
compliance_score = arguments.get("compliance_score")
|
|
711
|
+
|
|
712
|
+
state = AutocodingState.from_dict(state_dict)
|
|
713
|
+
|
|
714
|
+
if state.phase != "coach":
|
|
715
|
+
return CallToolResult(
|
|
716
|
+
content=[
|
|
717
|
+
TextContent(type="text", text=f"Error: Expected coach phase, got {state.phase}")
|
|
718
|
+
],
|
|
719
|
+
structuredContent={"error": f"Invalid phase: {state.phase}", "expected": "coach"},
|
|
720
|
+
isError=True,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
new_state = state.advance_turn(
|
|
724
|
+
coach_feedback=coach_feedback,
|
|
725
|
+
approved=approved,
|
|
726
|
+
compliance_score=compliance_score,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
await _send_progress("State advanced", 2.0, 2.0)
|
|
730
|
+
|
|
731
|
+
structured = new_state.to_dict()
|
|
732
|
+
|
|
733
|
+
if new_state.status == "approved":
|
|
734
|
+
response = f"""# AUTOCODING COMPLETE - APPROVED
|
|
735
|
+
|
|
736
|
+
**Session:** {new_state.session_id[:8]}...
|
|
737
|
+
**Turns Used:** {new_state.current_turn}/{new_state.max_turns}
|
|
738
|
+
**Final Status:** APPROVED
|
|
739
|
+
|
|
740
|
+
The implementation has been verified by the coach and meets all requirements."""
|
|
741
|
+
|
|
742
|
+
elif new_state.status == "timeout":
|
|
743
|
+
response = f"""# AUTOCODING COMPLETE - TIMEOUT
|
|
744
|
+
|
|
745
|
+
**Session:** {new_state.session_id[:8]}...
|
|
746
|
+
**Turns Used:** {new_state.current_turn}/{new_state.max_turns}
|
|
747
|
+
**Final Status:** TIMEOUT
|
|
748
|
+
|
|
749
|
+
Maximum turns reached without approval. Review the turn history for progress made."""
|
|
750
|
+
|
|
751
|
+
else:
|
|
752
|
+
response = f"""# AUTOCODING - CONTINUING
|
|
753
|
+
|
|
754
|
+
**Session:** {new_state.session_id[:8]}...
|
|
755
|
+
**Turn:** {new_state.current_turn + 1}/{new_state.max_turns}
|
|
756
|
+
**Status:** {new_state.status}
|
|
757
|
+
|
|
758
|
+
**Next Step:** Call `player_prompt` with the updated state to continue implementation."""
|
|
759
|
+
|
|
760
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
761
|
+
|
|
762
|
+
elif name == "autocoding_single_shot":
|
|
763
|
+
await _send_progress("Generating single-shot autocoding prompt...", 1.0, 1.0)
|
|
764
|
+
|
|
765
|
+
requirements = arguments["requirements"]
|
|
766
|
+
max_turns = arguments.get("max_turns", 10)
|
|
767
|
+
|
|
768
|
+
autocoding = PromptDrivenAutocoding()
|
|
769
|
+
prompt_obj = autocoding.generate_single_shot_prompt(
|
|
770
|
+
requirements=requirements,
|
|
771
|
+
max_turns=max_turns,
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
structured = {
|
|
775
|
+
**prompt_obj.to_dict(),
|
|
776
|
+
"requirements": requirements,
|
|
777
|
+
"max_turns": max_turns,
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
response = f"""# SINGLE-SHOT AUTOCODING PROMPT
|
|
781
|
+
|
|
782
|
+
{prompt_obj.prompt}
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
**Instructions:** {prompt_obj.instructions}
|
|
786
|
+
**Expected Format:** {prompt_obj.expected_format}"""
|
|
787
|
+
|
|
788
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
789
|
+
|
|
790
|
+
elif name == "autocoding_save":
|
|
791
|
+
state_dict = arguments["state"]
|
|
792
|
+
filepath = arguments["filepath"]
|
|
793
|
+
|
|
794
|
+
state = AutocodingState.from_dict(state_dict)
|
|
795
|
+
save_session(state, filepath)
|
|
796
|
+
|
|
797
|
+
structured = {
|
|
798
|
+
"session_id": state.session_id,
|
|
799
|
+
"filepath": filepath,
|
|
800
|
+
"saved": True,
|
|
801
|
+
}
|
|
802
|
+
response = f"""# SESSION SAVED
|
|
803
|
+
|
|
804
|
+
**Session ID:** {state.session_id[:8]}...
|
|
805
|
+
**Saved to:** {filepath}
|
|
806
|
+
**Phase:** {state.phase}
|
|
807
|
+
**Turn:** {state.current_turn + 1}/{state.max_turns}
|
|
808
|
+
|
|
809
|
+
Session saved successfully. Use `autocoding_load` with the filepath to restore."""
|
|
810
|
+
|
|
811
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
812
|
+
|
|
813
|
+
elif name == "autocoding_load":
|
|
814
|
+
filepath = arguments["filepath"]
|
|
815
|
+
|
|
816
|
+
try:
|
|
817
|
+
state = load_session(filepath)
|
|
818
|
+
except FileNotFoundError:
|
|
819
|
+
return CallToolResult(
|
|
820
|
+
content=[
|
|
821
|
+
TextContent(type="text", text=f"Error: Session file not found: {filepath}")
|
|
822
|
+
],
|
|
823
|
+
structuredContent={"error": f"File not found: {filepath}"},
|
|
824
|
+
isError=True,
|
|
825
|
+
)
|
|
826
|
+
except json.JSONDecodeError as e:
|
|
827
|
+
return CallToolResult(
|
|
828
|
+
content=[
|
|
829
|
+
TextContent(type="text", text=f"Error: Invalid JSON in session file: {e}")
|
|
830
|
+
],
|
|
831
|
+
structuredContent={"error": f"Invalid JSON: {str(e)}"},
|
|
832
|
+
isError=True,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
structured = state.to_dict()
|
|
836
|
+
response = f"""# SESSION LOADED
|
|
837
|
+
|
|
838
|
+
**Session ID:** {state.session_id[:8]}...
|
|
839
|
+
**Loaded from:** {filepath}
|
|
840
|
+
**Phase:** {state.phase}
|
|
841
|
+
**Status:** {state.status}
|
|
842
|
+
**Turn:** {state.current_turn + 1}/{state.max_turns}
|
|
843
|
+
|
|
844
|
+
Session restored. Continue with the appropriate tool based on phase:
|
|
845
|
+
- If phase is "player": call `player_prompt`
|
|
846
|
+
- If phase is "coach": call `coach_prompt`"""
|
|
847
|
+
|
|
848
|
+
return ([TextContent(type="text", text=response)], structured)
|
|
849
|
+
|
|
850
|
+
else:
|
|
851
|
+
return CallToolResult(
|
|
852
|
+
content=[TextContent(type="text", text=f"Unknown tool: {name}")],
|
|
853
|
+
structuredContent={"error": f"Unknown tool: {name}"},
|
|
854
|
+
isError=True,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
async def run_server() -> None:
|
|
859
|
+
"""Run the prompt-driven MCP server using the standard stdio transport."""
|
|
860
|
+
|
|
861
|
+
init_options = app.create_initialization_options()
|
|
862
|
+
|
|
863
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
864
|
+
await app.run(
|
|
865
|
+
read_stream,
|
|
866
|
+
write_stream,
|
|
867
|
+
init_options,
|
|
868
|
+
# Stateless keeps older MCP runtimes happy if they call tools/list immediately
|
|
869
|
+
# after initialization or skip explicit initialization notifications.
|
|
870
|
+
stateless=True,
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def main() -> None:
|
|
875
|
+
"""Main entry point for the prompt-driven MCP server."""
|
|
876
|
+
parser = argparse.ArgumentParser(
|
|
877
|
+
description="Hegelion Prompt-Driven MCP Server - Works with any LLM",
|
|
878
|
+
add_help=True,
|
|
879
|
+
)
|
|
880
|
+
parser.add_argument(
|
|
881
|
+
"--self-test",
|
|
882
|
+
action="store_true",
|
|
883
|
+
help="Run an in-process tool check (list tools + single-shot prompt) and exit",
|
|
884
|
+
)
|
|
885
|
+
args = parser.parse_args()
|
|
886
|
+
|
|
887
|
+
if args.self_test:
|
|
888
|
+
anyio.run(_self_test)
|
|
889
|
+
else:
|
|
890
|
+
anyio.run(run_server)
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
async def _self_test() -> None:
|
|
894
|
+
"""Self-test: list tools and call a sample tool so users see output."""
|
|
895
|
+
|
|
896
|
+
print("š Hegelion MCP self-test (in-process)")
|
|
897
|
+
tools = await list_tools()
|
|
898
|
+
tool_names = [t.name for t in tools]
|
|
899
|
+
print("Tools:", ", ".join(tool_names))
|
|
900
|
+
|
|
901
|
+
contents, structured = await call_tool(
|
|
902
|
+
name="dialectical_single_shot",
|
|
903
|
+
arguments={
|
|
904
|
+
"query": "Self-test: Can AI be genuinely creative?",
|
|
905
|
+
"use_council": True,
|
|
906
|
+
"response_style": "json",
|
|
907
|
+
},
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
print("\nresponse_style: json")
|
|
911
|
+
print("Structured keys:", list(structured.keys()))
|
|
912
|
+
prompt = structured.get("prompt", "")
|
|
913
|
+
print("Prompt preview:\n", prompt[:400], "...", sep="")
|
|
914
|
+
print("\nā
Self-test complete")
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
if __name__ == "__main__":
|
|
918
|
+
main()
|