ralphx 0.2.2__py3-none-any.whl → 0.3.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 (45) hide show
  1. ralphx/__init__.py +1 -1
  2. ralphx/api/main.py +9 -1
  3. ralphx/api/routes/auth.py +730 -65
  4. ralphx/api/routes/config.py +3 -56
  5. ralphx/api/routes/export_import.py +795 -0
  6. ralphx/api/routes/loops.py +4 -4
  7. ralphx/api/routes/planning.py +19 -5
  8. ralphx/api/routes/projects.py +84 -2
  9. ralphx/api/routes/templates.py +115 -2
  10. ralphx/api/routes/workflows.py +22 -22
  11. ralphx/cli.py +21 -6
  12. ralphx/core/auth.py +346 -171
  13. ralphx/core/database.py +615 -167
  14. ralphx/core/executor.py +0 -3
  15. ralphx/core/loop.py +15 -2
  16. ralphx/core/loop_templates.py +69 -3
  17. ralphx/core/planning_service.py +109 -21
  18. ralphx/core/preview.py +9 -25
  19. ralphx/core/project_db.py +175 -75
  20. ralphx/core/project_export.py +469 -0
  21. ralphx/core/project_import.py +670 -0
  22. ralphx/core/sample_project.py +430 -0
  23. ralphx/core/templates.py +46 -9
  24. ralphx/core/workflow_executor.py +35 -5
  25. ralphx/core/workflow_export.py +606 -0
  26. ralphx/core/workflow_import.py +1149 -0
  27. ralphx/examples/sample_project/DESIGN.md +345 -0
  28. ralphx/examples/sample_project/README.md +37 -0
  29. ralphx/examples/sample_project/guardrails.md +57 -0
  30. ralphx/examples/sample_project/stories.jsonl +10 -0
  31. ralphx/mcp/__init__.py +6 -2
  32. ralphx/mcp/registry.py +3 -3
  33. ralphx/mcp/server.py +99 -29
  34. ralphx/mcp/tools/__init__.py +4 -0
  35. ralphx/mcp/tools/help.py +204 -0
  36. ralphx/mcp/tools/workflows.py +114 -32
  37. ralphx/mcp_server.py +6 -2
  38. ralphx/static/assets/index-0ovNnfOq.css +1 -0
  39. ralphx/static/assets/index-CY9s08ZB.js +251 -0
  40. ralphx/static/assets/index-CY9s08ZB.js.map +1 -0
  41. ralphx/static/index.html +14 -0
  42. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/METADATA +34 -12
  43. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/RECORD +45 -30
  44. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/WHEEL +0 -0
  45. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,345 @@
1
+ # Excuse Generator - Design Document
2
+
3
+ ## Overview
4
+
5
+ The Excuse Generator is a fun, lightweight web application that helps users generate creative excuses for various life situations. Whether you're late to a meeting, forgot a birthday, or missed a deadline, this app provides entertaining (and sometimes surprisingly convincing) excuses at the click of a button.
6
+
7
+ ## Goals
8
+
9
+ 1. **Entertainment First** - The app should be fun to use and generate amusing content
10
+ 2. **Simple & Fast** - No account required, instant results, mobile-friendly
11
+ 3. **Customizable Output** - Users can tune excuses by category and tone
12
+ 4. **Memorable** - Save favorites and track history for repeat use
13
+
14
+ ## Features
15
+
16
+ ### Core Features
17
+
18
+ #### 1. Excuse Generation
19
+ - Select a **category** for the situation:
20
+ - Late to work/meeting
21
+ - Missed deadline
22
+ - Forgot birthday/anniversary
23
+ - Didn't reply to message
24
+ - Skipped event/party
25
+ - Generic/Other
26
+ - Select a **tone** for the excuse:
27
+ - Professional (workplace appropriate, formal)
28
+ - Casual (friendly, conversational)
29
+ - Dramatic (over-the-top, theatrical)
30
+ - Absurd (ridiculous, comedy-focused)
31
+ - Click "Generate" to get a random excuse matching the criteria
32
+ - Each excuse is built from templates with variable substitution for variety
33
+
34
+ #### 2. Favorites System
35
+ - Click a heart icon to save an excuse to favorites
36
+ - View all saved favorites in a dedicated tab
37
+ - Remove excuses from favorites
38
+ - Favorites persist in the database
39
+
40
+ #### 3. Believability Rating
41
+ - After generating an excuse, rate it 1-5 stars for "believability"
42
+ - Ratings are stored and can be viewed in history
43
+ - Optional: Show average rating for excuse templates
44
+
45
+ #### 4. History View
46
+ - Browse all previously generated excuses
47
+ - Search by keyword
48
+ - Filter by category, tone, or date range
49
+ - See when each excuse was generated
50
+ - View ratings given
51
+
52
+ #### 5. Excuse of the Day
53
+ - Homepage displays a featured "Excuse of the Day"
54
+ - Rotates daily (seeded by date for consistency)
55
+ - Pulls from curated high-quality excuse templates
56
+
57
+ #### 6. Copy to Clipboard
58
+ - One-click button to copy excuse text
59
+ - Visual feedback (checkmark animation) on successful copy
60
+ - Works on mobile and desktop
61
+
62
+ ## Technical Architecture
63
+
64
+ ### Tech Stack
65
+
66
+ | Component | Technology |
67
+ |-----------|------------|
68
+ | Backend | FastAPI (Python 3.10+) |
69
+ | Database | SQLite |
70
+ | Frontend | Vanilla HTML/CSS/JS |
71
+ | Styling | Custom CSS (no framework) |
72
+
73
+ ### Project Structure
74
+
75
+ ```
76
+ excuse-generator/
77
+ ├── main.py # FastAPI application
78
+ ├── database.py # SQLite database operations
79
+ ├── models.py # Pydantic models
80
+ ├── templates.py # Excuse template data
81
+ ├── requirements.txt # Python dependencies
82
+ ├── data/
83
+ │ └── excuses.db # SQLite database file
84
+ └── static/
85
+ ├── index.html # Main SPA page
86
+ ├── styles.css # Application styles
87
+ └── app.js # Frontend JavaScript
88
+ ```
89
+
90
+ ### Database Schema
91
+
92
+ ```sql
93
+ -- Excuse templates (seed data)
94
+ CREATE TABLE excuse_templates (
95
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
96
+ category TEXT NOT NULL,
97
+ tone TEXT NOT NULL,
98
+ template TEXT NOT NULL,
99
+ variables TEXT, -- JSON array of variable names
100
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
101
+ );
102
+
103
+ -- Generated excuses (history)
104
+ CREATE TABLE generated_excuses (
105
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
106
+ template_id INTEGER REFERENCES excuse_templates(id),
107
+ excuse_text TEXT NOT NULL,
108
+ category TEXT NOT NULL,
109
+ tone TEXT NOT NULL,
110
+ rating INTEGER, -- 1-5 stars, NULL if not rated
111
+ is_favorite BOOLEAN DEFAULT FALSE,
112
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
113
+ );
114
+
115
+ -- Daily excuse selection
116
+ CREATE TABLE daily_excuse (
117
+ date TEXT PRIMARY KEY, -- YYYY-MM-DD format
118
+ template_id INTEGER REFERENCES excuse_templates(id),
119
+ excuse_text TEXT NOT NULL
120
+ );
121
+ ```
122
+
123
+ ### API Endpoints
124
+
125
+ #### GET /api/health
126
+ Health check endpoint.
127
+
128
+ **Response:**
129
+ ```json
130
+ {
131
+ "status": "healthy",
132
+ "version": "1.0.0"
133
+ }
134
+ ```
135
+
136
+ #### GET /api/generate
137
+ Generate a random excuse.
138
+
139
+ **Query Parameters:**
140
+ - `category` (string, optional): Filter by category
141
+ - `tone` (string, optional): Filter by tone
142
+
143
+ **Response:**
144
+ ```json
145
+ {
146
+ "id": 42,
147
+ "excuse": "My cat accidentally unplugged my alarm clock while chasing a ghost.",
148
+ "category": "late",
149
+ "tone": "absurd",
150
+ "template_id": 15
151
+ }
152
+ ```
153
+
154
+ #### POST /api/excuses/{id}/favorite
155
+ Toggle favorite status for an excuse.
156
+
157
+ **Response:**
158
+ ```json
159
+ {
160
+ "id": 42,
161
+ "is_favorite": true
162
+ }
163
+ ```
164
+
165
+ #### POST /api/excuses/{id}/rate
166
+ Rate an excuse.
167
+
168
+ **Request Body:**
169
+ ```json
170
+ {
171
+ "rating": 4
172
+ }
173
+ ```
174
+
175
+ **Response:**
176
+ ```json
177
+ {
178
+ "id": 42,
179
+ "rating": 4
180
+ }
181
+ ```
182
+
183
+ #### GET /api/favorites
184
+ Get all favorited excuses.
185
+
186
+ **Response:**
187
+ ```json
188
+ {
189
+ "favorites": [
190
+ {
191
+ "id": 42,
192
+ "excuse": "...",
193
+ "category": "late",
194
+ "tone": "absurd",
195
+ "rating": 4,
196
+ "created_at": "2024-01-15T10:30:00Z"
197
+ }
198
+ ]
199
+ }
200
+ ```
201
+
202
+ #### GET /api/history
203
+ Get excuse generation history.
204
+
205
+ **Query Parameters:**
206
+ - `category` (string, optional): Filter by category
207
+ - `tone` (string, optional): Filter by tone
208
+ - `search` (string, optional): Search in excuse text
209
+ - `limit` (int, default 50): Max results
210
+ - `offset` (int, default 0): Pagination offset
211
+
212
+ **Response:**
213
+ ```json
214
+ {
215
+ "excuses": [...],
216
+ "total": 150,
217
+ "limit": 50,
218
+ "offset": 0
219
+ }
220
+ ```
221
+
222
+ #### GET /api/excuse-of-the-day
223
+ Get today's featured excuse.
224
+
225
+ **Response:**
226
+ ```json
227
+ {
228
+ "excuse": "The traffic was terrible because a family of ducks decided to cross the highway.",
229
+ "category": "late",
230
+ "tone": "casual",
231
+ "date": "2024-01-15"
232
+ }
233
+ ```
234
+
235
+ #### GET /api/categories
236
+ Get list of available categories.
237
+
238
+ **Response:**
239
+ ```json
240
+ {
241
+ "categories": ["late", "deadline", "birthday", "message", "event", "other"]
242
+ }
243
+ ```
244
+
245
+ #### GET /api/tones
246
+ Get list of available tones.
247
+
248
+ **Response:**
249
+ ```json
250
+ {
251
+ "tones": ["professional", "casual", "dramatic", "absurd"]
252
+ }
253
+ ```
254
+
255
+ ## User Interface
256
+
257
+ ### Layout
258
+
259
+ The app uses a single-page layout with tabs:
260
+
261
+ ```
262
+ +------------------------------------------+
263
+ | EXCUSE GENERATOR [History] [*] |
264
+ +------------------------------------------+
265
+ | |
266
+ | Category: [Late ▼] Tone: [Casual ▼] |
267
+ | |
268
+ | +------------------------------------+ |
269
+ | | | |
270
+ | | "Sorry I'm late, my neighbor's | |
271
+ | | parrot learned to mimic my | |
272
+ | | alarm and kept hitting snooze." | |
273
+ | | | |
274
+ | | [Copy] [♡] Rating: ★★★★☆ | |
275
+ | +------------------------------------+ |
276
+ | |
277
+ | [ Generate New Excuse ] |
278
+ | |
279
+ +------------------------------------------+
280
+ | Excuse of the Day: |
281
+ | "My GPS took me through a time warp..." |
282
+ +------------------------------------------+
283
+ ```
284
+
285
+ ### Responsive Design
286
+
287
+ - Mobile-first approach
288
+ - Single column on phones (<768px)
289
+ - Centered card layout on tablets/desktop
290
+ - Touch-friendly buttons (44px minimum)
291
+
292
+ ### Color Scheme
293
+
294
+ - Primary: #6366F1 (Indigo)
295
+ - Background: #F8FAFC (Light gray)
296
+ - Card: #FFFFFF
297
+ - Text: #1E293B (Slate)
298
+ - Accent: #F59E0B (Amber for favorites)
299
+
300
+ ## Excuse Template System
301
+
302
+ Excuses are generated from templates with variable placeholders:
303
+
304
+ ```python
305
+ templates = [
306
+ {
307
+ "category": "late",
308
+ "tone": "absurd",
309
+ "template": "My {pet} accidentally {action} my {item}.",
310
+ "variables": {
311
+ "pet": ["cat", "dog", "hamster", "goldfish"],
312
+ "action": ["ate", "hid", "unplugged", "sat on"],
313
+ "item": ["car keys", "alarm clock", "shoes", "phone"]
314
+ }
315
+ }
316
+ ]
317
+ ```
318
+
319
+ The generator:
320
+ 1. Filters templates by category and tone
321
+ 2. Picks a random template
322
+ 3. Substitutes variables with random choices
323
+ 4. Returns the complete excuse
324
+
325
+ ## Error Handling
326
+
327
+ - All API errors return consistent JSON format:
328
+ ```json
329
+ {
330
+ "error": {
331
+ "code": "not_found",
332
+ "message": "Excuse not found"
333
+ }
334
+ }
335
+ ```
336
+ - Frontend shows user-friendly error toasts
337
+ - Database errors are logged server-side
338
+
339
+ ## Future Enhancements (Out of Scope)
340
+
341
+ - User accounts and cloud sync
342
+ - Sharing excuses on social media
343
+ - Community-submitted templates
344
+ - AI-generated excuses
345
+ - Multi-language support
@@ -0,0 +1,37 @@
1
+ # Excuse Generator
2
+
3
+ A fun web app that generates creative excuses for any situation.
4
+
5
+ This is a sample project automatically created by RalphX to help you get started.
6
+ Location: `~/.ralphx/samples/excuse-generator/`
7
+
8
+ ## What's Included
9
+
10
+ - **DESIGN.md** - Complete design document with features, architecture, and API specs
11
+ - **stories.jsonl** - 10 user stories ready for implementation
12
+ - **guardrails.md** - Project-specific coding guidelines
13
+
14
+ ## How to Use
15
+
16
+ 1. Open RalphX dashboard at http://localhost:8765
17
+ 2. Click on "Excuse-Generator" project
18
+ 3. Open the pre-created workflow "Build Excuse Generator"
19
+ 4. Start the Implementation step and watch Claude build your excuse generator!
20
+
21
+ ## The App
22
+
23
+ When built, the Excuse Generator will feature:
24
+
25
+ - **Category selection** - Late to work, missed deadline, forgot birthday, etc.
26
+ - **Tone modes** - Professional, casual, dramatic, absurd
27
+ - **Favorites** - Save your best excuses
28
+ - **Believability rating** - Rate how convincing each excuse is
29
+ - **History** - Browse all your generated excuses
30
+ - **Excuse of the day** - Featured excuse on the homepage
31
+ - **Copy to clipboard** - One-click sharing
32
+
33
+ ## Tech Stack
34
+
35
+ - **Backend**: FastAPI (Python)
36
+ - **Database**: SQLite
37
+ - **Frontend**: Vanilla HTML/CSS/JS (no framework)
@@ -0,0 +1,57 @@
1
+ # Excuse Generator - Project Guardrails
2
+
3
+ ## Content Guidelines
4
+
5
+ - Keep all excuses **PG-rated** and workplace-appropriate
6
+ - No offensive, discriminatory, or harmful content
7
+ - Excuses should be clearly fictional/humorous - avoid anything that could be used maliciously
8
+ - No references to violence, illegal activities, or sensitive topics
9
+
10
+ ## Technical Guidelines
11
+
12
+ ### Frontend
13
+
14
+ - Use **vanilla JavaScript only** - no React, Vue, or other frameworks
15
+ - No npm dependencies for the frontend
16
+ - Keep JavaScript in a single `app.js` file
17
+ - Use modern ES6+ syntax (const/let, arrow functions, template literals)
18
+ - CSS should be custom - no Tailwind, Bootstrap, or CSS frameworks
19
+
20
+ ### Backend
21
+
22
+ - Use **FastAPI** for the Python backend
23
+ - SQLite for database - no PostgreSQL, MySQL, or other databases
24
+ - Keep dependencies minimal - only what's in requirements.txt
25
+ - Use Pydantic models for request/response validation
26
+
27
+ ### Code Style
28
+
29
+ - Follow PEP 8 for Python code
30
+ - Use meaningful variable and function names
31
+ - Add docstrings to all public functions
32
+ - Keep functions small and focused (< 30 lines ideally)
33
+
34
+ ### Database
35
+
36
+ - All data stored in SQLite at `data/excuses.db`
37
+ - Use parameterized queries to prevent SQL injection
38
+ - Include proper indexes for frequently queried columns
39
+
40
+ ### Performance
41
+
42
+ - Page load should be instant (< 1 second)
43
+ - No unnecessary API calls
44
+ - Cache static assets appropriately
45
+
46
+ ### Accessibility
47
+
48
+ - Use semantic HTML elements
49
+ - Include ARIA labels where needed
50
+ - Ensure color contrast meets WCAG AA standards
51
+ - All interactive elements must be keyboard accessible
52
+
53
+ ### Mobile Support
54
+
55
+ - Mobile-first responsive design
56
+ - Touch targets at least 44x44 pixels
57
+ - No horizontal scrolling on mobile devices
@@ -0,0 +1,10 @@
1
+ {"id": "EXC-001", "title": "Set up project structure", "description": "Initialize the FastAPI application with SQLite database and static file serving for the Excuse Generator web app.", "category": "infrastructure", "priority": "high", "acceptance_criteria": ["FastAPI app runs on localhost:8000", "SQLite database file created at data/excuses.db", "Static files served from /static path", "Health check endpoint at /api/health returns 200 OK", "requirements.txt includes fastapi, uvicorn, and other dependencies"], "dependencies": []}
2
+ {"id": "EXC-002", "title": "Create database schema and models", "description": "Design and implement the SQLite database schema with tables for excuse templates, generated excuses, and daily excuse tracking. Create corresponding Pydantic models.", "category": "backend", "priority": "high", "acceptance_criteria": ["excuse_templates table stores category, tone, template text, and variables", "generated_excuses table tracks history with ratings and favorites", "daily_excuse table stores the excuse of the day", "Pydantic models defined for all database entities", "Database initialization creates tables on startup"], "dependencies": ["EXC-001"]}
3
+ {"id": "EXC-003", "title": "Build excuse template system", "description": "Implement the excuse template system with variable substitution. Create a collection of 20+ excuse templates across all categories and tones.", "category": "backend", "priority": "high", "acceptance_criteria": ["Template system supports variable placeholders like {pet}, {action}, {item}", "Variables are randomly substituted from predefined lists", "At least 20 templates covering all 6 categories", "At least 5 templates per tone (professional, casual, dramatic, absurd)", "Templates seeded into database on first run"], "dependencies": ["EXC-002"]}
4
+ {"id": "EXC-004", "title": "Implement generate excuse endpoint", "description": "Create the /api/generate endpoint that returns a randomly generated excuse based on optional category and tone filters.", "category": "backend", "priority": "high", "acceptance_criteria": ["GET /api/generate returns a random excuse", "Optional category query param filters templates", "Optional tone query param filters templates", "Response includes excuse text, category, tone, and ID", "Generated excuse is saved to history"], "dependencies": ["EXC-003"]}
5
+ {"id": "EXC-005", "title": "Create homepage UI", "description": "Build the main homepage with category/tone selectors, generate button, and excuse display card using vanilla HTML/CSS/JS.", "category": "frontend", "priority": "high", "acceptance_criteria": ["Dropdown selectors for category and tone", "Generate button triggers API call", "Excuse displays in a styled card", "Loading state shown while generating", "Responsive design works on mobile and desktop", "Matches color scheme from design doc"], "dependencies": ["EXC-004"]}
6
+ {"id": "EXC-006", "title": "Add favorites functionality", "description": "Implement the ability to save excuses to favorites, view saved favorites, and remove from favorites.", "category": "feature", "priority": "medium", "acceptance_criteria": ["Heart icon toggles favorite status", "POST /api/excuses/{id}/favorite toggles is_favorite", "GET /api/favorites returns all favorited excuses", "Favorites tab shows saved excuses", "Visual indication when excuse is favorited", "Can unfavorite from the favorites view"], "dependencies": ["EXC-005"]}
7
+ {"id": "EXC-007", "title": "Implement believability rating", "description": "Add a 1-5 star rating system for excuses to track how believable users find them.", "category": "feature", "priority": "medium", "acceptance_criteria": ["Star rating component displays below excuse", "Clicking a star submits rating via API", "POST /api/excuses/{id}/rate saves the rating", "Rating persists and shows in history view", "Visual feedback on rating selection"], "dependencies": ["EXC-005"]}
8
+ {"id": "EXC-008", "title": "Build history view", "description": "Create a history tab showing all previously generated excuses with search and filtering capabilities.", "category": "feature", "priority": "medium", "acceptance_criteria": ["GET /api/history returns paginated excuse history", "History tab displays excuses in chronological order", "Search box filters by excuse text", "Dropdown filters for category and tone", "Pagination or infinite scroll for large history", "Shows rating and favorite status for each item"], "dependencies": ["EXC-006", "EXC-007"]}
9
+ {"id": "EXC-009", "title": "Build excuse of the day feature", "description": "Implement the daily featured excuse that rotates each day and displays prominently on the homepage.", "category": "feature", "priority": "medium", "acceptance_criteria": ["GET /api/excuse-of-the-day returns today's featured excuse", "Daily excuse is deterministic based on date (seeded random)", "Excuse of the day displays at bottom of homepage", "Different excuse shown each calendar day", "Uses curated selection of best templates"], "dependencies": ["EXC-003"]}
10
+ {"id": "EXC-010", "title": "Add copy to clipboard", "description": "Implement one-click copy functionality with visual feedback for easy sharing of excuses.", "category": "feature", "priority": "low", "acceptance_criteria": ["Copy button appears on each excuse card", "Clicking copies excuse text to clipboard", "Button shows checkmark animation on success", "Works on both mobile and desktop browsers", "Fallback for browsers without clipboard API"], "dependencies": ["EXC-005"]}
ralphx/mcp/__init__.py CHANGED
@@ -3,8 +3,12 @@
3
3
  This module provides a modular MCP server implementation that exposes RalphX
4
4
  functionality as tools that Claude Code can use.
5
5
 
6
- Usage:
7
- claude mcp add ralphx -- ralphx mcp
6
+ Usage (Linux/Mac):
7
+ claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- "$(which ralphx)" mcp
8
+ # Mac zsh: if "which" fails, run: conda init zsh && source ~/.zshrc
9
+
10
+ Usage (Windows - find path first with: where.exe ralphx):
11
+ claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- C:\\path\\to\\ralphx.exe mcp
8
12
  """
9
13
 
10
14
  from ralphx.mcp.server import MCPServer
ralphx/mcp/registry.py CHANGED
@@ -39,11 +39,11 @@ class ToolRegistry:
39
39
  """Check if a tool is registered."""
40
40
  return name in self._tools
41
41
 
42
- def call(self, name: str, **kwargs) -> Any:
42
+ def call(self, tool_name: str, **kwargs) -> Any:
43
43
  """Call a tool by name with arguments."""
44
- tool = self._tools.get(name)
44
+ tool = self._tools.get(tool_name)
45
45
  if not tool:
46
- raise KeyError(f"Unknown tool: {name}")
46
+ raise KeyError(f"Unknown tool: {tool_name}")
47
47
  return tool.handler(**kwargs)
48
48
 
49
49
  def get_definitions(self) -> list[dict]:
ralphx/mcp/server.py CHANGED
@@ -16,6 +16,26 @@ from ralphx.mcp.tools import get_all_tools
16
16
  # Version from pyproject.toml
17
17
  VERSION = "0.1.5"
18
18
 
19
+ # MCP protocol version we support
20
+ PROTOCOL_VERSION = "2025-03-26"
21
+
22
+ # Server instructions for Claude - explains what RalphX is and how to use it
23
+ SERVER_INSTRUCTIONS = """RalphX is an Autonomous AI Loop Orchestration system. It helps you run autonomous AI workflows.
24
+
25
+ Key concepts:
26
+ - Project: A directory registered with RalphX (your codebase)
27
+ - Loop: An autonomous workflow defined in YAML that runs repeatedly
28
+ - Work Item: Data generated/consumed by loops (user stories, tasks, etc.)
29
+ - Workflow: A multi-step pipeline (research → implement → test)
30
+
31
+ Start with ralphx_help to see common workflows and all 67 available tools.
32
+
33
+ Quick start:
34
+ 1. ralphx_list_projects - See registered projects
35
+ 2. ralphx_list_loops - See available loops in a project
36
+ 3. ralphx_start_loop - Run a loop
37
+ 4. ralphx_list_runs - Monitor progress"""
38
+
19
39
 
20
40
  class MCPServer:
21
41
  """MCP protocol handler for RalphX.
@@ -28,13 +48,11 @@ class MCPServer:
28
48
  """Initialize the MCP server with all registered tools."""
29
49
  self.registry = ToolRegistry()
30
50
  self.registry.register_all(get_all_tools())
51
+ self._initialized = False # Track whether initialize handshake completed
31
52
 
32
53
  def run(self) -> None:
33
54
  """Run the MCP server, reading from stdin and writing to stdout."""
34
- # Send initialization message
35
- self._send_init()
36
-
37
- # Process messages
55
+ # Process messages - wait for client to initiate
38
56
  try:
39
57
  for line in sys.stdin:
40
58
  line = line.strip()
@@ -55,22 +73,6 @@ class MCPServer:
55
73
  # Exit gracefully
56
74
  pass
57
75
 
58
- def _send_init(self) -> None:
59
- """Send MCP initialization message."""
60
- self._send({
61
- "jsonrpc": "2.0",
62
- "method": "initialized",
63
- "params": {
64
- "serverInfo": {
65
- "name": "ralphx",
66
- "version": VERSION,
67
- },
68
- "capabilities": {
69
- "tools": True,
70
- },
71
- },
72
- })
73
-
74
76
  def _send(self, message: dict) -> None:
75
77
  """Send a JSON-RPC message to stdout."""
76
78
  print(json.dumps(message), flush=True)
@@ -92,11 +94,23 @@ class MCPServer:
92
94
  params = message.get("params", {})
93
95
  msg_id = message.get("id")
94
96
 
97
+ # Per MCP spec: ping is allowed before initialization
98
+ if method == "ping":
99
+ return {
100
+ "jsonrpc": "2.0",
101
+ "id": msg_id,
102
+ "result": {},
103
+ }
104
+
95
105
  if method == "initialize":
106
+ # Respond to client's initialize request per MCP spec
107
+ # Client will then send notifications/initialized to signal ready
108
+ self._initialized = True
96
109
  return {
97
110
  "jsonrpc": "2.0",
98
111
  "id": msg_id,
99
112
  "result": {
113
+ "protocolVersion": PROTOCOL_VERSION,
100
114
  "serverInfo": {
101
115
  "name": "ralphx",
102
116
  "version": VERSION,
@@ -106,6 +120,19 @@ class MCPServer:
106
120
  "listChanged": False,
107
121
  },
108
122
  },
123
+ "instructions": SERVER_INSTRUCTIONS,
124
+ },
125
+ }
126
+
127
+ # Per MCP spec: most requests require initialization first
128
+ # (ping and initialize are handled above)
129
+ if not self._initialized and msg_id is not None:
130
+ return {
131
+ "jsonrpc": "2.0",
132
+ "id": msg_id,
133
+ "error": {
134
+ "code": -32600,
135
+ "message": "Server not initialized. Send 'initialize' request first.",
109
136
  },
110
137
  }
111
138
 
@@ -123,11 +150,13 @@ class MCPServer:
123
150
  arguments = params.get("arguments", {})
124
151
 
125
152
  if not self.registry.has(tool_name):
153
+ # Per MCP spec: unknown tool is Invalid params (-32602), not Method not found
154
+ # because tools/call method exists, the tool name is a parameter
126
155
  return {
127
156
  "jsonrpc": "2.0",
128
157
  "id": msg_id,
129
158
  "error": {
130
- "code": -32601,
159
+ "code": -32602,
131
160
  "message": f"Unknown tool: {tool_name}",
132
161
  },
133
162
  }
@@ -135,11 +164,27 @@ class MCPServer:
135
164
  try:
136
165
  result = self.registry.call(tool_name, **arguments)
137
166
 
138
- # Format result
139
- if isinstance(result, dict):
140
- result_text = json.dumps(result, indent=2)
141
- else:
142
- result_text = str(result)
167
+ # Format result - handle serialization errors gracefully
168
+ try:
169
+ if isinstance(result, dict):
170
+ result_text = json.dumps(result, indent=2)
171
+ else:
172
+ result_text = str(result)
173
+ except (TypeError, ValueError) as serialize_err:
174
+ # Non-JSON-serializable result - return as tool error, not protocol error
175
+ return {
176
+ "jsonrpc": "2.0",
177
+ "id": msg_id,
178
+ "result": {
179
+ "content": [
180
+ {
181
+ "type": "text",
182
+ "text": f"Tool returned non-serializable result: {serialize_err}",
183
+ }
184
+ ],
185
+ "isError": True,
186
+ },
187
+ }
143
188
 
144
189
  return {
145
190
  "jsonrpc": "2.0",
@@ -169,18 +214,43 @@ class MCPServer:
169
214
  },
170
215
  }
171
216
  except Exception as e:
217
+ # Unexpected tool execution error - return as tool error for LLM recovery
172
218
  return {
173
219
  "jsonrpc": "2.0",
174
220
  "id": msg_id,
175
- "error": {
176
- "code": -32603,
177
- "message": str(e),
221
+ "result": {
222
+ "content": [
223
+ {
224
+ "type": "text",
225
+ "text": f"Tool execution error: {e}",
226
+ }
227
+ ],
228
+ "isError": True,
178
229
  },
179
230
  }
180
231
 
232
+ if method == "notifications/initialized":
233
+ # Client signals it's ready for normal operations
234
+ # No response needed for notifications
235
+ return None
236
+
181
237
  if method == "notifications/cancelled":
238
+ # Client cancelled a request
182
239
  return None
183
240
 
241
+ # Unknown method handling per JSON-RPC 2.0:
242
+ # - If it has an id, it's a request and MUST return error
243
+ # - If no id, it's a notification and can be silently ignored
244
+ if msg_id is not None:
245
+ return {
246
+ "jsonrpc": "2.0",
247
+ "id": msg_id,
248
+ "error": {
249
+ "code": -32601,
250
+ "message": f"Method not found: {method}",
251
+ },
252
+ }
253
+ # Notification we don't handle - ignore silently per JSON-RPC spec
184
254
  return None
185
255
 
186
256