empathy-framework 4.6.3__py3-none-any.whl → 4.6.5__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.
Files changed (65) hide show
  1. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/METADATA +53 -11
  2. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/RECORD +32 -57
  3. empathy_llm_toolkit/agent_factory/crews/health_check.py +7 -4
  4. empathy_llm_toolkit/agent_factory/decorators.py +3 -2
  5. empathy_llm_toolkit/agent_factory/memory_integration.py +6 -2
  6. empathy_llm_toolkit/contextual_patterns.py +5 -2
  7. empathy_llm_toolkit/git_pattern_extractor.py +8 -4
  8. empathy_llm_toolkit/providers.py +4 -3
  9. empathy_os/__init__.py +1 -1
  10. empathy_os/cli/__init__.py +306 -0
  11. empathy_os/cli/__main__.py +26 -0
  12. empathy_os/cli/commands/__init__.py +8 -0
  13. empathy_os/cli/commands/inspection.py +48 -0
  14. empathy_os/cli/commands/memory.py +56 -0
  15. empathy_os/cli/commands/provider.py +86 -0
  16. empathy_os/cli/commands/utilities.py +94 -0
  17. empathy_os/cli/core.py +32 -0
  18. empathy_os/cli.py +18 -6
  19. empathy_os/cli_unified.py +19 -3
  20. empathy_os/memory/short_term.py +12 -2
  21. empathy_os/project_index/scanner.py +151 -49
  22. empathy_os/socratic/visual_editor.py +9 -4
  23. empathy_os/workflows/bug_predict.py +70 -1
  24. empathy_os/workflows/pr_review.py +6 -0
  25. empathy_os/workflows/security_audit.py +13 -0
  26. empathy_os/workflows/tier_tracking.py +50 -2
  27. wizards/discharge_summary_wizard.py +4 -2
  28. wizards/incident_report_wizard.py +4 -2
  29. empathy_os/meta_workflows/agent_creator 2.py +0 -254
  30. empathy_os/meta_workflows/builtin_templates 2.py +0 -567
  31. empathy_os/meta_workflows/cli_meta_workflows 2.py +0 -1551
  32. empathy_os/meta_workflows/form_engine 2.py +0 -304
  33. empathy_os/meta_workflows/intent_detector 2.py +0 -298
  34. empathy_os/meta_workflows/pattern_learner 2.py +0 -754
  35. empathy_os/meta_workflows/session_context 2.py +0 -398
  36. empathy_os/meta_workflows/template_registry 2.py +0 -229
  37. empathy_os/meta_workflows/workflow 2.py +0 -980
  38. empathy_os/orchestration/pattern_learner 2.py +0 -699
  39. empathy_os/orchestration/real_tools 2.py +0 -938
  40. empathy_os/socratic/__init__ 2.py +0 -273
  41. empathy_os/socratic/ab_testing 2.py +0 -969
  42. empathy_os/socratic/blueprint 2.py +0 -532
  43. empathy_os/socratic/cli 2.py +0 -689
  44. empathy_os/socratic/collaboration 2.py +0 -1112
  45. empathy_os/socratic/domain_templates 2.py +0 -916
  46. empathy_os/socratic/embeddings 2.py +0 -734
  47. empathy_os/socratic/engine 2.py +0 -729
  48. empathy_os/socratic/explainer 2.py +0 -663
  49. empathy_os/socratic/feedback 2.py +0 -767
  50. empathy_os/socratic/forms 2.py +0 -624
  51. empathy_os/socratic/generator 2.py +0 -716
  52. empathy_os/socratic/llm_analyzer 2.py +0 -635
  53. empathy_os/socratic/mcp_server 2.py +0 -751
  54. empathy_os/socratic/session 2.py +0 -306
  55. empathy_os/socratic/storage 2.py +0 -635
  56. empathy_os/socratic/success 2.py +0 -719
  57. empathy_os/socratic/visual_editor 2.py +0 -812
  58. empathy_os/socratic/web_ui 2.py +0 -925
  59. empathy_os/workflows/batch_processing 2.py +0 -310
  60. empathy_os/workflows/release_prep_crew 2.py +0 -968
  61. empathy_os/workflows/test_coverage_boost_crew 2.py +0 -848
  62. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/WHEEL +0 -0
  63. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/entry_points.txt +0 -0
  64. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/licenses/LICENSE +0 -0
  65. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/top_level.txt +0 -0
@@ -1,751 +0,0 @@
1
- """MCP Server for Socratic Agent Generation System.
2
-
3
- Exposes the Socratic workflow builder as MCP tools for Claude Desktop/Code.
4
-
5
- Usage:
6
- python -m empathy_os.socratic.mcp_server
7
-
8
- Or add to Claude Desktop config:
9
- {
10
- "mcpServers": {
11
- "socratic": {
12
- "command": "python",
13
- "args": ["-m", "empathy_os.socratic.mcp_server"],
14
- "env": {
15
- "ANTHROPIC_API_KEY": "your-key"
16
- }
17
- }
18
- }
19
- }
20
-
21
- Copyright 2026 Smart-AI-Memory
22
- Licensed under Fair Source License 0.9
23
- """
24
-
25
- import asyncio
26
- import json
27
- import logging
28
- import os
29
- import sys
30
- from typing import Any
31
-
32
- logger = logging.getLogger(__name__)
33
-
34
- # MCP Protocol Types
35
- MCP_VERSION = "2024-11-05"
36
-
37
- # Tool definitions for the Socratic system
38
- SOCRATIC_TOOLS = [
39
- {
40
- "name": "socratic_start_session",
41
- "description": "Start a new Socratic workflow builder session. Returns a session ID and initial state.",
42
- "inputSchema": {
43
- "type": "object",
44
- "properties": {
45
- "goal": {
46
- "type": "string",
47
- "description": "Optional initial goal. If not provided, session starts in AWAITING_GOAL state."
48
- }
49
- },
50
- "required": []
51
- }
52
- },
53
- {
54
- "name": "socratic_set_goal",
55
- "description": "Set or update the goal for a session. Triggers goal analysis and domain detection.",
56
- "inputSchema": {
57
- "type": "object",
58
- "properties": {
59
- "session_id": {
60
- "type": "string",
61
- "description": "The session ID to update"
62
- },
63
- "goal": {
64
- "type": "string",
65
- "description": "The user's goal in free-form text"
66
- }
67
- },
68
- "required": ["session_id", "goal"]
69
- }
70
- },
71
- {
72
- "name": "socratic_get_questions",
73
- "description": "Get the next set of clarifying questions for a session.",
74
- "inputSchema": {
75
- "type": "object",
76
- "properties": {
77
- "session_id": {
78
- "type": "string",
79
- "description": "The session ID"
80
- }
81
- },
82
- "required": ["session_id"]
83
- }
84
- },
85
- {
86
- "name": "socratic_submit_answers",
87
- "description": "Submit answers to clarifying questions.",
88
- "inputSchema": {
89
- "type": "object",
90
- "properties": {
91
- "session_id": {
92
- "type": "string",
93
- "description": "The session ID"
94
- },
95
- "answers": {
96
- "type": "object",
97
- "description": "Dictionary of field_id -> answer value"
98
- }
99
- },
100
- "required": ["session_id", "answers"]
101
- }
102
- },
103
- {
104
- "name": "socratic_generate_workflow",
105
- "description": "Generate the workflow once all questions are answered. Returns agent blueprints and success criteria.",
106
- "inputSchema": {
107
- "type": "object",
108
- "properties": {
109
- "session_id": {
110
- "type": "string",
111
- "description": "The session ID"
112
- }
113
- },
114
- "required": ["session_id"]
115
- }
116
- },
117
- {
118
- "name": "socratic_list_sessions",
119
- "description": "List all saved Socratic sessions.",
120
- "inputSchema": {
121
- "type": "object",
122
- "properties": {
123
- "status_filter": {
124
- "type": "string",
125
- "enum": ["all", "active", "completed"],
126
- "description": "Filter sessions by status"
127
- }
128
- },
129
- "required": []
130
- }
131
- },
132
- {
133
- "name": "socratic_get_session",
134
- "description": "Get details of a specific session.",
135
- "inputSchema": {
136
- "type": "object",
137
- "properties": {
138
- "session_id": {
139
- "type": "string",
140
- "description": "The session ID to retrieve"
141
- }
142
- },
143
- "required": ["session_id"]
144
- }
145
- },
146
- {
147
- "name": "socratic_list_blueprints",
148
- "description": "List all saved workflow blueprints.",
149
- "inputSchema": {
150
- "type": "object",
151
- "properties": {
152
- "domain_filter": {
153
- "type": "string",
154
- "description": "Optional domain to filter by (e.g., 'code_review', 'security')"
155
- }
156
- },
157
- "required": []
158
- }
159
- },
160
- {
161
- "name": "socratic_analyze_goal",
162
- "description": "Analyze a goal using LLM to detect domains, requirements, and ambiguities without starting a full session.",
163
- "inputSchema": {
164
- "type": "object",
165
- "properties": {
166
- "goal": {
167
- "type": "string",
168
- "description": "The goal to analyze"
169
- }
170
- },
171
- "required": ["goal"]
172
- }
173
- },
174
- {
175
- "name": "socratic_recommend_agents",
176
- "description": "Get agent recommendations based on requirements and historical success data.",
177
- "inputSchema": {
178
- "type": "object",
179
- "properties": {
180
- "domains": {
181
- "type": "array",
182
- "items": {"type": "string"},
183
- "description": "List of domains (e.g., ['code_review', 'security'])"
184
- },
185
- "languages": {
186
- "type": "array",
187
- "items": {"type": "string"},
188
- "description": "Programming languages involved"
189
- },
190
- "quality_focus": {
191
- "type": "array",
192
- "items": {"type": "string"},
193
- "description": "Quality focus areas (e.g., ['security', 'performance'])"
194
- }
195
- },
196
- "required": ["domains"]
197
- }
198
- }
199
- ]
200
-
201
-
202
- class SocraticMCPServer:
203
- """MCP Server exposing Socratic workflow builder tools."""
204
-
205
- def __init__(self):
206
- """Initialize the MCP server."""
207
- self._sessions: dict[str, Any] = {}
208
- self._builder = None
209
- self._storage = None
210
- self._llm_analyzer = None
211
- self._feedback_loop = None
212
- self._initialized = False
213
-
214
- async def _ensure_initialized(self):
215
- """Lazily initialize components."""
216
- if self._initialized:
217
- return
218
-
219
- try:
220
- from .engine import SocraticWorkflowBuilder
221
- from .feedback import FeedbackLoop
222
- from .llm_analyzer import LLMGoalAnalyzer
223
- from .storage import JSONFileStorage, StorageManager
224
-
225
- # Initialize storage
226
- self._storage = JSONFileStorage()
227
- StorageManager.initialize(self._storage)
228
-
229
- # Initialize builder
230
- self._builder = SocraticWorkflowBuilder()
231
-
232
- # Initialize LLM analyzer if API key available
233
- api_key = os.environ.get("ANTHROPIC_API_KEY")
234
- if api_key:
235
- self._llm_analyzer = LLMGoalAnalyzer(api_key=api_key)
236
-
237
- # Initialize feedback loop
238
- self._feedback_loop = FeedbackLoop(self._storage)
239
-
240
- self._initialized = True
241
- logger.info("Socratic MCP Server initialized successfully")
242
-
243
- except ImportError as e:
244
- logger.warning(f"Some components not available: {e}")
245
- self._initialized = True
246
-
247
- async def handle_tool_call(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
248
- """Handle a tool call from Claude.
249
-
250
- Args:
251
- name: Tool name
252
- arguments: Tool arguments
253
-
254
- Returns:
255
- Tool result as dictionary
256
- """
257
- await self._ensure_initialized()
258
-
259
- handlers = {
260
- "socratic_start_session": self._handle_start_session,
261
- "socratic_set_goal": self._handle_set_goal,
262
- "socratic_get_questions": self._handle_get_questions,
263
- "socratic_submit_answers": self._handle_submit_answers,
264
- "socratic_generate_workflow": self._handle_generate_workflow,
265
- "socratic_list_sessions": self._handle_list_sessions,
266
- "socratic_get_session": self._handle_get_session,
267
- "socratic_list_blueprints": self._handle_list_blueprints,
268
- "socratic_analyze_goal": self._handle_analyze_goal,
269
- "socratic_recommend_agents": self._handle_recommend_agents,
270
- }
271
-
272
- handler = handlers.get(name)
273
- if not handler:
274
- return {"error": f"Unknown tool: {name}"}
275
-
276
- try:
277
- return await handler(arguments)
278
- except Exception as e:
279
- logger.exception(f"Error handling {name}")
280
- return {"error": str(e)}
281
-
282
- async def _handle_start_session(self, args: dict[str, Any]) -> dict[str, Any]:
283
- """Start a new Socratic session."""
284
- goal = args.get("goal")
285
-
286
- session = self._builder.start_session(goal)
287
- self._sessions[session.session_id] = session
288
-
289
- # Persist session
290
- if self._storage:
291
- self._storage.save_session(session)
292
-
293
- result = {
294
- "session_id": session.session_id,
295
- "state": session.state.value,
296
- "message": "Session started successfully"
297
- }
298
-
299
- if goal:
300
- result["goal"] = goal
301
- result["detected_domains"] = list(session.detected_domains) if session.detected_domains else []
302
-
303
- return result
304
-
305
- async def _handle_set_goal(self, args: dict[str, Any]) -> dict[str, Any]:
306
- """Set goal for a session."""
307
- session_id = args["session_id"]
308
- goal = args["goal"]
309
-
310
- session = self._get_session(session_id)
311
- if not session:
312
- return {"error": f"Session not found: {session_id}"}
313
-
314
- # Use LLM analyzer if available
315
- analysis = None
316
- if self._llm_analyzer:
317
- try:
318
- analysis = await self._llm_analyzer.analyze_goal(goal)
319
- except Exception as e:
320
- logger.warning(f"LLM analysis failed, using fallback: {e}")
321
-
322
- session = self._builder.set_goal(session, goal)
323
- self._sessions[session_id] = session
324
-
325
- if self._storage:
326
- self._storage.save_session(session)
327
-
328
- result = {
329
- "session_id": session_id,
330
- "state": session.state.value,
331
- "goal": goal,
332
- "detected_domains": list(session.detected_domains) if session.detected_domains else []
333
- }
334
-
335
- if analysis:
336
- result["llm_analysis"] = {
337
- "primary_domain": analysis.primary_domain,
338
- "confidence": analysis.confidence,
339
- "ambiguities": analysis.ambiguities,
340
- "suggested_questions": analysis.suggested_questions[:3]
341
- }
342
-
343
- return result
344
-
345
- async def _handle_get_questions(self, args: dict[str, Any]) -> dict[str, Any]:
346
- """Get clarifying questions for a session."""
347
- session_id = args["session_id"]
348
-
349
- session = self._get_session(session_id)
350
- if not session:
351
- return {"error": f"Session not found: {session_id}"}
352
-
353
- form = self._builder.get_next_questions(session)
354
- if not form:
355
- return {
356
- "session_id": session_id,
357
- "state": session.state.value,
358
- "questions": [],
359
- "message": "No more questions - ready to generate workflow"
360
- }
361
-
362
- # Convert form to JSON-serializable format
363
- questions = []
364
- for field in form.fields:
365
- q = {
366
- "field_id": field.field_id,
367
- "type": field.field_type.value,
368
- "label": field.label,
369
- "required": field.required
370
- }
371
- if field.options:
372
- q["options"] = field.options
373
- if field.placeholder:
374
- q["placeholder"] = field.placeholder
375
- if field.help_text:
376
- q["help_text"] = field.help_text
377
- if field.default_value is not None:
378
- q["default"] = field.default_value
379
- questions.append(q)
380
-
381
- return {
382
- "session_id": session_id,
383
- "state": session.state.value,
384
- "form_id": form.form_id,
385
- "form_title": form.title,
386
- "questions": questions
387
- }
388
-
389
- async def _handle_submit_answers(self, args: dict[str, Any]) -> dict[str, Any]:
390
- """Submit answers to questions."""
391
- session_id = args["session_id"]
392
- answers = args["answers"]
393
-
394
- session = self._get_session(session_id)
395
- if not session:
396
- return {"error": f"Session not found: {session_id}"}
397
-
398
- session = self._builder.submit_answers(session, answers)
399
- self._sessions[session_id] = session
400
-
401
- if self._storage:
402
- self._storage.save_session(session)
403
-
404
- ready = self._builder.is_ready_to_generate(session)
405
-
406
- return {
407
- "session_id": session_id,
408
- "state": session.state.value,
409
- "ready_to_generate": ready,
410
- "message": "Ready to generate workflow" if ready else "More questions available"
411
- }
412
-
413
- async def _handle_generate_workflow(self, args: dict[str, Any]) -> dict[str, Any]:
414
- """Generate workflow from session."""
415
- session_id = args["session_id"]
416
-
417
- session = self._get_session(session_id)
418
- if not session:
419
- return {"error": f"Session not found: {session_id}"}
420
-
421
- if not self._builder.is_ready_to_generate(session):
422
- return {
423
- "error": "Session not ready for generation",
424
- "state": session.state.value
425
- }
426
-
427
- workflow = self._builder.generate_workflow(session)
428
-
429
- # Save blueprint
430
- if self._storage:
431
- self._storage.save_blueprint(workflow.blueprint)
432
- self._storage.save_session(session)
433
-
434
- # Convert to JSON-serializable format
435
- agents = []
436
- for agent in workflow.blueprint.agents:
437
- agents.append({
438
- "agent_id": agent.agent_id,
439
- "name": agent.name,
440
- "role": agent.role.value,
441
- "description": agent.description,
442
- "tools": [t.tool_id for t in agent.tools]
443
- })
444
-
445
- stages = []
446
- for stage in workflow.blueprint.stages:
447
- stages.append({
448
- "stage_id": stage.stage_id,
449
- "name": stage.name,
450
- "agent_ids": stage.agent_ids,
451
- "dependencies": stage.dependencies
452
- })
453
-
454
- metrics = []
455
- for metric in workflow.success_criteria.metrics:
456
- metrics.append({
457
- "metric_id": metric.metric_id,
458
- "name": metric.name,
459
- "description": metric.description,
460
- "type": metric.metric_type.value,
461
- "target": metric.target_value
462
- })
463
-
464
- return {
465
- "session_id": session_id,
466
- "blueprint_id": workflow.blueprint.blueprint_id,
467
- "workflow_name": workflow.blueprint.name,
468
- "agents": agents,
469
- "stages": stages,
470
- "success_metrics": metrics,
471
- "state": session.state.value
472
- }
473
-
474
- async def _handle_list_sessions(self, args: dict[str, Any]) -> dict[str, Any]:
475
- """List all sessions."""
476
- status_filter = args.get("status_filter", "all")
477
-
478
- sessions = []
479
-
480
- # Get from storage
481
- if self._storage:
482
- stored = self._storage.list_sessions()
483
- for s in stored:
484
- if status_filter == "all":
485
- sessions.append(s)
486
- elif status_filter == "active" and s.get("state") != "completed":
487
- sessions.append(s)
488
- elif status_filter == "completed" and s.get("state") == "completed":
489
- sessions.append(s)
490
-
491
- # Add in-memory sessions not yet persisted
492
- for sid, session in self._sessions.items():
493
- if not any(s.get("session_id") == sid for s in sessions):
494
- sessions.append({
495
- "session_id": session.session_id,
496
- "state": session.state.value,
497
- "goal": session.goal,
498
- "created_at": session.created_at.isoformat() if session.created_at else None
499
- })
500
-
501
- return {
502
- "sessions": sessions,
503
- "count": len(sessions)
504
- }
505
-
506
- async def _handle_get_session(self, args: dict[str, Any]) -> dict[str, Any]:
507
- """Get session details."""
508
- session_id = args["session_id"]
509
-
510
- session = self._get_session(session_id)
511
- if not session:
512
- return {"error": f"Session not found: {session_id}"}
513
-
514
- return {
515
- "session_id": session.session_id,
516
- "state": session.state.value,
517
- "goal": session.goal,
518
- "detected_domains": list(session.detected_domains) if session.detected_domains else [],
519
- "answers": session.answers,
520
- "created_at": session.created_at.isoformat() if session.created_at else None,
521
- "updated_at": session.updated_at.isoformat() if session.updated_at else None
522
- }
523
-
524
- async def _handle_list_blueprints(self, args: dict[str, Any]) -> dict[str, Any]:
525
- """List all blueprints."""
526
- domain_filter = args.get("domain_filter")
527
-
528
- blueprints = []
529
-
530
- if self._storage:
531
- stored = self._storage.list_blueprints()
532
- for bp in stored:
533
- if domain_filter:
534
- domains = bp.get("domains", [])
535
- if domain_filter not in domains:
536
- continue
537
- blueprints.append(bp)
538
-
539
- return {
540
- "blueprints": blueprints,
541
- "count": len(blueprints)
542
- }
543
-
544
- async def _handle_analyze_goal(self, args: dict[str, Any]) -> dict[str, Any]:
545
- """Analyze a goal without starting a session."""
546
- goal = args["goal"]
547
-
548
- # Use LLM analyzer if available
549
- if self._llm_analyzer:
550
- try:
551
- analysis = await self._llm_analyzer.analyze_goal(goal)
552
- return {
553
- "goal": goal,
554
- "primary_domain": analysis.primary_domain,
555
- "secondary_domains": analysis.secondary_domains,
556
- "confidence": analysis.confidence,
557
- "detected_requirements": analysis.detected_requirements,
558
- "ambiguities": analysis.ambiguities,
559
- "suggested_questions": analysis.suggested_questions,
560
- "suggested_agents": analysis.suggested_agents,
561
- "analysis_method": "llm"
562
- }
563
- except Exception as e:
564
- logger.warning(f"LLM analysis failed: {e}")
565
-
566
- # Fallback to keyword-based analysis
567
- domains = self._builder._detect_domains(goal)
568
- return {
569
- "goal": goal,
570
- "detected_domains": list(domains),
571
- "analysis_method": "keyword"
572
- }
573
-
574
- async def _handle_recommend_agents(self, args: dict[str, Any]) -> dict[str, Any]:
575
- """Get agent recommendations."""
576
- domains = args["domains"]
577
- languages = args.get("languages", [])
578
- quality_focus = args.get("quality_focus", [])
579
-
580
- from .feedback import AdaptiveAgentGenerator
581
- from .generator import AgentGenerator
582
-
583
- # Use adaptive generator if feedback available
584
- if self._feedback_loop:
585
- adaptive_gen = AdaptiveAgentGenerator(self._feedback_loop.collector)
586
- context = {
587
- "domains": domains,
588
- "languages": languages,
589
- "quality_focus": quality_focus
590
- }
591
- recommendations = adaptive_gen.recommend_agents(context)
592
- else:
593
- # Use basic generator
594
- generator = AgentGenerator()
595
- recommendations = []
596
- for domain in domains:
597
- templates = generator._get_templates_for_domain(domain)
598
- for t in templates:
599
- recommendations.append({
600
- "template_id": t,
601
- "domain": domain,
602
- "confidence": 0.8
603
- })
604
-
605
- return {
606
- "recommendations": recommendations,
607
- "count": len(recommendations)
608
- }
609
-
610
- def _get_session(self, session_id: str):
611
- """Get session from memory or storage."""
612
- # Check memory first
613
- if session_id in self._sessions:
614
- return self._sessions[session_id]
615
-
616
- # Try loading from storage
617
- if self._storage:
618
- session = self._storage.load_session(session_id)
619
- if session:
620
- self._sessions[session_id] = session
621
- return session
622
-
623
- return None
624
-
625
-
626
- async def run_mcp_server():
627
- """Run the MCP server using stdio transport."""
628
- server = SocraticMCPServer()
629
-
630
- # Read from stdin, write to stdout
631
- reader = asyncio.StreamReader()
632
- protocol = asyncio.StreamReaderProtocol(reader)
633
- await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin)
634
-
635
- writer_transport, writer_protocol = await asyncio.get_event_loop().connect_write_pipe(
636
- asyncio.streams.FlowControlMixin, sys.stdout
637
- )
638
- writer = asyncio.StreamWriter(writer_transport, writer_protocol, reader, asyncio.get_event_loop())
639
-
640
- async def send_response(response: dict):
641
- """Send a JSON-RPC response."""
642
- data = json.dumps(response) + "\n"
643
- writer.write(data.encode())
644
- await writer.drain()
645
-
646
- async def handle_message(message: dict):
647
- """Handle an incoming JSON-RPC message."""
648
- method = message.get("method")
649
- params = message.get("params", {})
650
- msg_id = message.get("id")
651
-
652
- if method == "initialize":
653
- # MCP initialization
654
- response = {
655
- "jsonrpc": "2.0",
656
- "id": msg_id,
657
- "result": {
658
- "protocolVersion": MCP_VERSION,
659
- "capabilities": {
660
- "tools": {}
661
- },
662
- "serverInfo": {
663
- "name": "socratic-workflow-builder",
664
- "version": "1.0.0"
665
- }
666
- }
667
- }
668
- await send_response(response)
669
-
670
- elif method == "tools/list":
671
- # List available tools
672
- response = {
673
- "jsonrpc": "2.0",
674
- "id": msg_id,
675
- "result": {
676
- "tools": SOCRATIC_TOOLS
677
- }
678
- }
679
- await send_response(response)
680
-
681
- elif method == "tools/call":
682
- # Execute a tool
683
- tool_name = params.get("name")
684
- tool_args = params.get("arguments", {})
685
-
686
- result = await server.handle_tool_call(tool_name, tool_args)
687
-
688
- response = {
689
- "jsonrpc": "2.0",
690
- "id": msg_id,
691
- "result": {
692
- "content": [
693
- {
694
- "type": "text",
695
- "text": json.dumps(result, indent=2)
696
- }
697
- ]
698
- }
699
- }
700
- await send_response(response)
701
-
702
- elif method == "notifications/initialized":
703
- # Client initialized notification - no response needed
704
- pass
705
-
706
- else:
707
- # Unknown method
708
- response = {
709
- "jsonrpc": "2.0",
710
- "id": msg_id,
711
- "error": {
712
- "code": -32601,
713
- "message": f"Method not found: {method}"
714
- }
715
- }
716
- await send_response(response)
717
-
718
- # Main message loop
719
- logger.info("Socratic MCP Server starting...")
720
-
721
- while True:
722
- try:
723
- line = await reader.readline()
724
- if not line:
725
- break
726
-
727
- message = json.loads(line.decode().strip())
728
- await handle_message(message)
729
-
730
- except json.JSONDecodeError as e:
731
- logger.error(f"Invalid JSON: {e}")
732
- except Exception as e:
733
- logger.exception(f"Error processing message: {e}")
734
-
735
-
736
- def main():
737
- """Entry point for MCP server."""
738
- logging.basicConfig(
739
- level=logging.INFO,
740
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
741
- stream=sys.stderr # Log to stderr, MCP uses stdout
742
- )
743
-
744
- try:
745
- asyncio.run(run_mcp_server())
746
- except KeyboardInterrupt:
747
- logger.info("Server shutting down...")
748
-
749
-
750
- if __name__ == "__main__":
751
- main()