diff --git a/CLAUDE.md b/CLAUDE.md index dcd2bd7..16b01cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,7 @@ See `docs/dev/mcp-server-architecture.md` for full architecture details. | `MCP_AUTH_SECRET` | If auth | `openssl rand -hex 32` | | `MCP_AUTH_ISSUER` | If auth | public HTTPS URL of this server | | `MCP_AUTH_STATE_DIR` | No | Directory for persisted OAuth state (default `/app/state`) | +| `MCP_LOCALE` | No | BCP 47 locale tag for `localTime` in `get_local_time` (e.g. `sv-SE`); defaults to system locale | | `LOG_LEVEL` | No | `trace`/`debug`/`info`/`warn`/`error`/`fatal` | ## Tests diff --git a/mcp-server/src/auth/store.ts b/mcp-server/src/auth/store.ts index 1efe3d7..befbb40 100644 --- a/mcp-server/src/auth/store.ts +++ b/mcp-server/src/auth/store.ts @@ -45,6 +45,11 @@ function loadJson(filePath: string): T | null { } catch (err: unknown) { if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { logger.warn({ file: filePath, err }, '[state] Failed to load state file — starting fresh'); + try { + unlinkSync(filePath); + } catch { + /* ignore */ + } } return null; } diff --git a/mcp-server/src/tools/apps/calendar.ts b/mcp-server/src/tools/apps/calendar.ts index 6a481a1..565bb2b 100644 --- a/mcp-server/src/tools/apps/calendar.ts +++ b/mcp-server/src/tools/apps/calendar.ts @@ -118,6 +118,11 @@ function icalNow(): string { function toICalDateTime(dateStr: string): string { // Already in iCal format if (/^\d{8}(T\d{6}Z?)?$/.test(dateStr)) { + if (dateStr.length === 8) { + // Parse as local-midnight date, then convert to UTC for CalDAV time-range + const d = new Date(+dateStr.slice(0, 4), +dateStr.slice(4, 6) - 1, +dateStr.slice(6, 8)); + return d.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; + } return dateStr; } // ISO format -> iCal UTC diff --git a/mcp-server/src/tools/system/status.ts b/mcp-server/src/tools/system/status.ts index 7c89a90..fdd3323 100644 --- a/mcp-server/src/tools/system/status.ts +++ b/mcp-server/src/tools/system/status.ts @@ -98,7 +98,47 @@ export const setupChecksTool = { }, }; +/** + * Get the current local time and timezone of the MCP server + */ +export const getLocalTimeTool = { + name: 'get_local_time', + description: + 'Get the current local time and timezone of the MCP server. ' + + 'Call this to establish a time reference before creating or listing calendar events, ' + + 'or to resolve scheduling ambiguities with the user.', + inputSchema: z.object({}), + handler: async () => { + const now = new Date(); + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + const offsetMin = now.getTimezoneOffset(); // positive = behind UTC, negative = ahead + const sign = offsetMin <= 0 ? '+' : '-'; + const abs = Math.abs(offsetMin); + const h = String(Math.floor(abs / 60)).padStart(2, '0'); + const m = String(abs % 60).padStart(2, '0'); + const utcOffset = `${sign}${h}:${m}`; + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify( + { + localTime: now.toLocaleString(process.env.MCP_LOCALE, { timeZone: tz }), + utcTime: now.toISOString(), + timezone: tz, // e.g. "Europe/Berlin" + utcOffset, // e.g. "+01:00" + }, + null, + 2 + ), + }, + ], + }; + }, +}; + /** * Export all System Status tools */ -export const statusTools = [systemStatusTool, setupChecksTool]; +export const statusTools = [systemStatusTool, setupChecksTool, getLocalTimeTool];