iflow-mcp_excelsier-things3-enhanced-mcp 1.0.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.
- iflow_mcp_excelsier_things3_enhanced_mcp-1.0.0.dist-info/METADATA +444 -0
- iflow_mcp_excelsier_things3_enhanced_mcp-1.0.0.dist-info/RECORD +20 -0
- iflow_mcp_excelsier_things3_enhanced_mcp-1.0.0.dist-info/WHEEL +4 -0
- iflow_mcp_excelsier_things3_enhanced_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_excelsier_things3_enhanced_mcp-1.0.0.dist-info/licenses/LICENSE +24 -0
- things_mcp/__init__.py +5 -0
- things_mcp/applescript_bridge.py +335 -0
- things_mcp/cache.py +240 -0
- things_mcp/config.py +120 -0
- things_mcp/fast_server.py +633 -0
- things_mcp/formatters.py +128 -0
- things_mcp/handlers.py +601 -0
- things_mcp/logging_config.py +218 -0
- things_mcp/mcp_tools.py +465 -0
- things_mcp/simple_server.py +687 -0
- things_mcp/simple_url_scheme.py +230 -0
- things_mcp/tag_handler.py +111 -0
- things_mcp/things_server.py +106 -0
- things_mcp/url_scheme.py +318 -0
- things_mcp/utils.py +360 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enhanced logging configuration for Things MCP server.
|
|
4
|
+
Provides structured logging with multiple outputs and log levels.
|
|
5
|
+
"""
|
|
6
|
+
import logging
|
|
7
|
+
import logging.handlers
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, Any, Optional
|
|
13
|
+
|
|
14
|
+
# Create logs directory if it doesn't exist
|
|
15
|
+
LOGS_DIR = Path.home() / '.things-mcp' / 'logs'
|
|
16
|
+
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
|
|
18
|
+
class StructuredFormatter(logging.Formatter):
|
|
19
|
+
"""Custom formatter that outputs structured JSON logs for better analysis."""
|
|
20
|
+
|
|
21
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
22
|
+
log_data = {
|
|
23
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
24
|
+
'level': record.levelname,
|
|
25
|
+
'logger': record.name,
|
|
26
|
+
'message': record.getMessage(),
|
|
27
|
+
'module': record.module,
|
|
28
|
+
'function': record.funcName,
|
|
29
|
+
'line': record.lineno,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Add any extra fields
|
|
33
|
+
if hasattr(record, 'operation'):
|
|
34
|
+
log_data['operation'] = record.operation
|
|
35
|
+
if hasattr(record, 'duration'):
|
|
36
|
+
log_data['duration'] = record.duration
|
|
37
|
+
if hasattr(record, 'error_type'):
|
|
38
|
+
log_data['error_type'] = record.error_type
|
|
39
|
+
if hasattr(record, 'retry_count'):
|
|
40
|
+
log_data['retry_count'] = record.retry_count
|
|
41
|
+
|
|
42
|
+
# Add exception info if present
|
|
43
|
+
if record.exc_info:
|
|
44
|
+
log_data['exception'] = self.formatException(record.exc_info)
|
|
45
|
+
|
|
46
|
+
return json.dumps(log_data)
|
|
47
|
+
|
|
48
|
+
class OperationLogFilter(logging.Filter):
|
|
49
|
+
"""Filter to add operation context to log records."""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
super().__init__()
|
|
53
|
+
self.operation_context = {}
|
|
54
|
+
|
|
55
|
+
def set_operation_context(self, operation: str, **kwargs):
|
|
56
|
+
"""Set the current operation context."""
|
|
57
|
+
self.operation_context = {
|
|
58
|
+
'operation': operation,
|
|
59
|
+
**kwargs
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def clear_operation_context(self):
|
|
63
|
+
"""Clear the operation context."""
|
|
64
|
+
self.operation_context = {}
|
|
65
|
+
|
|
66
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
67
|
+
# Add operation context to the record
|
|
68
|
+
for key, value in self.operation_context.items():
|
|
69
|
+
setattr(record, key, value)
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
# Global operation filter instance
|
|
73
|
+
operation_filter = OperationLogFilter()
|
|
74
|
+
|
|
75
|
+
def setup_logging(
|
|
76
|
+
console_level: str = "INFO",
|
|
77
|
+
file_level: str = "DEBUG",
|
|
78
|
+
structured_logs: bool = True,
|
|
79
|
+
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
|
80
|
+
backup_count: int = 5
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Configure comprehensive logging for the Things MCP server.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
console_level: Log level for console output
|
|
87
|
+
file_level: Log level for file output
|
|
88
|
+
structured_logs: Whether to use structured JSON logging
|
|
89
|
+
max_bytes: Maximum size of log files before rotation
|
|
90
|
+
backup_count: Number of backup files to keep
|
|
91
|
+
"""
|
|
92
|
+
# Get the root logger
|
|
93
|
+
root_logger = logging.getLogger()
|
|
94
|
+
root_logger.setLevel(logging.DEBUG) # Capture everything, filter at handler level
|
|
95
|
+
|
|
96
|
+
# Remove existing handlers
|
|
97
|
+
root_logger.handlers.clear()
|
|
98
|
+
|
|
99
|
+
# Console handler with simple formatting
|
|
100
|
+
console_handler = logging.StreamHandler()
|
|
101
|
+
console_handler.setLevel(getattr(logging, console_level.upper()))
|
|
102
|
+
console_format = logging.Formatter(
|
|
103
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
104
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
105
|
+
)
|
|
106
|
+
console_handler.setFormatter(console_format)
|
|
107
|
+
console_handler.addFilter(operation_filter)
|
|
108
|
+
root_logger.addHandler(console_handler)
|
|
109
|
+
|
|
110
|
+
# File handlers with rotation
|
|
111
|
+
if structured_logs:
|
|
112
|
+
# Structured JSON logs for analysis
|
|
113
|
+
json_file_handler = logging.handlers.RotatingFileHandler(
|
|
114
|
+
LOGS_DIR / 'things_mcp_structured.json',
|
|
115
|
+
maxBytes=max_bytes,
|
|
116
|
+
backupCount=backup_count
|
|
117
|
+
)
|
|
118
|
+
json_file_handler.setLevel(getattr(logging, file_level.upper()))
|
|
119
|
+
json_file_handler.setFormatter(StructuredFormatter())
|
|
120
|
+
json_file_handler.addFilter(operation_filter)
|
|
121
|
+
root_logger.addHandler(json_file_handler)
|
|
122
|
+
|
|
123
|
+
# Human-readable file logs
|
|
124
|
+
text_file_handler = logging.handlers.RotatingFileHandler(
|
|
125
|
+
LOGS_DIR / 'things_mcp.log',
|
|
126
|
+
maxBytes=max_bytes,
|
|
127
|
+
backupCount=backup_count
|
|
128
|
+
)
|
|
129
|
+
text_file_handler.setLevel(getattr(logging, file_level.upper()))
|
|
130
|
+
text_format = logging.Formatter(
|
|
131
|
+
'%(asctime)s - %(name)s - %(levelname)s - [%(funcName)s:%(lineno)d] - %(message)s',
|
|
132
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
133
|
+
)
|
|
134
|
+
text_file_handler.setFormatter(text_format)
|
|
135
|
+
text_file_handler.addFilter(operation_filter)
|
|
136
|
+
root_logger.addHandler(text_file_handler)
|
|
137
|
+
|
|
138
|
+
# Error-only file handler
|
|
139
|
+
error_file_handler = logging.handlers.RotatingFileHandler(
|
|
140
|
+
LOGS_DIR / 'things_mcp_errors.log',
|
|
141
|
+
maxBytes=max_bytes,
|
|
142
|
+
backupCount=backup_count
|
|
143
|
+
)
|
|
144
|
+
error_file_handler.setLevel(logging.ERROR)
|
|
145
|
+
error_file_handler.setFormatter(text_format)
|
|
146
|
+
error_file_handler.addFilter(operation_filter)
|
|
147
|
+
root_logger.addHandler(error_file_handler)
|
|
148
|
+
|
|
149
|
+
# Log the logging configuration
|
|
150
|
+
logger = logging.getLogger(__name__)
|
|
151
|
+
logger.info(f"Logging configured - Console: {console_level}, File: {file_level}, Structured: {structured_logs}")
|
|
152
|
+
logger.info(f"Log files location: {LOGS_DIR}")
|
|
153
|
+
|
|
154
|
+
def log_operation_start(operation: str, **kwargs) -> None:
|
|
155
|
+
"""Log the start of an operation and set context."""
|
|
156
|
+
operation_filter.set_operation_context(operation, **kwargs)
|
|
157
|
+
logger = logging.getLogger(__name__)
|
|
158
|
+
logger.info(f"Starting operation: {operation}", extra={'operation': operation, **kwargs})
|
|
159
|
+
|
|
160
|
+
def log_operation_end(operation: str, success: bool, duration: float = None, **kwargs) -> None:
|
|
161
|
+
"""Log the end of an operation."""
|
|
162
|
+
logger = logging.getLogger(__name__)
|
|
163
|
+
extra = {
|
|
164
|
+
'operation': operation,
|
|
165
|
+
'success': success,
|
|
166
|
+
**kwargs
|
|
167
|
+
}
|
|
168
|
+
if duration is not None:
|
|
169
|
+
extra['duration'] = duration
|
|
170
|
+
|
|
171
|
+
if success:
|
|
172
|
+
logger.info(f"Operation completed: {operation}", extra=extra)
|
|
173
|
+
else:
|
|
174
|
+
logger.error(f"Operation failed: {operation}", extra=extra)
|
|
175
|
+
|
|
176
|
+
operation_filter.clear_operation_context()
|
|
177
|
+
|
|
178
|
+
def log_retry_attempt(operation: str, attempt: int, max_attempts: int, error: str) -> None:
|
|
179
|
+
"""Log a retry attempt."""
|
|
180
|
+
logger = logging.getLogger(__name__)
|
|
181
|
+
logger.warning(
|
|
182
|
+
f"Retry attempt {attempt}/{max_attempts} for {operation}: {error}",
|
|
183
|
+
extra={
|
|
184
|
+
'operation': operation,
|
|
185
|
+
'retry_count': attempt,
|
|
186
|
+
'max_attempts': max_attempts,
|
|
187
|
+
'error': error
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def log_circuit_breaker_state(state: str, failure_count: int = None) -> None:
|
|
192
|
+
"""Log circuit breaker state changes."""
|
|
193
|
+
logger = logging.getLogger(__name__)
|
|
194
|
+
extra = {'circuit_breaker_state': state}
|
|
195
|
+
if failure_count is not None:
|
|
196
|
+
extra['failure_count'] = failure_count
|
|
197
|
+
|
|
198
|
+
logger.warning(f"Circuit breaker state changed to: {state}", extra=extra)
|
|
199
|
+
|
|
200
|
+
def log_dead_letter_queue(operation: str, params: Dict[str, Any], error: str) -> None:
|
|
201
|
+
"""Log when an operation is added to the dead letter queue."""
|
|
202
|
+
logger = logging.getLogger(__name__)
|
|
203
|
+
logger.error(
|
|
204
|
+
f"Added to dead letter queue: {operation}",
|
|
205
|
+
extra={
|
|
206
|
+
'operation': operation,
|
|
207
|
+
'params': params,
|
|
208
|
+
'error': error,
|
|
209
|
+
'dlq': True
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def get_logger(name: str) -> logging.Logger:
|
|
214
|
+
"""Get a logger instance with the given name."""
|
|
215
|
+
return logging.getLogger(name)
|
|
216
|
+
|
|
217
|
+
# Initialize logging when module is imported
|
|
218
|
+
setup_logging()
|
things_mcp/mcp_tools.py
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP tools configuration for Things integration with Windsurf.
|
|
4
|
+
This ensures proper tool registration and naming for seamless integration.
|
|
5
|
+
"""
|
|
6
|
+
import mcp.types as types
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
def get_mcp_tools_list() -> List[types.Tool]:
|
|
10
|
+
"""
|
|
11
|
+
Return the list of MCP tools with consistent naming between registration and implementation.
|
|
12
|
+
|
|
13
|
+
Uses consistent naming without prefixes to ensure proper tool functioning.
|
|
14
|
+
"""
|
|
15
|
+
return [
|
|
16
|
+
# Basic operations
|
|
17
|
+
types.Tool(
|
|
18
|
+
name="get-todos",
|
|
19
|
+
description="Get todos from Things, optionally filtered by project",
|
|
20
|
+
inputSchema={
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"project_uuid": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Optional UUID of a specific project to get todos from",
|
|
26
|
+
},
|
|
27
|
+
"include_items": {
|
|
28
|
+
"type": "boolean",
|
|
29
|
+
"description": "Include checklist items",
|
|
30
|
+
"default": True
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"required": [],
|
|
34
|
+
},
|
|
35
|
+
),
|
|
36
|
+
types.Tool(
|
|
37
|
+
name="get-projects",
|
|
38
|
+
description="Get all projects from Things",
|
|
39
|
+
inputSchema={
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"include_items": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"description": "Include tasks within projects",
|
|
45
|
+
"default": False
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": [],
|
|
49
|
+
},
|
|
50
|
+
),
|
|
51
|
+
types.Tool(
|
|
52
|
+
name="get-areas",
|
|
53
|
+
description="Get all areas from Things",
|
|
54
|
+
inputSchema={
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"include_items": {
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"description": "Include projects and tasks within areas",
|
|
60
|
+
"default": False
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": [],
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
# List views
|
|
68
|
+
types.Tool(
|
|
69
|
+
name="get-inbox",
|
|
70
|
+
description="Get todos from Inbox",
|
|
71
|
+
inputSchema={
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {},
|
|
74
|
+
"required": [],
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
types.Tool(
|
|
78
|
+
name="get-today",
|
|
79
|
+
description="Get todos due today",
|
|
80
|
+
inputSchema={
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {},
|
|
83
|
+
"required": [],
|
|
84
|
+
},
|
|
85
|
+
),
|
|
86
|
+
types.Tool(
|
|
87
|
+
name="get-upcoming",
|
|
88
|
+
description="Get upcoming todos",
|
|
89
|
+
inputSchema={
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {},
|
|
92
|
+
"required": [],
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
types.Tool(
|
|
96
|
+
name="get-anytime",
|
|
97
|
+
description="Get todos from Anytime list",
|
|
98
|
+
inputSchema={
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {},
|
|
101
|
+
"required": [],
|
|
102
|
+
},
|
|
103
|
+
),
|
|
104
|
+
types.Tool(
|
|
105
|
+
name="get-someday",
|
|
106
|
+
description="Get todos from Someday list",
|
|
107
|
+
inputSchema={
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {},
|
|
110
|
+
"required": [],
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
types.Tool(
|
|
114
|
+
name="get-logbook",
|
|
115
|
+
description="Get completed todos from Logbook, defaults to last 7 days",
|
|
116
|
+
inputSchema={
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"period": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Time period to look back (e.g., '3d', '1w', '2m', '1y'). Defaults to '7d'",
|
|
122
|
+
"pattern": "^\\d+[dwmy]$"
|
|
123
|
+
},
|
|
124
|
+
"limit": {
|
|
125
|
+
"type": "integer",
|
|
126
|
+
"description": "Maximum number of entries to return. Defaults to 50",
|
|
127
|
+
"minimum": 1,
|
|
128
|
+
"maximum": 100
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"required": [],
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
types.Tool(
|
|
135
|
+
name="get-trash",
|
|
136
|
+
description="Get trashed todos",
|
|
137
|
+
inputSchema={
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {},
|
|
140
|
+
"required": [],
|
|
141
|
+
},
|
|
142
|
+
),
|
|
143
|
+
|
|
144
|
+
# Tag operations
|
|
145
|
+
types.Tool(
|
|
146
|
+
name="get-tags",
|
|
147
|
+
description="Get all tags",
|
|
148
|
+
inputSchema={
|
|
149
|
+
"type": "object",
|
|
150
|
+
"properties": {
|
|
151
|
+
"include_items": {
|
|
152
|
+
"type": "boolean",
|
|
153
|
+
"description": "Include items tagged with each tag",
|
|
154
|
+
"default": False
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"required": [],
|
|
158
|
+
},
|
|
159
|
+
),
|
|
160
|
+
types.Tool(
|
|
161
|
+
name="get-tagged-items",
|
|
162
|
+
description="Get items with a specific tag",
|
|
163
|
+
inputSchema={
|
|
164
|
+
"type": "object",
|
|
165
|
+
"properties": {
|
|
166
|
+
"tag": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "Tag title to filter by"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"required": ["tag"],
|
|
172
|
+
},
|
|
173
|
+
),
|
|
174
|
+
|
|
175
|
+
# Search operations
|
|
176
|
+
types.Tool(
|
|
177
|
+
name="search-todos",
|
|
178
|
+
description="Search todos by title or notes",
|
|
179
|
+
inputSchema={
|
|
180
|
+
"type": "object",
|
|
181
|
+
"properties": {
|
|
182
|
+
"query": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"description": "Search term to look for in todo titles and notes",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
"required": ["query"],
|
|
188
|
+
},
|
|
189
|
+
),
|
|
190
|
+
types.Tool(
|
|
191
|
+
name="search-advanced",
|
|
192
|
+
description="Advanced todo search with multiple filters",
|
|
193
|
+
inputSchema={
|
|
194
|
+
"type": "object",
|
|
195
|
+
"properties": {
|
|
196
|
+
"status": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"enum": ["incomplete", "completed", "canceled"],
|
|
199
|
+
"description": "Filter by todo status"
|
|
200
|
+
},
|
|
201
|
+
"start_date": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"description": "Filter by start date (YYYY-MM-DD)"
|
|
204
|
+
},
|
|
205
|
+
"deadline": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"description": "Filter by deadline (YYYY-MM-DD)"
|
|
208
|
+
},
|
|
209
|
+
"tag": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"description": "Filter by tag"
|
|
212
|
+
},
|
|
213
|
+
"area": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"description": "Filter by area UUID"
|
|
216
|
+
},
|
|
217
|
+
"type": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"enum": ["to-do", "project", "heading"],
|
|
220
|
+
"description": "Filter by item type"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"required": [],
|
|
224
|
+
},
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
# Recent items
|
|
228
|
+
types.Tool(
|
|
229
|
+
name="get-recent",
|
|
230
|
+
description="Get recently created items",
|
|
231
|
+
inputSchema={
|
|
232
|
+
"type": "object",
|
|
233
|
+
"properties": {
|
|
234
|
+
"period": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"description": "Time period (e.g., '3d', '1w', '2m', '1y')",
|
|
237
|
+
"pattern": "^\\d+[dwmy]$"
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"required": ["period"],
|
|
241
|
+
},
|
|
242
|
+
),
|
|
243
|
+
|
|
244
|
+
# Things URL Scheme tools
|
|
245
|
+
types.Tool(
|
|
246
|
+
name="add-todo",
|
|
247
|
+
description="Create a new todo in Things",
|
|
248
|
+
inputSchema={
|
|
249
|
+
"type": "object",
|
|
250
|
+
"properties": {
|
|
251
|
+
"title": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"description": "Title of the todo"
|
|
254
|
+
},
|
|
255
|
+
"notes": {
|
|
256
|
+
"type": "string",
|
|
257
|
+
"description": "Notes for the todo"
|
|
258
|
+
},
|
|
259
|
+
"when": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"description": "When to schedule the todo (today, tomorrow, evening, anytime, someday, or YYYY-MM-DD)"
|
|
262
|
+
},
|
|
263
|
+
"deadline": {
|
|
264
|
+
"type": "string",
|
|
265
|
+
"description": "Deadline for the todo (YYYY-MM-DD)"
|
|
266
|
+
},
|
|
267
|
+
"tags": {
|
|
268
|
+
"type": "array",
|
|
269
|
+
"items": {"type": "string"},
|
|
270
|
+
"description": "Tags to apply to the todo"
|
|
271
|
+
},
|
|
272
|
+
"checklist_items": {
|
|
273
|
+
"type": "array",
|
|
274
|
+
"items": {"type": "string"},
|
|
275
|
+
"description": "Checklist items to add"
|
|
276
|
+
},
|
|
277
|
+
"list_id": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"description": "ID of project/area to add to"
|
|
280
|
+
},
|
|
281
|
+
"list_title": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"description": "Title of project/area to add to"
|
|
284
|
+
},
|
|
285
|
+
"heading": {
|
|
286
|
+
"type": "string",
|
|
287
|
+
"description": "Heading to add under"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"required": ["title"]
|
|
291
|
+
}
|
|
292
|
+
),
|
|
293
|
+
|
|
294
|
+
types.Tool(
|
|
295
|
+
name="add-project",
|
|
296
|
+
description="Create a new project in Things",
|
|
297
|
+
inputSchema={
|
|
298
|
+
"type": "object",
|
|
299
|
+
"properties": {
|
|
300
|
+
"title": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"description": "Title of the project"
|
|
303
|
+
},
|
|
304
|
+
"notes": {
|
|
305
|
+
"type": "string",
|
|
306
|
+
"description": "Notes for the project"
|
|
307
|
+
},
|
|
308
|
+
"when": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"description": "When to schedule the project"
|
|
311
|
+
},
|
|
312
|
+
"deadline": {
|
|
313
|
+
"type": "string",
|
|
314
|
+
"description": "Deadline for the project"
|
|
315
|
+
},
|
|
316
|
+
"tags": {
|
|
317
|
+
"type": "array",
|
|
318
|
+
"items": {"type": "string"},
|
|
319
|
+
"description": "Tags to apply to the project"
|
|
320
|
+
},
|
|
321
|
+
"area_id": {
|
|
322
|
+
"type": "string",
|
|
323
|
+
"description": "ID of area to add to"
|
|
324
|
+
},
|
|
325
|
+
"area_title": {
|
|
326
|
+
"type": "string",
|
|
327
|
+
"description": "Title of area to add to"
|
|
328
|
+
},
|
|
329
|
+
"todos": {
|
|
330
|
+
"type": "array",
|
|
331
|
+
"items": {"type": "string"},
|
|
332
|
+
"description": "Initial todos to create in the project"
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
"required": ["title"]
|
|
336
|
+
}
|
|
337
|
+
),
|
|
338
|
+
|
|
339
|
+
types.Tool(
|
|
340
|
+
name="update-todo",
|
|
341
|
+
description="Update an existing todo in Things",
|
|
342
|
+
inputSchema={
|
|
343
|
+
"type": "object",
|
|
344
|
+
"properties": {
|
|
345
|
+
"id": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"description": "ID of the todo to update"
|
|
348
|
+
},
|
|
349
|
+
"title": {
|
|
350
|
+
"type": "string",
|
|
351
|
+
"description": "New title"
|
|
352
|
+
},
|
|
353
|
+
"notes": {
|
|
354
|
+
"type": "string",
|
|
355
|
+
"description": "New notes"
|
|
356
|
+
},
|
|
357
|
+
"when": {
|
|
358
|
+
"type": "string",
|
|
359
|
+
"description": "New schedule"
|
|
360
|
+
},
|
|
361
|
+
"deadline": {
|
|
362
|
+
"type": "string",
|
|
363
|
+
"description": "New deadline"
|
|
364
|
+
},
|
|
365
|
+
"tags": {
|
|
366
|
+
"type": "array",
|
|
367
|
+
"items": {"type": "string"},
|
|
368
|
+
"description": "New tags"
|
|
369
|
+
},
|
|
370
|
+
"completed": {
|
|
371
|
+
"type": "boolean",
|
|
372
|
+
"description": "Mark as completed"
|
|
373
|
+
},
|
|
374
|
+
"canceled": {
|
|
375
|
+
"type": "boolean",
|
|
376
|
+
"description": "Mark as canceled"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"required": ["id"]
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
|
|
383
|
+
types.Tool(
|
|
384
|
+
name="update-project",
|
|
385
|
+
description="Update an existing project in Things",
|
|
386
|
+
inputSchema={
|
|
387
|
+
"type": "object",
|
|
388
|
+
"properties": {
|
|
389
|
+
"id": {
|
|
390
|
+
"type": "string",
|
|
391
|
+
"description": "ID of the project to update"
|
|
392
|
+
},
|
|
393
|
+
"title": {
|
|
394
|
+
"type": "string",
|
|
395
|
+
"description": "New title"
|
|
396
|
+
},
|
|
397
|
+
"notes": {
|
|
398
|
+
"type": "string",
|
|
399
|
+
"description": "New notes"
|
|
400
|
+
},
|
|
401
|
+
"when": {
|
|
402
|
+
"type": "string",
|
|
403
|
+
"description": "New schedule"
|
|
404
|
+
},
|
|
405
|
+
"deadline": {
|
|
406
|
+
"type": "string",
|
|
407
|
+
"description": "New deadline"
|
|
408
|
+
},
|
|
409
|
+
"tags": {
|
|
410
|
+
"type": "array",
|
|
411
|
+
"items": {"type": "string"},
|
|
412
|
+
"description": "New tags"
|
|
413
|
+
},
|
|
414
|
+
"completed": {
|
|
415
|
+
"type": "boolean",
|
|
416
|
+
"description": "Mark as completed"
|
|
417
|
+
},
|
|
418
|
+
"canceled": {
|
|
419
|
+
"type": "boolean",
|
|
420
|
+
"description": "Mark as canceled"
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
"required": ["id"]
|
|
424
|
+
}
|
|
425
|
+
),
|
|
426
|
+
|
|
427
|
+
types.Tool(
|
|
428
|
+
name="search-items",
|
|
429
|
+
description="Search for items in Things",
|
|
430
|
+
inputSchema={
|
|
431
|
+
"type": "object",
|
|
432
|
+
"properties": {
|
|
433
|
+
"query": {
|
|
434
|
+
"type": "string",
|
|
435
|
+
"description": "Search query"
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
"required": ["query"]
|
|
439
|
+
}
|
|
440
|
+
),
|
|
441
|
+
|
|
442
|
+
types.Tool(
|
|
443
|
+
name="show-item",
|
|
444
|
+
description="Show a specific item or list in Things",
|
|
445
|
+
inputSchema={
|
|
446
|
+
"type": "object",
|
|
447
|
+
"properties": {
|
|
448
|
+
"id": {
|
|
449
|
+
"type": "string",
|
|
450
|
+
"description": "ID of item to show, or one of: inbox, today, upcoming, anytime, someday, logbook"
|
|
451
|
+
},
|
|
452
|
+
"query": {
|
|
453
|
+
"type": "string",
|
|
454
|
+
"description": "Optional query to filter by"
|
|
455
|
+
},
|
|
456
|
+
"filter_tags": {
|
|
457
|
+
"type": "array",
|
|
458
|
+
"items": {"type": "string"},
|
|
459
|
+
"description": "Optional tags to filter by"
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
"required": ["id"]
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
]
|