From b0dee4dafbda901403a21efa50e837cd730a0611 Mon Sep 17 00:00:00 2001 From: jubnl Date: Mon, 6 Apr 2026 00:08:17 +0200 Subject: [PATCH 1/4] feat(mcp): add MCP_MAX_SESSION_PER_USER env var and document it everywhere --- README.md | 2 ++ chart/values.yaml | 2 ++ docker-compose.yml | 1 + server/.env.example | 1 + server/src/mcp/index.ts | 3 ++- unraid-template.xml | 1 + 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e5d768a9..49d844fd 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ services: # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist # - MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60) + # - MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5) volumes: - ./data:/app/data - ./uploads:/app/uploads @@ -303,6 +304,7 @@ trek.yourdomain.com { | **Other** | | | | `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` | | `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `60` | +| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `5` | ## Optional API Keys diff --git a/chart/values.yaml b/chart/values.yaml index 52dfccf7..47a941c7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -53,6 +53,8 @@ env: # Enable demo mode (hourly data resets). # MCP_RATE_LIMIT: "60" # Max MCP API requests per user per minute. Defaults to 60. + # MCP_MAX_SESSION_PER_USER: "5" + # Max concurrent MCP sessions per user. Defaults to 5. # Secret environment variables stored in a Kubernetes Secret. diff --git a/docker-compose.yml b/docker-compose.yml index d6af6b68..39a82c60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,7 @@ services: # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist # - MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60) +# - MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5) volumes: - ./data:/app/data - ./uploads:/app/uploads diff --git a/server/.env.example b/server/.env.example index 9c70b492..5e8c677a 100644 --- a/server/.env.example +++ b/server/.env.example @@ -29,6 +29,7 @@ OIDC_SCOPE=openid email profile # Fully overrides the default. Add extra scopes DEMO_MODE=false # Demo mode - resets data hourly # MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60) +# MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5) # Initial admin account — only used on first boot when no users exist yet. # If both are set the admin account is created with these credentials. diff --git a/server/src/mcp/index.ts b/server/src/mcp/index.ts index 130aaa06..86714a05 100644 --- a/server/src/mcp/index.ts +++ b/server/src/mcp/index.ts @@ -18,7 +18,8 @@ interface McpSession { const sessions = new Map(); const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour -const MAX_SESSIONS_PER_USER = 5; +const sessionParsed = Number.parseInt(process.env.MCP_MAX_SESSION_PER_USER ?? ""); +const MAX_SESSIONS_PER_USER = Number.isFinite(sessionParsed) && sessionParsed > 0 ? sessionParsed : 5; const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute const parsed = Number.parseInt(process.env.MCP_RATE_LIMIT ?? ""); const RATE_LIMIT_MAX = Number.isFinite(parsed) && parsed > 0 ? parsed : 60; // requests per minute per user diff --git a/unraid-template.xml b/unraid-template.xml index 5d2759d2..74fe88a8 100644 --- a/unraid-template.xml +++ b/unraid-template.xml @@ -58,4 +58,5 @@ false 60 + 5 From f2ffea5ba4d95f6d0a97ee27202f54ffec25fd23 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 5 Apr 2026 22:09:41 +0000 Subject: [PATCH 2/4] chore: bump version to 2.9.8 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- server/package-lock.json | 4 ++-- server/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 18078911..364c5457 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.8", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 0462203b..6f9d5b7b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.8", "private": true, "type": "module", "scripts": { diff --git a/server/package-lock.json b/server/package-lock.json index 062d1241..0c77cebb 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-server", - "version": "2.9.7", + "version": "2.9.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-server", - "version": "2.9.7", + "version": "2.9.8", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "archiver": "^6.0.1", diff --git a/server/package.json b/server/package.json index 7fee6fce..306a4537 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "trek-server", - "version": "2.9.7", + "version": "2.9.8", "main": "src/index.ts", "scripts": { "start": "node --import tsx src/index.ts", From 66a057a070424e3c4342932aa0672959131b4a7b Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 6 Apr 2026 11:32:06 +0200 Subject: [PATCH 3/4] fix(bookings): resolve date handling and file auth bugs - Clear reservation_time fields when switching booking type to hotel (#459) - Parse date-only reservation_end_time correctly on edit (#455) - Show end date on booking cards for date-only values (#455) - Add auth token to file download links in bookings (#454) - Account for timezone offsets in flight time validation (#456) --- .../components/Planner/ReservationModal.tsx | 25 +++++++++++++++++-- .../components/Planner/ReservationsPanel.tsx | 5 ++-- server/src/services/reservationService.ts | 4 +-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index d974d6a8..cbbfba68 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -10,6 +10,7 @@ import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' import { CustomDatePicker } from '../shared/CustomDateTimePicker' import CustomTimePicker from '../shared/CustomTimePicker' +import { getAuthUrl } from '../../api/authUrl' import type { Day, Place, Reservation, TripFile, AssignmentsMap, Accommodation } from '../../types' const TYPE_OPTIONS = [ @@ -113,6 +114,9 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p if (rawEnd.includes('T')) { endDate = rawEnd.split('T')[0] endTime = rawEnd.split('T')[1]?.slice(0, 5) || '' + } else if (/^\d{4}-\d{2}-\d{2}$/.test(rawEnd)) { + endDate = rawEnd + endTime = '' } setForm({ title: reservation.title || '', @@ -166,6 +170,22 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p const startDate = form.reservation_time.split('T')[0] const startTime = form.reservation_time.split('T')[1] || '00:00' const endTime = form.reservation_end_time || '00:00' + // For flights, compare in UTC using timezone offsets + if (form.type === 'flight') { + const parseOffset = (tz: string): number | null => { + if (!tz) return null + const m = tz.trim().match(/^(?:UTC|GMT)?\s*([+-])(\d{1,2})(?::(\d{2}))?$/i) + if (!m) return null + const sign = m[1] === '+' ? 1 : -1 + return sign * (parseInt(m[2]) * 60 + parseInt(m[3] || '0')) + } + const depOffset = parseOffset(form.meta_departure_timezone) + const arrOffset = parseOffset(form.meta_arrival_timezone) + if (depOffset === null || arrOffset === null) return false + const depMinutes = new Date(`${startDate}T${startTime}`).getTime() - depOffset * 60000 + const arrMinutes = new Date(`${form.end_date}T${endTime}`).getTime() - arrOffset * 60000 + return arrMinutes <= depMinutes + } const startFull = `${startDate}T${startTime}` const endFull = `${form.end_date}T${endTime}` return endFull <= startFull @@ -204,7 +224,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p } const saveData: Record = { title: form.title, type: form.type, status: form.status, - reservation_time: form.reservation_time, reservation_end_time: combinedEndTime, + reservation_time: form.type === 'hotel' ? null : form.reservation_time, + reservation_end_time: form.type === 'hotel' ? null : combinedEndTime, location: form.location, confirmation_number: form.confirmation_number, notes: form.notes, assignment_id: form.assignment_id || null, @@ -565,7 +586,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
{f.original_name} - + { e.preventDefault(); const u = await getAuthUrl(f.url, 'download'); window.open(u, '_blank', 'noreferrer') }} style={{ color: 'var(--text-faint)', display: 'flex', flexShrink: 0, cursor: 'pointer' }}>