Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions .github/workflows/mcp-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,4 @@ jobs:
exit 2
fi
# Basic JSON sanity and presence of a top-level server key
python - <<'PY'
import json,sys
with open('artifacts/mcp_mcpjson.json',encoding='utf-8') as f:
j=json.load(f)
if not isinstance(j, dict) or len(j)==0:
print('ERROR: artifacts/mcp_mcpjson.json does not contain a server object', file=sys.stderr)
sys.exit(2)
print('OK: mcp_mcpjson.json looks valid; server keys:', list(j.keys())[:5])
PY
python3 -c "import json,sys; j=json.load(open('artifacts/mcp_mcpjson.json',encoding='utf-8')); sys.exit(2) if not isinstance(j,dict) or len(j)==0 else print('OK: mcp_mcpjson.json valid; server keys:',list(j.keys())[:5])"
13 changes: 0 additions & 13 deletions docs/developer/mcp-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,18 +332,5 @@

## Prompts

### DAILY_REVIEW_PROMPT

- Source: `taskmajor/mcp/prompts/review_prompts.py:9`

- Prompt snippet: `# Daily Review Perform a complete daily review for the user. ## Steps 1. **Overdue** — Check `taskmajor://status/overdue`. List each overdue task with its delay. If any, propose rescheduling or completing them. 2. **Today's Agenda** — Check `taskmajor://agenda/today`. Present: - Appointments (entry_type=appointment) with their time - Tasks due today, sorted by priority - Active tas...`



### WEEKLY_REVIEW_PROMPT

- Source: `taskmajor/mcp/prompts/review_prompts.py:51`

- Prompt snippet: `# Weekly Review Perform a complete weekly review. ## Steps 1. **Week Summary** — Call `list_tasks(status="completed")`. Filter those completed this week (last 7 days). Summary: "{count} tasks completed this week." 2. **Week Ahead** — Check `taskmajor://agenda/week`. Present the planning day by day. - Identify busy and free days. - Identify appointments and reminders (entry_type=app...`


6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ allow-direct-references = true

[project]
name = "taskmajor"
version = "0.2.4"
version = "0.2.5"
description = "TaskMajor MCP. Coordinate your tasks. Execute with precision."
readme = "README.md"
requires-python = ">=3.12"
Expand Down Expand Up @@ -71,5 +71,9 @@ ignore = [
[tool.ruff.lint.isort]
known-first-party = ["taskmajor"]

[tool.mypy]
python_version = "3.12"
mypy_path = ["tools"]

#[tool.uv.sources]
#pytaskwarrior = { path = "../../pytaskwarrior", editable = true }
2 changes: 1 addition & 1 deletion taskmajor/bootstrap/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import argparse
import logging
from typing import cast, Literal
from typing import Literal, cast

from fastmcp import FastMCP
from fastmcp.resources import FunctionResource
Expand Down
51 changes: 51 additions & 0 deletions taskmajor/mcp/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Structured error handling for MCP tools.

All MCP tool responses should be wrapped using ok() or fail() to produce
a consistent ToolResult shape that callers can rely on.
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel

# ---------------------------------------------------------------------------
# Error code constants
# ---------------------------------------------------------------------------

TASK_NOT_FOUND = "TASK_NOT_FOUND"
INVALID_INPUT = "INVALID_INPUT"
TASK_ALREADY_STARTED = "TASK_ALREADY_STARTED"
TASK_ALREADY_STOPPED = "TASK_ALREADY_STOPPED"
CONFIG_ERROR = "CONFIG_ERROR"
PROFILE_ERROR = "PROFILE_ERROR"
INTERNAL_ERROR = "INTERNAL_ERROR"


# ---------------------------------------------------------------------------
# ToolResult model
# ---------------------------------------------------------------------------


class ToolResult(BaseModel):
success: bool
error: str | None = None
error_code: str | None = None
data: Any | None = None


# ---------------------------------------------------------------------------
# Shortcut helpers
# ---------------------------------------------------------------------------


def ok(data: Any) -> dict:
"""Return a successful ToolResult payload."""
return ToolResult(success=True, data=data).model_dump()


def fail(message: str, code: str = INTERNAL_ERROR) -> dict:
"""Return a failed ToolResult payload."""
return ToolResult(success=False, error=message, error_code=code).model_dump()
125 changes: 0 additions & 125 deletions taskmajor/mcp/prompts/review_prompts.py

This file was deleted.

40 changes: 24 additions & 16 deletions taskmajor/mcp/tools/config_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from taskwarrior.dto.uda_dto import UdaConfig

from taskmajor.domains.taskwarrior import TaskConfigService
from taskmajor.mcp.errors import CONFIG_ERROR, INTERNAL_ERROR, INVALID_INPUT, fail, ok


def register_config_tools(
Expand All @@ -35,12 +36,15 @@ def get_config() -> dict[str, Any]:

Use this to inspect the current setup before making changes.
"""
return task_config.get_all_config()
try:
return ok(task_config.get_all_config())
except Exception as e:
return fail(str(e), INTERNAL_ERROR)

if _allowed("set_timezone"):

@mcp.tool
def set_timezone(timezone: str) -> str:
def set_timezone(timezone: str) -> dict[str, Any]:
"""
Set the timezone in the configuration.

Expand All @@ -52,14 +56,16 @@ def set_timezone(timezone: str) -> str:
"""
try:
task_config.set_timezone(timezone)
return f"Timezone set to '{timezone}'."
return ok(f"Timezone set to '{timezone}'.")
except ValueError as e:
return fail(str(e), INVALID_INPUT)
except Exception as e:
return f"Failed to set timezone: {e}"
return fail(f"Failed to set timezone: {e}", CONFIG_ERROR)

if _allowed("add_uda"):

@mcp.tool
def add_uda(uda_config: UdaConfig) -> str:
def add_uda(uda_config: UdaConfig) -> dict[str, Any]:
"""
Define or update a User Defined Attribute (UDA).

Expand All @@ -71,14 +77,16 @@ def add_uda(uda_config: UdaConfig) -> str:
"""
try:
task_config.add_uda(uda_config)
return f"UDA '{uda_config.name}' (type={uda_config.uda_type}) defined successfully."
return ok(f"UDA '{uda_config.name}' (type={uda_config.uda_type}) defined successfully.")
except ValueError as e:
return fail(str(e), INVALID_INPUT)
except Exception as e:
return f"Failed to define UDA '{uda_config.name}': {e}"
return fail(f"Failed to define UDA '{uda_config.name}': {e}", CONFIG_ERROR)

if _allowed("delete_uda"):

@mcp.tool
def delete_uda(name: str) -> str:
def delete_uda(name: str) -> dict[str, Any]:
"""
Delete a User Defined Attribute (UDA).

Expand All @@ -92,14 +100,14 @@ def delete_uda(name: str) -> str:
"""
try:
task_config.delete_uda(name)
return f"UDA '{name}' deleted successfully."
return ok(f"UDA '{name}' deleted successfully.")
except Exception as e:
return f"Failed to delete UDA '{name}': {e}"
return fail(f"Failed to delete UDA '{name}': {e}", CONFIG_ERROR)

if _allowed("define_context"):

@mcp.tool
def define_context(context: ContextDTO) -> str:
def define_context(context: ContextDTO) -> dict[str, Any]:
"""
Create or update a TaskWarrior context.

Expand All @@ -114,14 +122,14 @@ def define_context(context: ContextDTO) -> str:
"""
try:
task_config.define_context(context)
return f"Context '{context.name}' defined successfully."
return ok(f"Context '{context.name}' defined successfully.")
except Exception as e:
return f"Failed to define context '{context.name}': {e}"
return fail(f"Failed to define context '{context.name}': {e}", CONFIG_ERROR)

if _allowed("delete_context"):

@mcp.tool
def delete_context(name: str) -> str:
def delete_context(name: str) -> dict[str, Any]:
"""
Delete a TaskWarrior context.

Expand All @@ -133,6 +141,6 @@ def delete_context(name: str) -> str:
"""
try:
task_config.delete_context(name)
return f"Context '{name}' deleted."
return ok(f"Context '{name}' deleted.")
except Exception as e:
return f"Failed to delete context '{name}': {e}"
return fail(f"Failed to delete context '{name}': {e}", CONFIG_ERROR)
Loading
Loading