Skip to content

Commit 5d0170b

Browse files
authored
Merge pull request #20 from PhilflowIO/feature/integrate-tsdav-utils
Integrate tsdav-utils for field-agnostic updates
2 parents 09f15cf + 53b0a14 commit 5d0170b

File tree

8 files changed

+202
-220
lines changed

8 files changed

+202
-220
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ full-test.js
3333
# tsdav library (npm dependency)
3434
tsdav/
3535

36-
# Internal development guides
36+
# Internal development guides (keep local only, never commit)
3737
CLAUDE.md
38+
GIT_WORKFLOW.md
39+
RESEARCH_FINDINGS.md
3840
COMPATIBILITY.md
41+
INTEGRATION_NOTES.md
42+
MIGRATION_*.md
3943

4044
# Test artifacts
4145
tests/integration/test-results-*.json

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Changed
11+
- **Field-agnostic updates**: Integrated tsdav-utils for universal field update support
12+
- `update_event` now supports all RFC 5545 iCalendar properties (SUMMARY, DESCRIPTION, LOCATION, DTSTART, DTEND, STATUS, etc.)
13+
- `update_todo` now supports all RFC 5545 VTODO properties (SUMMARY, DESCRIPTION, STATUS, PRIORITY, DUE, PERCENT-COMPLETE, etc.)
14+
- `update_contact` now supports all RFC 6350 vCard properties (FN, N, EMAIL, TEL, ORG, TITLE, NOTE, URL, ADR, BDAY, etc.)
15+
- All update tools now accept custom X-* properties for extensions (e.g., X-ZOOM-LINK, X-MEETING-ROOM)
16+
- Replaced manual iCal/vCard string manipulation with structured field updates via tsdav-utils
17+
- Simplified update tool implementations (reduced code by 40-45% per tool)
18+
- Updated input schemas to accept any RFC property name (field-agnostic validation)
19+
20+
### Added
21+
- **New dependency**: tsdav-utils v0.1.0 for field-based calendar/contact operations
22+
- Support for updating event locations (LOCATION field)
23+
- Support for updating event date/time (DTSTART, DTEND fields)
24+
- Support for updating event status (STATUS: TENTATIVE/CONFIRMED/CANCELLED)
25+
- Support for updating todo status (STATUS: NEEDS-ACTION/IN-PROCESS/COMPLETED/CANCELLED)
26+
- Support for updating todo priority (PRIORITY: 0-9)
27+
- Support for updating todo due dates (DUE field)
28+
- Support for updating todo completion percentage (PERCENT-COMPLETE: 0-100)
29+
- Support for updating contact addresses (ADR field)
30+
- Support for updating contact birthdays (BDAY field)
31+
- Support for custom X-* properties across all update tools
32+
33+
### Dependencies
34+
- Added: tsdav-utils (v0.1.0) - Field-agnostic utility layer for RFC-compliant updates
35+
36+
## [2.6.0] - Previous Release
37+
38+
Initial release with 26 MCP tools for CalDAV, CardDAV, and VTODO operations.
39+

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ When partial tools force your AI to improvise, complete tools let it **execute p
2020
| **Calendar Management** | ✅ Full CRUD (11 tools) | ⚠️ Create + list only (2-3 tools) |
2121
| **Contact Management** | ✅ Complete CardDAV (8 tools) | ❌ Often missing entirely |
2222
| **Task Management** | ✅ Full VTODO support (7 tools) | ❌ Rarely included |
23-
| **Field-Based Updates** |LLM-friendly (no iCal/vCard format) | ❌ Rarely available |
23+
| **Field-Based Updates** |All RFC properties + custom fields | ❌ Rarely available |
2424
| **Server-Side Filtering** | ✅ Efficient queries | ❌ Dumps all data |
2525
| **Multi-Provider** | ✅ Any CalDAV/CardDAV server | ⚠️ Limited provider support |
2626
| **Total Tools** | **26 tools** | **2-6 tools** |
@@ -35,7 +35,7 @@ When partial tools force your AI to improvise, complete tools let it **execute p
3535
- **CalDAV Integration**: ~88% tsdav coverage (11 tools)
3636
- **CardDAV Integration**: 100% tsdav coverage (8 tools)
3737
- **VTODO Support**: Full task management with status, priorities, due dates (7 tools)
38-
- **Field-Based Updates**: LLM-friendly update tools (no iCal/vCard formatting required)
38+
- **Field-Based Updates**: Field-agnostic updates via tsdav-utils - supports all RFC 5545/6350 properties + custom X-* fields
3939
- **RFC-Compliant**: ical.js for RFC 5545 (iCalendar) and RFC 6350 (vCard) support
4040

4141
### Production-Ready Infrastructure
@@ -61,7 +61,7 @@ When partial tools force your AI to improvise, complete tools let it **execute p
6161
1. **list_calendars** - List all available calendars
6262
2. **list_events** - List ALL events (⚠️ WARNING: use calendar_query for filtered searches)
6363
3. **create_event** - Create a new calendar event
64-
4. **update_event** - ⭐ PREFERRED: Update event fields (summary, description) without iCal formatting
64+
4. **update_event** - ⭐ PREFERRED: Update any event field (SUMMARY, LOCATION, DTSTART, STATUS, custom X-* properties)
6565
5. **update_event_raw** - Update event with raw iCal data (advanced)
6666
6. **delete_event** - Delete an event permanently
6767
7. **calendar_query** - ⭐ PREFERRED: Search and filter events efficiently by text, date range, or location
@@ -75,7 +75,7 @@ When partial tools force your AI to improvise, complete tools let it **execute p
7575
12. **list_addressbooks** - List all available address books
7676
13. **list_contacts** - List ALL contacts (⚠️ WARNING: use addressbook_query for filtered searches)
7777
14. **create_contact** - Create a new contact (vCard)
78-
15. **update_contact** - ⭐ PREFERRED: Update contact fields (name, email, phone, etc.) without vCard formatting
78+
15. **update_contact** - ⭐ PREFERRED: Update any contact field (FN, EMAIL, TEL, ORG, ADR, custom X-* properties)
7979
16. **update_contact_raw** - Update contact with raw vCard data (advanced)
8080
17. **delete_contact** - Delete a contact permanently
8181
18. **addressbook_query** - ⭐ PREFERRED: Search and filter contacts efficiently by name, email, or organization
@@ -85,7 +85,7 @@ When partial tools force your AI to improvise, complete tools let it **execute p
8585

8686
20. **list_todos** - List ALL todos/tasks (⚠️ WARNING: use todo_query for filtered searches)
8787
21. **create_todo** - Create a new todo/task with optional due date, priority, status
88-
22. **update_todo** - ⭐ PREFERRED: Update todo fields (summary, description) without iCal formatting
88+
22. **update_todo** - ⭐ PREFERRED: Update any todo field (SUMMARY, STATUS, PRIORITY, DUE, PERCENT-COMPLETE, custom X-* properties)
8989
23. **update_todo_raw** - Update todo with raw VTODO iCal data (advanced)
9090
24. **delete_todo** - Delete a todo/task permanently
9191
25. **todo_query** - ⭐ PREFERRED: Search and filter todos efficiently by status/due date
@@ -248,6 +248,7 @@ MIT License - see [LICENSE](LICENSE) for details
248248

249249
Built with:
250250
- **[tsdav](https://github.com/natelindev/tsdav)** - Excellent TypeScript CalDAV/CardDAV library
251+
- **[tsdav-utils](https://github.com/PhilflowIO/tsdav-utils)** - Field-agnostic utility layer for RFC-compliant field updates
251252
- **[MCP SDK](https://modelcontextprotocol.io)** - Model Context Protocol by Anthropic
252253
- **[ical.js](https://github.com/kewisch/ical.js)** - RFC-compliant iCalendar parser
253254

package-lock.json

Lines changed: 26 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"express-rate-limit": "^8.1.0",
6666
"ical.js": "^2.2.1",
6767
"tsdav": "github:PhilflowIO/tsdav#master",
68+
"tsdav-utils": "file:../tsdav_utils",
6869
"zod": "^3.25.76"
6970
},
7071
"devDependencies": {

src/tools/calendar/update-event-fields.js

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,32 @@ import { tsdavManager } from '../../tsdav-client.js';
22
import { validateInput } from '../../validation.js';
33
import { formatSuccess, formatError } from '../../formatters.js';
44
import { z } from 'zod';
5-
import ICAL from 'ical.js';
6-
// Import using namespace import for maximum CommonJS/ESM compatibility
7-
import * as tsdavAll from 'tsdav';
8-
// Access as property - works in both ESM (named export) and CJS (exports.xxx)
9-
const tsdavUpdateEventFields = tsdavAll.updateEventFields;
10-
11-
/**
12-
* Manual iCal field update function as fallback
13-
* Updates specific fields in an iCal string when tsdav's updateEventFields is not available
14-
*/
15-
function manualUpdateEventFields(calendarObject, fields) {
16-
const jcalData = ICAL.parse(calendarObject.data);
17-
const comp = new ICAL.Component(jcalData);
18-
const vevent = comp.getFirstSubcomponent('vevent');
19-
20-
if (!vevent) {
21-
throw new Error('No VEVENT component found in calendar object');
22-
}
23-
24-
const modified = [];
25-
const warnings = [];
26-
27-
// Update fields
28-
if (fields.summary !== undefined) {
29-
vevent.updatePropertyWithValue('summary', fields.summary);
30-
modified.push('summary');
31-
}
32-
33-
if (fields.description !== undefined) {
34-
vevent.updatePropertyWithValue('description', fields.description);
35-
modified.push('description');
36-
}
37-
38-
// Convert back to iCal string
39-
const updatedData = comp.toString();
40-
41-
return {
42-
data: updatedData,
43-
modified,
44-
warnings
45-
};
46-
}
5+
import { updateFields } from 'tsdav-utils';
476

487
/**
498
* Schema for field-based event updates
50-
* Currently supports MVP fields from tsdav v2.2.0: SUMMARY and DESCRIPTION
51-
* Note: tsdav uses UPPERCASE field names internally for iCal compatibility
9+
* Supports all RFC 5545 iCalendar properties via tsdav-utils
10+
* Common fields: SUMMARY, DESCRIPTION, LOCATION, DTSTART, DTEND, STATUS
11+
* Custom properties: Any X-* property
5212
*/
5313
const updateEventFieldsSchema = z.object({
5414
event_url: z.string().url('Event URL must be a valid URL'),
5515
event_etag: z.string().min(1, 'Event etag is required'),
56-
fields: z.object({
57-
summary: z.string().optional(),
58-
description: z.string().optional(),
59-
// Future fields (pending tsdav support):
60-
// location: z.string().optional(),
61-
// start_date: z.string().optional(),
62-
// end_date: z.string().optional(),
63-
}).optional()
16+
fields: z.record(z.string()).optional()
6417
});
6518

6619
/**
67-
* Wrapper for tsdav's native updateEventFields function
68-
* Uses the field-based update implementation from tsdav v2.2.0 MVP
20+
* Field-agnostic event update tool powered by tsdav-utils
21+
* Supports all RFC 5545 iCalendar properties without validation
6922
*
70-
* Current MVP limitations:
71-
* - Only SUMMARY and DESCRIPTION fields are officially supported
72-
* - Other iCal fields require using the full update_event tool
23+
* Features:
24+
* - Any standard VEVENT property (SUMMARY, DESCRIPTION, LOCATION, DTSTART, etc.)
25+
* - Custom X-* properties for extensions
26+
* - Field-agnostic: no pre-defined field list required
7327
*/
7428
export const updateEventFields = {
7529
name: 'update_event',
76-
description: 'PREFERRED: Update event fields (summary, description) easily without iCal formatting. Use this for simple event updates. For advanced iCal properties, use update_event_raw instead. Currently supports summary and description fields only.',
30+
description: 'PREFERRED: Update event fields without iCal formatting. Supports: SUMMARY (title), DESCRIPTION (details), LOCATION (place), DTSTART (start time), DTEND (end time), STATUS (TENTATIVE/CONFIRMED/CANCELLED), and any RFC 5545 property including custom X-* properties (e.g., X-ZOOM-LINK, X-MEETING-ROOM).',
7731
inputSchema: {
7832
type: 'object',
7933
properties: {
@@ -87,15 +41,34 @@ export const updateEventFields = {
8741
},
8842
fields: {
8943
type: 'object',
90-
description: 'Fields to update - only include fields you want to change. Currently supported: summary, description',
44+
description: 'Fields to update - use UPPERCASE property names (e.g., SUMMARY, LOCATION, DTSTART). Any RFC 5545 property or custom X-* property is supported.',
45+
additionalProperties: {
46+
type: 'string'
47+
},
9148
properties: {
92-
summary: {
49+
SUMMARY: {
50+
type: 'string',
51+
description: 'Event title/summary'
52+
},
53+
DESCRIPTION: {
54+
type: 'string',
55+
description: 'Event description/details'
56+
},
57+
LOCATION: {
9358
type: 'string',
94-
description: 'Event title/summary - the main heading shown in calendars'
59+
description: 'Physical or virtual meeting location'
9560
},
96-
description: {
61+
DTSTART: {
9762
type: 'string',
98-
description: 'Event description - detailed information about the event'
63+
description: 'Start datetime (ISO 8601 or iCal format: 20250128T100000Z)'
64+
},
65+
DTEND: {
66+
type: 'string',
67+
description: 'End datetime (ISO 8601 or iCal format)'
68+
},
69+
STATUS: {
70+
type: 'string',
71+
description: 'Event status: TENTATIVE, CONFIRMED, or CANCELLED'
9972
}
10073
}
10174
}
@@ -120,42 +93,23 @@ export const updateEventFields = {
12093

12194
const calendarObject = currentEvents[0];
12295

123-
// Step 2: Transform snake_case field names to UPPERCASE for tsdav
124-
// tsdav's updateEventFields expects UPPERCASE iCal property names
125-
const tsdavFields = {};
126-
if (validated.fields) {
127-
if (validated.fields.summary !== undefined) {
128-
tsdavFields.summary = validated.fields.summary;
129-
}
130-
if (validated.fields.description !== undefined) {
131-
tsdavFields.description = validated.fields.description;
132-
}
133-
}
134-
135-
// Step 3: Use tsdav's native updateEventFields function or fallback to manual implementation
136-
let result;
137-
if (typeof tsdavUpdateEventFields === 'function') {
138-
// Use tsdav's native function if available
139-
result = tsdavUpdateEventFields(calendarObject, tsdavFields);
140-
} else {
141-
// Fallback to manual iCal manipulation
142-
result = manualUpdateEventFields(calendarObject, tsdavFields);
143-
}
96+
// Step 2: Update fields using tsdav-utils (field-agnostic)
97+
// Accepts any RFC 5545 property name (UPPERCASE)
98+
const updatedData = updateFields(calendarObject, validated.fields || {});
14499

145-
// Step 4: Send the updated event back to server
100+
// Step 3: Send the updated event back to server
146101
const updateResponse = await client.updateCalendarObject({
147102
calendarObject: {
148103
url: validated.event_url,
149-
data: result.data,
104+
data: updatedData,
150105
etag: validated.event_etag
151106
}
152107
});
153108

154109
return formatSuccess('Event updated successfully', {
155110
etag: updateResponse.etag,
156-
updated_fields: Object.keys(tsdavFields),
157-
modified: result.modified,
158-
warnings: result.warnings
111+
updated_fields: Object.keys(validated.fields || {}),
112+
message: `Updated ${Object.keys(validated.fields || {}).length} field(s): ${Object.keys(validated.fields || {}).join(', ')}`
159113
});
160114

161115
} catch (error) {

0 commit comments

Comments
 (0)