dav-mcp 3.0.0 → 3.0.2
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.
- package/CHANGELOG.md +40 -0
- package/README.md +108 -133
- package/package.json +4 -4
- package/src/logger.js +4 -1
- package/src/server-http.js +3 -3
- package/src/server-stdio.js +172 -161
- package/src/tools/todos/delete-todo.js +1 -1
- package/src/tools/todos/update-todo-fields.js +1 -1
- package/src/tools/todos/update-todo-raw.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.0.1] - 2026-01-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **CLI flags**: `--http` and `--port` flags for easier server startup
|
|
14
|
+
- `npx dav-mcp` → STDIO mode (default)
|
|
15
|
+
- `npx dav-mcp --http` → HTTP mode on port 3000
|
|
16
|
+
- `npx dav-mcp --http --port=8080` → HTTP mode with custom port
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **README restructured** by user type (Claude Desktop, n8n, Docker)
|
|
20
|
+
- **Simplified setup**: No git clone needed for most users, just `npx dav-mcp`
|
|
21
|
+
- **Cleaned up `.env.example`**: Removed unnecessary `MCP_SERVER_NAME` and `MCP_SERVER_VERSION`
|
|
22
|
+
|
|
23
|
+
## [3.0.0] - 2026-01-20
|
|
24
|
+
|
|
25
|
+
### Breaking Changes
|
|
26
|
+
- **Removed HTTP+SSE transport**: The deprecated `/sse` and `/messages` endpoints have been removed
|
|
27
|
+
- **New transports**: Replaced with STDIO and Stateless HTTP transports
|
|
28
|
+
- **n8n users**: Must update endpoint from `/sse` to `/mcp`
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **STDIO transport** (`src/server-stdio.js`): For local clients (Claude Desktop, Cursor, npx)
|
|
32
|
+
- **Stateless HTTP transport** (`src/server-http.js`): For remote clients (n8n, cloud deployments)
|
|
33
|
+
- **MIGRATION.md**: Upgrade guide for migrating from v2.x
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- Default `npm start` now runs STDIO server (was SSE)
|
|
37
|
+
- Logger now writes to stderr in STDIO mode (preserves stdout for JSON-RPC)
|
|
38
|
+
- Dockerfile updated to use HTTP server
|
|
39
|
+
- Simplified HTTP server (stateless, no session management)
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
- `src/index.js` (old HTTP+SSE server)
|
|
43
|
+
- Session management in HTTP transport
|
|
44
|
+
- `/sse` endpoint
|
|
45
|
+
- `/messages` endpoint
|
|
46
|
+
|
|
47
|
+
### Migration
|
|
48
|
+
See [MIGRATION.md](MIGRATION.md) for detailed upgrade instructions.
|
|
49
|
+
|
|
10
50
|
## [2.7.0] - 2025-10-30
|
|
11
51
|
|
|
12
52
|
### Added
|
package/README.md
CHANGED
|
@@ -4,65 +4,108 @@
|
|
|
4
4
|
|
|
5
5
|
Built on 26 production-ready tools spanning CalDAV, CardDAV, and VTODO protocols.
|
|
6
6
|
|
|
7
|
-
Built for n8n, Claude Desktop, and any MCP client.
|
|
8
|
-
|
|
9
7
|
[](https://opensource.org/licenses/MIT)
|
|
10
8
|
[](https://www.npmjs.com/package/dav-mcp)
|
|
11
9
|
|
|
12
10
|
---
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
## Quick Start
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
### Claude Desktop / Cursor (Local)
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
Add to your MCP config file:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"dav-mcp": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "dav-mcp"],
|
|
24
|
+
"env": {
|
|
25
|
+
"CALDAV_SERVER_URL": "https://dav.example.com",
|
|
26
|
+
"CALDAV_USERNAME": "your_username",
|
|
27
|
+
"CALDAV_PASSWORD": "your_password"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Config file locations:**
|
|
35
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
36
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
37
|
+
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
27
38
|
|
|
39
|
+
Restart Claude Desktop after adding the configuration.
|
|
28
40
|
|
|
29
41
|
---
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
### n8n (Remote HTTP)
|
|
32
44
|
|
|
33
|
-
|
|
34
|
-
- **26 MCP Tools**: Full CRUD operations for calendars, contacts, and tasks
|
|
35
|
-
- **CalDAV Integration**: ~88% tsdav coverage (11 tools)
|
|
36
|
-
- **CardDAV Integration**: 100% tsdav coverage (8 tools)
|
|
37
|
-
- **VTODO Support**: Full task management with status, priorities, due dates (7 tools)
|
|
38
|
-
- **Field-Based Updates**: Field-agnostic updates via tsdav-utils - supports all RFC 5545/6350 properties + custom X-* fields
|
|
39
|
-
- **RFC-Compliant**: ical.js for RFC 5545 (iCalendar) and RFC 6350 (vCard) support
|
|
45
|
+
Start the HTTP server:
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
47
|
+
```bash
|
|
48
|
+
CALDAV_SERVER_URL=https://dav.example.com \
|
|
49
|
+
CALDAV_USERNAME=your_username \
|
|
50
|
+
CALDAV_PASSWORD=your_password \
|
|
51
|
+
BEARER_TOKEN=your-secret-token \
|
|
52
|
+
npx dav-mcp --http
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then in n8n:
|
|
56
|
+
1. Add **AI Agent** node
|
|
57
|
+
2. Add **MCP Client Tool** node and connect to AI Agent
|
|
58
|
+
3. Configure:
|
|
59
|
+
- **MCP Endpoint**: `http://localhost:3000/mcp`
|
|
60
|
+
- **Authentication**: Bearer
|
|
61
|
+
- **Token**: your-secret-token
|
|
62
|
+
|
|
63
|
+
**Custom port:**
|
|
64
|
+
```bash
|
|
65
|
+
npx dav-mcp --http --port=8080
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
46
69
|
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
### Docker
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/PhilflowIO/dav-mcp.git
|
|
74
|
+
cd dav-mcp
|
|
75
|
+
cp .env.example .env
|
|
76
|
+
# Edit .env with your credentials
|
|
77
|
+
docker-compose up
|
|
78
|
+
```
|
|
51
79
|
|
|
52
80
|
---
|
|
53
81
|
|
|
54
|
-
##
|
|
82
|
+
## The Orchestration
|
|
83
|
+
|
|
84
|
+
When partial tools force your AI to improvise, complete tools let it **execute precise operations across all components**.
|
|
85
|
+
|
|
86
|
+
| Capability | dav-mcp | Most MCPs |
|
|
87
|
+
|------------|---------|-----------|
|
|
88
|
+
| **Calendar Management** | Full CRUD (11 tools) | Create + list only (2-3 tools) |
|
|
89
|
+
| **Contact Management** | Complete CardDAV (8 tools) | Often missing entirely |
|
|
90
|
+
| **Task Management** | Full VTODO support (7 tools) | Rarely included |
|
|
91
|
+
| **Field-Based Updates** | All RFC properties + custom fields | Rarely available |
|
|
92
|
+
| **Server-Side Filtering** | Efficient queries | Dumps all data |
|
|
93
|
+
| **Multi-Provider** | Any CalDAV/CardDAV server | Limited provider support |
|
|
94
|
+
| **Total Tools** | **26 tools** | **2-6 tools** |
|
|
95
|
+
|
|
96
|
+
---
|
|
55
97
|
|
|
98
|
+
## Available Tools (26 Total)
|
|
56
99
|
|
|
57
100
|
### CalDAV Tools (11 tools)
|
|
58
101
|
|
|
59
102
|
1. **list_calendars** - List all available calendars
|
|
60
|
-
2. **list_events** - List ALL events (
|
|
103
|
+
2. **list_events** - List ALL events (use calendar_query for filtered searches)
|
|
61
104
|
3. **create_event** - Create a new calendar event
|
|
62
|
-
4. **update_event** -
|
|
105
|
+
4. **update_event** - PREFERRED: Update any event field (SUMMARY, LOCATION, DTSTART, STATUS, custom X-* properties)
|
|
63
106
|
5. **update_event_raw** - Update event with raw iCal data (advanced)
|
|
64
107
|
6. **delete_event** - Delete an event permanently
|
|
65
|
-
7. **calendar_query** -
|
|
108
|
+
7. **calendar_query** - PREFERRED: Search and filter events efficiently by text, date range, or location
|
|
66
109
|
8. **make_calendar** - Create a new calendar collection
|
|
67
110
|
9. **update_calendar** - Update calendar properties (display name, description, color, timezone)
|
|
68
111
|
10. **delete_calendar** - Permanently delete a calendar and all its events
|
|
@@ -71,29 +114,27 @@ When partial tools force your AI to improvise, complete tools let it **execute p
|
|
|
71
114
|
### CardDAV Tools (8 tools)
|
|
72
115
|
|
|
73
116
|
12. **list_addressbooks** - List all available address books
|
|
74
|
-
13. **list_contacts** - List ALL contacts (
|
|
117
|
+
13. **list_contacts** - List ALL contacts (use addressbook_query for filtered searches)
|
|
75
118
|
14. **create_contact** - Create a new contact (vCard)
|
|
76
|
-
15. **update_contact** -
|
|
119
|
+
15. **update_contact** - PREFERRED: Update any contact field (FN, EMAIL, TEL, ORG, ADR, custom X-* properties)
|
|
77
120
|
16. **update_contact_raw** - Update contact with raw vCard data (advanced)
|
|
78
121
|
17. **delete_contact** - Delete a contact permanently
|
|
79
|
-
18. **addressbook_query** -
|
|
122
|
+
18. **addressbook_query** - PREFERRED: Search and filter contacts efficiently by name, email, or organization
|
|
80
123
|
19. **addressbook_multi_get** - Batch fetch multiple specific contacts by URLs
|
|
81
124
|
|
|
82
125
|
### VTODO Tools (7 tools)
|
|
83
126
|
|
|
84
|
-
20. **list_todos** - List ALL todos/tasks (
|
|
127
|
+
20. **list_todos** - List ALL todos/tasks (use todo_query for filtered searches)
|
|
85
128
|
21. **create_todo** - Create a new todo/task with optional due date, priority, status
|
|
86
|
-
22. **update_todo** -
|
|
129
|
+
22. **update_todo** - PREFERRED: Update any todo field (SUMMARY, STATUS, PRIORITY, DUE, PERCENT-COMPLETE, custom X-* properties)
|
|
87
130
|
23. **update_todo_raw** - Update todo with raw VTODO iCal data (advanced)
|
|
88
131
|
24. **delete_todo** - Delete a todo/task permanently
|
|
89
|
-
25. **todo_query** -
|
|
132
|
+
25. **todo_query** - PREFERRED: Search and filter todos efficiently by status/due date
|
|
90
133
|
26. **todo_multi_get** - Batch fetch multiple specific todos by URLs
|
|
91
134
|
|
|
92
135
|
---
|
|
93
136
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
See how complete tool coverage transforms basic operations into powerful workflows.
|
|
137
|
+
## Real-World Applications
|
|
97
138
|
|
|
98
139
|
### n8n Automation Workflows
|
|
99
140
|
- **Meeting Management**: "Show me all Friday meetings" → calendar_query with date filter returns only relevant events
|
|
@@ -109,120 +150,54 @@ See how complete tool coverage transforms basic operations into powerful workflo
|
|
|
109
150
|
|
|
110
151
|
---
|
|
111
152
|
|
|
112
|
-
##
|
|
113
|
-
|
|
114
|
-
### Installation
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
git clone https://github.com/PhilflowIO/dav-mcp.git
|
|
118
|
-
cd dav-mcp
|
|
119
|
-
npm install
|
|
120
|
-
cp .env.example .env
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Configuration
|
|
153
|
+
## Works Across All Major Providers
|
|
124
154
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```env
|
|
128
|
-
# CalDAV/CardDAV Server
|
|
129
|
-
CALDAV_SERVER_URL=https://dav.example.com
|
|
130
|
-
CALDAV_USERNAME=your_username
|
|
131
|
-
CALDAV_PASSWORD=your_password
|
|
132
|
-
|
|
133
|
-
# MCP Server
|
|
134
|
-
PORT=3000
|
|
135
|
-
MCP_SERVER_NAME=dav-mcp
|
|
136
|
-
MCP_SERVER_VERSION=2.6.0
|
|
137
|
-
|
|
138
|
-
# Authentication (optional)
|
|
139
|
-
BEARER_TOKEN=your-secure-token-here
|
|
140
|
-
```
|
|
155
|
+
Works with any CalDAV/CardDAV server that follows RFC 4791 and RFC 6352:
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```
|
|
157
|
+
- **Nextcloud** - Full support
|
|
158
|
+
- **Baikal** - Full support
|
|
159
|
+
- **Radicale** - Full support
|
|
160
|
+
- **iCloud** - Works with app-specific password
|
|
161
|
+
- **Any RFC-compliant server** - Standard protocol support
|
|
148
162
|
|
|
149
163
|
---
|
|
150
164
|
|
|
151
|
-
##
|
|
152
|
-
|
|
153
|
-
### n8n Workflow
|
|
154
|
-
|
|
155
|
-
1. **Start HTTP server**: `BEARER_TOKEN=your-token npm run start:http`
|
|
156
|
-
2. **Add "AI Agent" node**
|
|
157
|
-
3. **Add "MCP Client Tool" node** and connect to AI Agent
|
|
158
|
-
4. **Configure the connection:**
|
|
159
|
-
- **MCP Endpoint**: `http://localhost:3000/mcp`
|
|
160
|
-
- **Authentication Method**: `Bearer`
|
|
161
|
-
- **Bearer Token**: Your token
|
|
162
|
-
|
|
163
|
-
5. **Example prompts:**
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
"List all my calendars"
|
|
167
|
-
"Create a meeting tomorrow at 2 PM"
|
|
168
|
-
"Show me all events in October"
|
|
169
|
-
"Find all contacts at Google"
|
|
170
|
-
"Create a new contact for Jane Smith"
|
|
171
|
-
"Show overdue tasks with high priority"
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Claude Desktop
|
|
165
|
+
## Google Calendar (OAuth2)
|
|
175
166
|
|
|
176
|
-
|
|
167
|
+
For Google Calendar, use OAuth2 authentication:
|
|
177
168
|
|
|
178
169
|
```json
|
|
179
170
|
{
|
|
180
171
|
"mcpServers": {
|
|
181
172
|
"dav-mcp": {
|
|
182
|
-
"command": "
|
|
183
|
-
"args": ["
|
|
173
|
+
"command": "npx",
|
|
174
|
+
"args": ["-y", "dav-mcp"],
|
|
184
175
|
"env": {
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
176
|
+
"AUTH_METHOD": "OAuth",
|
|
177
|
+
"GOOGLE_USER": "your@gmail.com",
|
|
178
|
+
"GOOGLE_CLIENT_ID": "your-client-id",
|
|
179
|
+
"GOOGLE_CLIENT_SECRET": "your-client-secret",
|
|
180
|
+
"GOOGLE_REFRESH_TOKEN": "your-refresh-token"
|
|
188
181
|
}
|
|
189
182
|
}
|
|
190
183
|
}
|
|
191
184
|
}
|
|
192
185
|
```
|
|
193
186
|
|
|
194
|
-
**Config file locations:**
|
|
195
|
-
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
196
|
-
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
197
|
-
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
198
|
-
|
|
199
|
-
**Restart Claude Desktop** after adding the configuration.
|
|
200
|
-
|
|
201
187
|
---
|
|
202
188
|
|
|
203
|
-
##
|
|
204
|
-
|
|
205
|
-
Works with any CalDAV/CardDAV server that follows RFC 4791 and RFC 6352:
|
|
206
|
-
|
|
207
|
-
- ✅ **Nextcloud** - Full support
|
|
208
|
-
- ✅ **Baikal** - Full support
|
|
209
|
-
- ✅ **Radicale** - Full support
|
|
210
|
-
- ✅ **iCloud** - Works with app-specific password
|
|
211
|
-
- ✅ **Any RFC-compliant server** - Standard protocol support
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
## 🔒 Security
|
|
189
|
+
## Security
|
|
215
190
|
|
|
216
191
|
- **Input Validation**: All inputs validated with Zod schemas before execution
|
|
217
|
-
- **Rate Limiting**: 100 requests/minute per session
|
|
218
|
-
- **Bearer Auth**:
|
|
192
|
+
- **Rate Limiting**: 100 requests/minute per session (HTTP mode)
|
|
193
|
+
- **Bearer Auth**: Token authentication for HTTP transport
|
|
219
194
|
- **No Credential Storage**: Pass-through only, never logged or cached
|
|
220
195
|
- **Structured Logging**: Audit trail with request IDs, no PII exposure
|
|
221
196
|
- **CORS Protection**: Whitelist origins, block cross-site attacks
|
|
222
197
|
|
|
198
|
+
---
|
|
223
199
|
|
|
224
|
-
|
|
225
|
-
## 📚 Documentation
|
|
200
|
+
## Documentation
|
|
226
201
|
|
|
227
202
|
- **[MCP Specification](https://modelcontextprotocol.io/specification/2025-03-26)** - Model Context Protocol docs
|
|
228
203
|
- **[tsdav Docs](https://tsdav.vercel.app/docs/intro)** - CalDAV/CardDAV library reference
|
|
@@ -231,19 +206,19 @@ Works with any CalDAV/CardDAV server that follows RFC 4791 and RFC 6352:
|
|
|
231
206
|
|
|
232
207
|
---
|
|
233
208
|
|
|
234
|
-
##
|
|
209
|
+
## Contributing
|
|
235
210
|
|
|
236
211
|
Pull requests are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
237
212
|
|
|
238
213
|
---
|
|
239
214
|
|
|
240
|
-
##
|
|
215
|
+
## License
|
|
241
216
|
|
|
242
217
|
MIT License - see [LICENSE](LICENSE) for details
|
|
243
218
|
|
|
244
219
|
---
|
|
245
220
|
|
|
246
|
-
##
|
|
221
|
+
## Acknowledgments
|
|
247
222
|
|
|
248
223
|
Built with:
|
|
249
224
|
- **[tsdav](https://github.com/natelindev/tsdav)** - Excellent TypeScript CalDAV/CardDAV library
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dav-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Complete DAV integration for AI - Calendar (CalDAV), Contacts (CardDAV), and Tasks (VTODO) management via MCP protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server-stdio.js",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"CHANGELOG.md"
|
|
61
61
|
],
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
63
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
64
64
|
"cors": "^2.8.5",
|
|
65
|
-
"dotenv": "^
|
|
66
|
-
"express": "^
|
|
65
|
+
"dotenv": "^17.2.3",
|
|
66
|
+
"express": "^5.2.1",
|
|
67
67
|
"express-rate-limit": "^8.1.0",
|
|
68
68
|
"ical.js": "^2.2.1",
|
|
69
69
|
"tsdav": "github:PhilflowIO/tsdav#master",
|
package/src/logger.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
// Detect STDIO transport mode - must write to stderr to preserve stdout for JSON-RPC
|
|
10
10
|
const isStdioMode = process.env.MCP_TRANSPORT === 'stdio';
|
|
11
11
|
|
|
12
|
+
// Cache environment check to avoid repeated env access (satisfies CodeQL)
|
|
13
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
14
|
+
|
|
12
15
|
class JSONLogger {
|
|
13
16
|
constructor(context = {}, level = 'info') {
|
|
14
17
|
this.context = context;
|
|
@@ -61,7 +64,7 @@ class JSONLogger {
|
|
|
61
64
|
: (msg) => console.log(msg);
|
|
62
65
|
|
|
63
66
|
// Pretty-print in development, single-line JSON in production
|
|
64
|
-
if (
|
|
67
|
+
if (!isProduction) {
|
|
65
68
|
// Development: colored output with readable format
|
|
66
69
|
const colorMap = {
|
|
67
70
|
error: '\x1b[31m', // Red
|
package/src/server-http.js
CHANGED
|
@@ -175,7 +175,7 @@ function createMCPServer(requestId) {
|
|
|
175
175
|
const server = new Server(
|
|
176
176
|
{
|
|
177
177
|
name: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
178
|
-
version: process.env.MCP_SERVER_VERSION || '3.0.
|
|
178
|
+
version: process.env.MCP_SERVER_VERSION || '3.0.1',
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
capabilities: {
|
|
@@ -310,7 +310,7 @@ app.get('/health', (req, res) => {
|
|
|
310
310
|
res.json({
|
|
311
311
|
status: 'healthy',
|
|
312
312
|
server: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
313
|
-
version: process.env.MCP_SERVER_VERSION || '3.0.
|
|
313
|
+
version: process.env.MCP_SERVER_VERSION || '3.0.1',
|
|
314
314
|
transport: 'http-stateless',
|
|
315
315
|
timestamp: new Date().toISOString(),
|
|
316
316
|
tools: tools.length,
|
|
@@ -324,7 +324,7 @@ app.get('/health', (req, res) => {
|
|
|
324
324
|
app.get('/', (req, res) => {
|
|
325
325
|
res.json({
|
|
326
326
|
name: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
327
|
-
version: process.env.MCP_SERVER_VERSION || '3.0.
|
|
327
|
+
version: process.env.MCP_SERVER_VERSION || '3.0.1',
|
|
328
328
|
transport: 'http-stateless',
|
|
329
329
|
description: 'MCP Streamable HTTP Server for CalDAV/CardDAV integration (stateless)',
|
|
330
330
|
endpoints: {
|
package/src/server-stdio.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* dav-mcp
|
|
3
|
+
* dav-mcp - Main Entry Point
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* to avoid corrupting JSON-RPC messages on stdout.
|
|
5
|
+
* Supports two modes:
|
|
6
|
+
* - STDIO (default): For local clients (Claude Desktop, Cursor, VS Code)
|
|
7
|
+
* - HTTP (--http flag): For remote clients (n8n, cloud deployments)
|
|
9
8
|
*
|
|
10
9
|
* Usage:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* npx dav-mcp
|
|
10
|
+
* npx dav-mcp # STDIO mode (default)
|
|
11
|
+
* npx dav-mcp --http # HTTP mode on port 3000
|
|
12
|
+
* npx dav-mcp --http --port=8080 # HTTP mode on custom port
|
|
15
13
|
*
|
|
16
14
|
* Configuration via environment variables:
|
|
17
15
|
* - CALDAV_SERVER_URL: CalDAV server URL
|
|
18
16
|
* - CALDAV_USERNAME: Username for Basic Auth
|
|
19
17
|
* - CALDAV_PASSWORD: Password for Basic Auth
|
|
20
18
|
* - AUTH_METHOD: 'Basic' (default) or 'OAuth'
|
|
19
|
+
* - BEARER_TOKEN: Required for HTTP mode
|
|
21
20
|
*
|
|
22
21
|
* For OAuth2 (Google Calendar):
|
|
23
22
|
* - GOOGLE_SERVER_URL: Google CalDAV URL
|
|
@@ -27,146 +26,161 @@
|
|
|
27
26
|
* - GOOGLE_REFRESH_TOKEN: OAuth2 refresh token
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
process.
|
|
29
|
+
// Parse CLI arguments BEFORE any imports
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const isHttpMode = args.includes('--http');
|
|
32
|
+
const portArg = args.find(a => a.startsWith('--port='));
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (isHttpMode) {
|
|
35
|
+
// HTTP mode - set port and load HTTP server
|
|
36
|
+
if (portArg) {
|
|
37
|
+
process.env.PORT = portArg.split('=')[1];
|
|
38
|
+
}
|
|
39
|
+
// Dynamic import of HTTP server (it will start itself)
|
|
40
|
+
import('./server-http.js');
|
|
41
|
+
} else {
|
|
42
|
+
// STDIO mode - run STDIO server
|
|
43
|
+
startStdioServer();
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
async function startStdioServer() {
|
|
47
|
+
// Set STDIO mode BEFORE importing logger
|
|
48
|
+
process.env.MCP_TRANSPORT = 'stdio';
|
|
49
|
+
|
|
50
|
+
const dotenv = await import('dotenv');
|
|
51
|
+
const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
|
|
52
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
53
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = await import('@modelcontextprotocol/sdk/types.js');
|
|
54
|
+
|
|
55
|
+
const { tsdavManager } = await import('./tsdav-client.js');
|
|
56
|
+
const { tools } = await import('./tools/index.js');
|
|
57
|
+
const { createToolErrorResponse, MCP_ERROR_CODES } = await import('./error-handler.js');
|
|
58
|
+
const { logger } = await import('./logger.js');
|
|
59
|
+
const { initializeToolCallLogger, getToolCallLogger } = await import('./tool-call-logger.js');
|
|
60
|
+
|
|
61
|
+
// Load environment variables
|
|
62
|
+
dotenv.default.config();
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initialize tsdav clients based on auth method
|
|
66
|
+
*/
|
|
67
|
+
async function initializeTsdav() {
|
|
68
|
+
const authMethod = process.env.AUTH_METHOD || 'Basic';
|
|
69
|
+
|
|
70
|
+
if (authMethod === 'OAuth' || authMethod === 'Oauth') {
|
|
71
|
+
// OAuth2 Configuration (e.g., Google Calendar)
|
|
72
|
+
logger.info('Initializing with OAuth2 authentication');
|
|
73
|
+
|
|
74
|
+
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET || !process.env.GOOGLE_REFRESH_TOKEN) {
|
|
75
|
+
throw new Error('OAuth2 requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await tsdavManager.initialize({
|
|
79
|
+
serverUrl: process.env.GOOGLE_SERVER_URL || 'https://apidata.googleusercontent.com/caldav/v2/',
|
|
80
|
+
authMethod: 'OAuth',
|
|
81
|
+
username: process.env.GOOGLE_USER,
|
|
82
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
83
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
84
|
+
refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
|
|
85
|
+
tokenUrl: process.env.GOOGLE_TOKEN_URL || 'https://accounts.google.com/o/oauth2/token',
|
|
86
|
+
});
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
88
|
+
logger.info('OAuth2 clients initialized successfully');
|
|
89
|
+
} else {
|
|
90
|
+
// Basic Auth Configuration (standard CalDAV servers)
|
|
91
|
+
logger.info('Initializing with Basic authentication');
|
|
46
92
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
async function initializeTsdav() {
|
|
51
|
-
const authMethod = process.env.AUTH_METHOD || 'Basic';
|
|
93
|
+
if (!process.env.CALDAV_SERVER_URL || !process.env.CALDAV_USERNAME || !process.env.CALDAV_PASSWORD) {
|
|
94
|
+
throw new Error('Basic Auth requires CALDAV_SERVER_URL, CALDAV_USERNAME, and CALDAV_PASSWORD');
|
|
95
|
+
}
|
|
52
96
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
await tsdavManager.initialize({
|
|
98
|
+
serverUrl: process.env.CALDAV_SERVER_URL,
|
|
99
|
+
authMethod: 'Basic',
|
|
100
|
+
username: process.env.CALDAV_USERNAME,
|
|
101
|
+
password: process.env.CALDAV_PASSWORD,
|
|
102
|
+
});
|
|
56
103
|
|
|
57
|
-
|
|
58
|
-
throw new Error('OAuth2 requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN');
|
|
104
|
+
logger.info('Basic Auth clients initialized successfully');
|
|
59
105
|
}
|
|
106
|
+
}
|
|
60
107
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Create MCP Server with tool handlers
|
|
110
|
+
*/
|
|
111
|
+
function createMCPServer() {
|
|
112
|
+
const server = new Server(
|
|
113
|
+
{
|
|
114
|
+
name: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
115
|
+
version: process.env.MCP_SERVER_VERSION || '3.0.1',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
capabilities: {
|
|
119
|
+
tools: {},
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Register tools/list handler
|
|
125
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
126
|
+
logger.debug({ count: tools.length }, 'tools/list request received');
|
|
127
|
+
return {
|
|
128
|
+
tools: tools.map(t => ({
|
|
129
|
+
name: t.name,
|
|
130
|
+
description: t.description,
|
|
131
|
+
inputSchema: t.inputSchema,
|
|
132
|
+
})),
|
|
133
|
+
};
|
|
69
134
|
});
|
|
70
135
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
136
|
+
// Register tools/call handler
|
|
137
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
138
|
+
const toolName = request.params.name;
|
|
139
|
+
const toolArgs = request.params.arguments || {};
|
|
140
|
+
const toolCallLogger = getToolCallLogger();
|
|
141
|
+
|
|
142
|
+
logger.info({ tool: toolName }, 'tools/call request received');
|
|
143
|
+
|
|
144
|
+
const tool = tools.find(t => t.name === toolName);
|
|
145
|
+
if (!tool) {
|
|
146
|
+
logger.error({ tool: toolName }, 'Tool not found');
|
|
147
|
+
const error = new Error(`Unknown tool: ${toolName}`);
|
|
148
|
+
error.code = MCP_ERROR_CODES.METHOD_NOT_FOUND;
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
toolCallLogger.logToolCallStart(toolName, toolArgs, { transport: 'stdio' });
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
logger.debug({ tool: toolName }, 'Executing tool');
|
|
157
|
+
const result = await tool.handler(toolArgs);
|
|
158
|
+
const duration = Date.now() - startTime;
|
|
159
|
+
|
|
160
|
+
logger.info({ tool: toolName, duration }, 'Tool executed successfully');
|
|
161
|
+
toolCallLogger.logToolCallSuccess(toolName, toolArgs, result, {
|
|
162
|
+
transport: 'stdio',
|
|
163
|
+
duration,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return result;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
const duration = Date.now() - startTime;
|
|
169
|
+
|
|
170
|
+
logger.error({ tool: toolName, error: error.message }, 'Tool execution error');
|
|
171
|
+
toolCallLogger.logToolCallError(toolName, toolArgs, error, {
|
|
172
|
+
transport: 'stdio',
|
|
173
|
+
duration,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return createToolErrorResponse(error, process.env.NODE_ENV === 'development');
|
|
177
|
+
}
|
|
85
178
|
});
|
|
86
179
|
|
|
87
|
-
|
|
180
|
+
return server;
|
|
88
181
|
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Create MCP Server with tool handlers
|
|
93
|
-
*/
|
|
94
|
-
function createMCPServer() {
|
|
95
|
-
const server = new Server(
|
|
96
|
-
{
|
|
97
|
-
name: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
98
|
-
version: process.env.MCP_SERVER_VERSION || '3.0.0',
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
capabilities: {
|
|
102
|
-
tools: {},
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// Register tools/list handler
|
|
108
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
109
|
-
logger.debug({ count: tools.length }, 'tools/list request received');
|
|
110
|
-
return {
|
|
111
|
-
tools: tools.map(t => ({
|
|
112
|
-
name: t.name,
|
|
113
|
-
description: t.description,
|
|
114
|
-
inputSchema: t.inputSchema,
|
|
115
|
-
})),
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Register tools/call handler
|
|
120
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
121
|
-
const toolName = request.params.name;
|
|
122
|
-
const args = request.params.arguments || {};
|
|
123
|
-
const toolCallLogger = getToolCallLogger();
|
|
124
|
-
|
|
125
|
-
logger.info({ tool: toolName }, 'tools/call request received');
|
|
126
|
-
|
|
127
|
-
const tool = tools.find(t => t.name === toolName);
|
|
128
|
-
if (!tool) {
|
|
129
|
-
logger.error({ tool: toolName }, 'Tool not found');
|
|
130
|
-
const error = new Error(`Unknown tool: ${toolName}`);
|
|
131
|
-
error.code = MCP_ERROR_CODES.METHOD_NOT_FOUND;
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
182
|
|
|
135
|
-
|
|
136
|
-
toolCallLogger.logToolCallStart(toolName, args, { transport: 'stdio' });
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
logger.debug({ tool: toolName }, 'Executing tool');
|
|
140
|
-
const result = await tool.handler(args);
|
|
141
|
-
const duration = Date.now() - startTime;
|
|
142
|
-
|
|
143
|
-
logger.info({ tool: toolName, duration }, 'Tool executed successfully');
|
|
144
|
-
toolCallLogger.logToolCallSuccess(toolName, args, result, {
|
|
145
|
-
transport: 'stdio',
|
|
146
|
-
duration,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
return result;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
const duration = Date.now() - startTime;
|
|
152
|
-
|
|
153
|
-
logger.error({ tool: toolName, error: error.message }, 'Tool execution error');
|
|
154
|
-
toolCallLogger.logToolCallError(toolName, args, error, {
|
|
155
|
-
transport: 'stdio',
|
|
156
|
-
duration,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
return createToolErrorResponse(error, process.env.NODE_ENV === 'development');
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
return server;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Main entry point
|
|
168
|
-
*/
|
|
169
|
-
async function main() {
|
|
183
|
+
// Main entry point
|
|
170
184
|
try {
|
|
171
185
|
logger.info('Starting dav-mcp STDIO server...');
|
|
172
186
|
|
|
@@ -189,7 +203,7 @@ async function main() {
|
|
|
189
203
|
|
|
190
204
|
logger.info({
|
|
191
205
|
name: process.env.MCP_SERVER_NAME || 'dav-mcp',
|
|
192
|
-
version: process.env.MCP_SERVER_VERSION || '3.0.
|
|
206
|
+
version: process.env.MCP_SERVER_VERSION || '3.0.1',
|
|
193
207
|
tools: tools.length,
|
|
194
208
|
}, 'dav-mcp STDIO server ready');
|
|
195
209
|
|
|
@@ -197,29 +211,26 @@ async function main() {
|
|
|
197
211
|
logger.error({ error: error.message, stack: error.stack }, 'Fatal error starting server');
|
|
198
212
|
process.exit(1);
|
|
199
213
|
}
|
|
200
|
-
}
|
|
201
214
|
|
|
202
|
-
// Graceful shutdown handlers
|
|
203
|
-
process.on('SIGTERM', () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
process.on('SIGINT', () => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Handle uncaught errors gracefully
|
|
214
|
-
process.on('uncaughtException', (error) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
process.on('unhandledRejection', (reason) => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Start the server
|
|
225
|
-
main();
|
|
215
|
+
// Graceful shutdown handlers
|
|
216
|
+
process.on('SIGTERM', () => {
|
|
217
|
+
logger.info('Received SIGTERM, shutting down...');
|
|
218
|
+
process.exit(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
process.on('SIGINT', () => {
|
|
222
|
+
logger.info('Received SIGINT, shutting down...');
|
|
223
|
+
process.exit(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Handle uncaught errors gracefully
|
|
227
|
+
process.on('uncaughtException', (error) => {
|
|
228
|
+
logger.error({ error: error.message, stack: error.stack }, 'Uncaught exception');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
process.on('unhandledRejection', (reason) => {
|
|
233
|
+
logger.error({ reason }, 'Unhandled promise rejection');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
@@ -99,7 +99,7 @@ export const updateTodoFields = {
|
|
|
99
99
|
|
|
100
100
|
// Step 3: Send the updated todo back to server
|
|
101
101
|
const updateResponse = await client.updateTodo({
|
|
102
|
-
|
|
102
|
+
calendarObject: {
|
|
103
103
|
url: validated.todo_url,
|
|
104
104
|
data: updatedData,
|
|
105
105
|
etag: validated.todo_etag
|