Vignesh19 commited on
Commit
3b28b41
Β·
1 Parent(s): 4ee38ca

Add MCP task manager server with Gradio interface

Browse files
Files changed (4) hide show
  1. app.py +61 -0
  2. requirements.txt +3 -0
  3. task_manager.py +501 -0
  4. tasks.json +65 -0
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ from task_manager import TaskManagerServer
4
+ import asyncio
5
+
6
+ # Create MCP server instance
7
+ server = TaskManagerServer()
8
+
9
+ async def call_mcp_tool(tool_name, arguments_json):
10
+ """Call MCP tools via web interface"""
11
+ try:
12
+ args = json.loads(arguments_json) if arguments_json else {}
13
+
14
+ if tool_name == "add_task":
15
+ result = await server.add_task(args.get("title", ""), args.get("description", ""))
16
+ elif tool_name == "list_tasks":
17
+ result = await server.list_tasks()
18
+ elif tool_name == "complete_task":
19
+ result = await server.complete_task(args.get("task_id", 0))
20
+ elif tool_name == "delete_task":
21
+ result = await server.delete_task(args.get("task_id", 0))
22
+ else:
23
+ return "Unknown tool"
24
+
25
+ return json.dumps(result, indent=2)
26
+ except Exception as e:
27
+ return f"Error: {str(e)}"
28
+
29
+ def sync_call_mcp_tool(tool_name, arguments_json):
30
+ """Sync wrapper for async function"""
31
+ return asyncio.run(call_mcp_tool(tool_name, arguments_json))
32
+
33
+ # Create Gradio interface
34
+ with gr.Blocks(title="Task Manager MCP Server") as demo:
35
+ gr.Markdown("# Task Manager MCP Server")
36
+ gr.Markdown("Test your MCP server tools through this web interface")
37
+
38
+ with gr.Row():
39
+ tool_dropdown = gr.Dropdown(
40
+ choices=["add_task", "list_tasks", "complete_task", "delete_task"],
41
+ label="Select Tool",
42
+ value="list_tasks"
43
+ )
44
+
45
+ arguments_input = gr.Textbox(
46
+ label="Arguments (JSON)",
47
+ placeholder='{"title": "My Task", "description": "Task description"}',
48
+ lines=3
49
+ )
50
+
51
+ submit_btn = gr.Button("Call Tool")
52
+ output = gr.Textbox(label="Result", lines=10)
53
+
54
+ submit_btn.click(
55
+ sync_call_mcp_tool,
56
+ inputs=[tool_dropdown, arguments_input],
57
+ outputs=output
58
+ )
59
+
60
+ if __name__ == "__main__":
61
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ mcp
3
+ pydantic
task_manager.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Task Manager MCP Server - A comprehensive example for learning MCP development
3
+
4
+ This server provides task management functionality with multiple tools:
5
+ - Create, read, update, delete tasks
6
+ - List tasks with filtering
7
+ - Mark tasks as complete/incomplete
8
+ - Add tags and priorities
9
+ - Search functionality
10
+
11
+ Perfect for learning MCP concepts and extending with new features!
12
+ """
13
+
14
+ import json
15
+ import os
16
+ from datetime import datetime
17
+ from typing import Any, Dict, List, Optional
18
+ from enum import Enum
19
+
20
+ from mcp.server.fastmcp import FastMCP
21
+ from pydantic import BaseModel
22
+
23
+ # Initialize FastMCP server
24
+ mcp = FastMCP("task-manager")
25
+
26
+ # Data models
27
+ class Priority(str, Enum):
28
+ LOW = "low"
29
+ MEDIUM = "medium"
30
+ HIGH = "high"
31
+ URGENT = "urgent"
32
+
33
+ class TaskStatus(str, Enum):
34
+ PENDING = "pending"
35
+ IN_PROGRESS = "in_progress"
36
+ COMPLETED = "completed"
37
+ CANCELLED = "cancelled"
38
+
39
+ class Task(BaseModel):
40
+ id: int
41
+ title: str
42
+ description: str = ""
43
+ status: TaskStatus = TaskStatus.PENDING
44
+ priority: Priority = Priority.MEDIUM
45
+ tags: List[str] = []
46
+ created_at: str
47
+ updated_at: str
48
+ due_date: Optional[str] = None
49
+ completed_at: Optional[str] = None
50
+
51
+ # In-memory storage (in a real app, you'd use a database)
52
+ tasks_db: Dict[int, Task] = {}
53
+ next_task_id = 1
54
+
55
+ # File-based persistence
56
+ DATA_FILE = "tasks.json"
57
+
58
+ def load_tasks():
59
+ """Load tasks from file if it exists"""
60
+ global tasks_db, next_task_id
61
+
62
+ if os.path.exists(DATA_FILE):
63
+ try:
64
+ with open(DATA_FILE, 'r') as f:
65
+ data = json.load(f)
66
+ tasks_db = {int(k): Task(**v) for k, v in data.get('tasks', {}).items()}
67
+ next_task_id = data.get('next_id', 1)
68
+ except Exception as e:
69
+ print(f"Error loading tasks: {e}")
70
+
71
+ def save_tasks():
72
+ """Save tasks to file"""
73
+ try:
74
+ data = {
75
+ 'tasks': {str(k): v.model_dump() for k, v in tasks_db.items()},
76
+ 'next_id': next_task_id
77
+ }
78
+ with open(DATA_FILE, 'w') as f:
79
+ json.dump(data, f, indent=2)
80
+ except Exception as e:
81
+ print(f"Error saving tasks: {e}")
82
+
83
+ def get_current_time() -> str:
84
+ """Get current timestamp as ISO string"""
85
+ return datetime.now().isoformat()
86
+
87
+ # Load existing tasks on startup
88
+ load_tasks()
89
+
90
+ @mcp.tool()
91
+ def create_task(
92
+ title: str,
93
+ description: str = "",
94
+ priority: str = "medium",
95
+ tags: str = "",
96
+ due_date: str = ""
97
+ ) -> str:
98
+ """Create a new task.
99
+
100
+ Args:
101
+ title: Task title (required)
102
+ description: Detailed description of the task
103
+ priority: Task priority (low, medium, high, urgent)
104
+ tags: Comma-separated list of tags
105
+ due_date: Due date in YYYY-MM-DD format
106
+ """
107
+ global next_task_id
108
+
109
+ try:
110
+ # Parse priority
111
+ task_priority = Priority(priority.lower())
112
+ except ValueError:
113
+ return f"Invalid priority '{priority}'. Use: low, medium, high, urgent"
114
+
115
+ # Parse tags
116
+ task_tags = [tag.strip() for tag in tags.split(",") if tag.strip()]
117
+
118
+ # Validate due date if provided
119
+ task_due_date = None
120
+ if due_date:
121
+ try:
122
+ datetime.fromisoformat(due_date)
123
+ task_due_date = due_date
124
+ except ValueError:
125
+ return f"Invalid due date format. Use YYYY-MM-DD"
126
+
127
+ # Create task
128
+ current_time = get_current_time()
129
+ task = Task(
130
+ id=next_task_id,
131
+ title=title,
132
+ description=description,
133
+ priority=task_priority,
134
+ tags=task_tags,
135
+ created_at=current_time,
136
+ updated_at=current_time,
137
+ due_date=task_due_date
138
+ )
139
+
140
+ tasks_db[next_task_id] = task
141
+ next_task_id += 1
142
+ save_tasks()
143
+
144
+ return f"βœ… Created task #{task.id}: {task.title}"
145
+
146
+ @mcp.tool()
147
+ def list_tasks(
148
+ status: str = "all",
149
+ priority: str = "all",
150
+ tag: str = "",
151
+ limit: int = 10
152
+ ) -> str:
153
+ """List tasks with optional filtering.
154
+
155
+ Args:
156
+ status: Filter by status (all, pending, in_progress, completed, cancelled)
157
+ priority: Filter by priority (all, low, medium, high, urgent)
158
+ tag: Filter by tag (exact match)
159
+ limit: Maximum number of tasks to return
160
+ """
161
+ if not tasks_db:
162
+ return "πŸ“ No tasks found. Create your first task!"
163
+
164
+ filtered_tasks = list(tasks_db.values())
165
+
166
+ # Filter by status
167
+ if status != "all":
168
+ try:
169
+ status_filter = TaskStatus(status)
170
+ filtered_tasks = [t for t in filtered_tasks if t.status == status_filter]
171
+ except ValueError:
172
+ return f"Invalid status '{status}'. Use: all, pending, in_progress, completed, cancelled"
173
+
174
+ # Filter by priority
175
+ if priority != "all":
176
+ try:
177
+ priority_filter = Priority(priority)
178
+ filtered_tasks = [t for t in filtered_tasks if t.priority == priority_filter]
179
+ except ValueError:
180
+ return f"Invalid priority '{priority}'. Use: all, low, medium, high, urgent"
181
+
182
+ # Filter by tag
183
+ if tag:
184
+ filtered_tasks = [t for t in filtered_tasks if tag in t.tags]
185
+
186
+ # Sort by creation date (newest first)
187
+ filtered_tasks.sort(key=lambda x: x.created_at, reverse=True)
188
+
189
+ # Apply limit
190
+ filtered_tasks = filtered_tasks[:limit]
191
+
192
+ if not filtered_tasks:
193
+ return "πŸ“ No tasks match your filters."
194
+
195
+ # Format output
196
+ result = f"πŸ“‹ Found {len(filtered_tasks)} task(s):\n\n"
197
+
198
+ for task in filtered_tasks:
199
+ status_emoji = {
200
+ TaskStatus.PENDING: "⏳",
201
+ TaskStatus.IN_PROGRESS: "πŸ”„",
202
+ TaskStatus.COMPLETED: "βœ…",
203
+ TaskStatus.CANCELLED: "❌"
204
+ }
205
+
206
+ priority_emoji = {
207
+ Priority.LOW: "🟒",
208
+ Priority.MEDIUM: "🟑",
209
+ Priority.HIGH: "🟠",
210
+ Priority.URGENT: "πŸ”΄"
211
+ }
212
+
213
+ tags_str = f" #{' #'.join(task.tags)}" if task.tags else ""
214
+ due_str = f" (Due: {task.due_date})" if task.due_date else ""
215
+
216
+ result += f"{status_emoji[task.status]} #{task.id} - {task.title}\n"
217
+ result += f" {priority_emoji[task.priority]} {task.priority.value.title()}{due_str}{tags_str}\n"
218
+ if task.description:
219
+ result += f" πŸ“„ {task.description}\n"
220
+ result += "\n"
221
+
222
+ return result.strip()
223
+
224
+ @mcp.tool()
225
+ def get_task(task_id: int) -> str:
226
+ """Get detailed information about a specific task.
227
+
228
+ Args:
229
+ task_id: The ID of the task to retrieve
230
+ """
231
+ if task_id not in tasks_db:
232
+ return f"❌ Task #{task_id} not found."
233
+
234
+ task = tasks_db[task_id]
235
+
236
+ status_emoji = {
237
+ TaskStatus.PENDING: "⏳",
238
+ TaskStatus.IN_PROGRESS: "πŸ”„",
239
+ TaskStatus.COMPLETED: "βœ…",
240
+ TaskStatus.CANCELLED: "❌"
241
+ }
242
+
243
+ priority_emoji = {
244
+ Priority.LOW: "🟒",
245
+ Priority.MEDIUM: "🟑",
246
+ Priority.HIGH: "🟠",
247
+ Priority.URGENT: "πŸ”΄"
248
+ }
249
+
250
+ result = f"πŸ“‹ Task #{task.id}\n"
251
+ result += f"Title: {task.title}\n"
252
+ result += f"Status: {status_emoji[task.status]} {task.status.value.replace('_', ' ').title()}\n"
253
+ result += f"Priority: {priority_emoji[task.priority]} {task.priority.value.title()}\n"
254
+
255
+ if task.description:
256
+ result += f"Description: {task.description}\n"
257
+
258
+ if task.tags:
259
+ result += f"Tags: #{' #'.join(task.tags)}\n"
260
+
261
+ if task.due_date:
262
+ result += f"Due Date: {task.due_date}\n"
263
+
264
+ result += f"Created: {task.created_at}\n"
265
+ result += f"Updated: {task.updated_at}\n"
266
+
267
+ if task.completed_at:
268
+ result += f"Completed: {task.completed_at}\n"
269
+
270
+ return result
271
+
272
+ @mcp.tool()
273
+ def update_task(
274
+ task_id: int,
275
+ title: str = "",
276
+ description: str = "",
277
+ status: str = "",
278
+ priority: str = "",
279
+ tags: str = "",
280
+ due_date: str = ""
281
+ ) -> str:
282
+ """Update an existing task. Only provided fields will be updated.
283
+
284
+ Args:
285
+ task_id: The ID of the task to update
286
+ title: New task title
287
+ description: New task description
288
+ status: New status (pending, in_progress, completed, cancelled)
289
+ priority: New priority (low, medium, high, urgent)
290
+ tags: New comma-separated list of tags
291
+ due_date: New due date in YYYY-MM-DD format (use 'none' to clear)
292
+ """
293
+ if task_id not in tasks_db:
294
+ return f"❌ Task #{task_id} not found."
295
+
296
+ task = tasks_db[task_id]
297
+ updated_fields = []
298
+
299
+ # Update title
300
+ if title:
301
+ task.title = title
302
+ updated_fields.append("title")
303
+
304
+ # Update description
305
+ if description:
306
+ task.description = description
307
+ updated_fields.append("description")
308
+
309
+ # Update status
310
+ if status:
311
+ try:
312
+ new_status = TaskStatus(status)
313
+ task.status = new_status
314
+ updated_fields.append("status")
315
+
316
+ # Set completion time if marking as completed
317
+ if new_status == TaskStatus.COMPLETED and not task.completed_at:
318
+ task.completed_at = get_current_time()
319
+ elif new_status != TaskStatus.COMPLETED:
320
+ task.completed_at = None
321
+
322
+ except ValueError:
323
+ return f"Invalid status '{status}'. Use: pending, in_progress, completed, cancelled"
324
+
325
+ # Update priority
326
+ if priority:
327
+ try:
328
+ task.priority = Priority(priority)
329
+ updated_fields.append("priority")
330
+ except ValueError:
331
+ return f"Invalid priority '{priority}'. Use: low, medium, high, urgent"
332
+
333
+ # Update tags
334
+ if tags:
335
+ if tags.lower() == "none":
336
+ task.tags = []
337
+ else:
338
+ task.tags = [tag.strip() for tag in tags.split(",") if tag.strip()]
339
+ updated_fields.append("tags")
340
+
341
+ # Update due date
342
+ if due_date:
343
+ if due_date.lower() == "none":
344
+ task.due_date = None
345
+ else:
346
+ try:
347
+ datetime.fromisoformat(due_date)
348
+ task.due_date = due_date
349
+ except ValueError:
350
+ return f"Invalid due date format. Use YYYY-MM-DD or 'none' to clear"
351
+ updated_fields.append("due_date")
352
+
353
+ if not updated_fields:
354
+ return f"❌ No fields provided to update for task #{task_id}."
355
+
356
+ # Update timestamp
357
+ task.updated_at = get_current_time()
358
+ save_tasks()
359
+
360
+ return f"βœ… Updated task #{task_id}: {', '.join(updated_fields)}"
361
+
362
+ @mcp.tool()
363
+ def delete_task(task_id: int) -> str:
364
+ """Delete a task permanently.
365
+
366
+ Args:
367
+ task_id: The ID of the task to delete
368
+ """
369
+ if task_id not in tasks_db:
370
+ return f"❌ Task #{task_id} not found."
371
+
372
+ task = tasks_db[task_id]
373
+ del tasks_db[task_id]
374
+ save_tasks()
375
+
376
+ return f"πŸ—‘οΈ Deleted task #{task_id}: {task.title}"
377
+
378
+ @mcp.tool()
379
+ def search_tasks(query: str, limit: int = 10) -> str:
380
+ """Search tasks by title, description, or tags.
381
+
382
+ Args:
383
+ query: Search query (searches in title, description, and tags)
384
+ limit: Maximum number of results to return
385
+ """
386
+ if not tasks_db:
387
+ return "πŸ“ No tasks to search."
388
+
389
+ query_lower = query.lower()
390
+ matching_tasks = []
391
+
392
+ for task in tasks_db.values():
393
+ # Search in title, description, and tags
394
+ if (query_lower in task.title.lower() or
395
+ query_lower in task.description.lower() or
396
+ any(query_lower in tag.lower() for tag in task.tags)):
397
+ matching_tasks.append(task)
398
+
399
+ if not matching_tasks:
400
+ return f"πŸ” No tasks found matching '{query}'."
401
+
402
+ # Sort by relevance (title matches first, then description, then tags)
403
+ def relevance_score(task):
404
+ score = 0
405
+ if query_lower in task.title.lower():
406
+ score += 3
407
+ if query_lower in task.description.lower():
408
+ score += 2
409
+ if any(query_lower in tag.lower() for tag in task.tags):
410
+ score += 1
411
+ return score
412
+
413
+ matching_tasks.sort(key=relevance_score, reverse=True)
414
+ matching_tasks = matching_tasks[:limit]
415
+
416
+ result = f"πŸ” Found {len(matching_tasks)} task(s) matching '{query}':\n\n"
417
+
418
+ for task in matching_tasks:
419
+ status_emoji = {
420
+ TaskStatus.PENDING: "⏳",
421
+ TaskStatus.IN_PROGRESS: "πŸ”„",
422
+ TaskStatus.COMPLETED: "βœ…",
423
+ TaskStatus.CANCELLED: "❌"
424
+ }
425
+
426
+ result += f"{status_emoji[task.status]} #{task.id} - {task.title}\n"
427
+ if task.description:
428
+ result += f" πŸ“„ {task.description[:100]}{'...' if len(task.description) > 100 else ''}\n"
429
+ result += "\n"
430
+
431
+ return result.strip()
432
+
433
+ @mcp.tool()
434
+ def get_stats() -> str:
435
+ """Get task statistics and summary."""
436
+ if not tasks_db:
437
+ return "πŸ“Š No tasks yet. Create your first task to see statistics!"
438
+
439
+ total_tasks = len(tasks_db)
440
+
441
+ # Count by status
442
+ status_counts = {}
443
+ for status in TaskStatus:
444
+ status_counts[status] = sum(1 for task in tasks_db.values() if task.status == status)
445
+
446
+ # Count by priority
447
+ priority_counts = {}
448
+ for priority in Priority:
449
+ priority_counts[priority] = sum(1 for task in tasks_db.values() if task.priority == priority)
450
+
451
+ # Overdue tasks
452
+ today = datetime.now().date()
453
+ overdue_count = 0
454
+ for task in tasks_db.values():
455
+ if (task.due_date and task.status != TaskStatus.COMPLETED and
456
+ datetime.fromisoformat(task.due_date).date() < today):
457
+ overdue_count += 1
458
+
459
+ # Most common tags
460
+ all_tags = []
461
+ for task in tasks_db.values():
462
+ all_tags.extend(task.tags)
463
+
464
+ tag_counts = {}
465
+ for tag in all_tags:
466
+ tag_counts[tag] = tag_counts.get(tag, 0) + 1
467
+
468
+ top_tags = sorted(tag_counts.items(), key=lambda x: x[1], reverse=True)[:5]
469
+
470
+ result = f"πŸ“Š Task Statistics\n\n"
471
+ result += f"Total Tasks: {total_tasks}\n\n"
472
+
473
+ result += "πŸ“ˆ By Status:\n"
474
+ for status, count in status_counts.items():
475
+ emoji = {"pending": "⏳", "in_progress": "πŸ”„", "completed": "βœ…", "cancelled": "❌"}
476
+ result += f" {emoji.get(status.value, 'β€’')} {status.value.replace('_', ' ').title()}: {count}\n"
477
+
478
+ result += "\n🎯 By Priority:\n"
479
+ for priority, count in priority_counts.items():
480
+ emoji = {"low": "🟒", "medium": "🟑", "high": "🟠", "urgent": "πŸ”΄"}
481
+ result += f" {emoji.get(priority.value, 'β€’')} {priority.value.title()}: {count}\n"
482
+
483
+ if overdue_count > 0:
484
+ result += f"\n⚠️ Overdue Tasks: {overdue_count}\n"
485
+
486
+ if top_tags:
487
+ result += f"\n🏷️ Top Tags:\n"
488
+ for tag, count in top_tags:
489
+ result += f" #{tag}: {count}\n"
490
+
491
+ return result
492
+
493
+ def main():
494
+ """Initialize and run the MCP server"""
495
+ print("Starting Task Manager MCP Server...")
496
+ print(f"Data file: {os.path.abspath(DATA_FILE)}")
497
+ print(f"Loaded {len(tasks_db)} existing tasks")
498
+ mcp.run(transport="stdio")
499
+
500
+ if __name__ == "__main__":
501
+ main()
tasks.json ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tasks": {
3
+ "1": {
4
+ "id": 1,
5
+ "title": "Learn MCP development",
6
+ "description": "Study Model Context Protocol",
7
+ "status": "in_progress",
8
+ "priority": "high",
9
+ "tags": [
10
+ "learning",
11
+ "mcp"
12
+ ],
13
+ "created_at": "2025-12-16T01:47:25.373667",
14
+ "updated_at": "2025-12-16T01:47:25.375669",
15
+ "due_date": null,
16
+ "completed_at": null
17
+ },
18
+ "2": {
19
+ "id": 2,
20
+ "title": "Build a web app",
21
+ "description": "Create a task management web interface",
22
+ "status": "completed",
23
+ "priority": "medium",
24
+ "tags": [
25
+ "web",
26
+ "frontend"
27
+ ],
28
+ "created_at": "2025-12-16T01:47:25.374668",
29
+ "updated_at": "2025-12-16T01:47:25.376669",
30
+ "due_date": "2024-12-25",
31
+ "completed_at": "2025-12-16T01:47:25.376669"
32
+ },
33
+ "3": {
34
+ "id": 3,
35
+ "title": "Write documentation",
36
+ "description": "Document the MCP server",
37
+ "status": "pending",
38
+ "priority": "low",
39
+ "tags": [
40
+ "docs"
41
+ ],
42
+ "created_at": "2025-12-16T01:47:25.374668",
43
+ "updated_at": "2025-12-16T01:47:25.374668",
44
+ "due_date": null,
45
+ "completed_at": null
46
+ },
47
+ "4": {
48
+ "id": 4,
49
+ "title": "Learn React",
50
+ "description": "Study React fundamentals, components, hooks, and state management",
51
+ "status": "pending",
52
+ "priority": "high",
53
+ "tags": [
54
+ "frontend",
55
+ "react",
56
+ "learning"
57
+ ],
58
+ "created_at": "2025-12-16T01:53:44.607322",
59
+ "updated_at": "2025-12-16T01:53:44.607322",
60
+ "due_date": null,
61
+ "completed_at": null
62
+ }
63
+ },
64
+ "next_id": 5
65
+ }