elizabethsiegle/remote-mcp-server-authless-chat-with-cal
criticalRemote MCP server to read and write to Google Calendar with Google Calendar API, Workers AI, and Cloudflare MCP
This MCP server provides remote access to Google Calendar via Cloudflare Workers, allowing users to query, create, update, and delete calendar events ...
9export interface Env {
10 AI: Ai;
11 GOOGLE_CLIENT_EMAIL: string; // service account email
12 GOOGLE_PRIVATE_KEY: string; // https://console.cloud.google.com/iam-admin/serviceaccounts -> service account -> keys -> add key -> create new key -> JSON -> get private key from JSON
13 GOOGLE_CALENDAR_ID: string; //email of calendar to query ie lizzie.siegle@gmail.com
14}// Network-exposed MCP server; any client can trigger tools that use the private key. If the key is leaked, an attacker can directly access Google Calendar API.
The Google Service Account private key is stored in an environment variable and used directly in the code. While environment variables are a common practice, the key is also processed and potentially logged (e.g., console.log statements). If the environment variable is exposed or logs are leaked, the private key could be compromised, allowing full access to the Google Calendar.
ImpactAn attacker with access to the environment variable or logs could use the private key to authenticate as the service account and gain full control over the associated Google Calendar (read, create, update, delete events).
FixUse a secrets manager (e.g., Cloudflare Workers Secrets) to store the private key securely. Avoid logging the key or any processed version of it. Ensure environment variables are not exposed in error messages or logs.
100const auth = new GoogleAuth({
101 credentials: {
102 client_email: env.GOOGLE_CLIENT_EMAIL,
103 private_key: privateKey
104 },
105 scopes: ['https://www.googleapis.com/auth/calendar'],
106});// Network-exposed MCP server; the scope is used for all tools. Even without credential leakage, the LLM could be tricked into performing actions beyond the intended purpose if the scope allows it.
The Google Service Account is granted the 'https://www.googleapis.com/auth/calendar' scope, which provides full read/write access to the calendar. The intended purpose is to query, create, update, and delete events, but this scope also allows modifying calendar settings, managing ACLs, and accessing other calendars. This is broader than necessary.
ImpactIf the service account credentials are compromised, an attacker has full control over the calendar, including potentially granting access to other users or modifying calendar properties.
FixUse a more restrictive scope such as 'https://www.googleapis.com/auth/calendar.events' if only event management is needed. Alternatively, create a service account with limited permissions via Google Cloud IAM.
57const dateRangePrompt = `Given the query "${query}", todays date is ${new Date().toISOString()}, and it is a ${this.toDayString(new Date())} determine the start and end dates to search for calendar events.
58Return ONLY a JSON object with two ISO date strings: startDate and endDate.
59Example: {"startDate": "2024-03-15T00:00:00Z", "endDate": "2024-03-17T23:59:59Z"}
60Today's date is ${new Date().toISOString()} and with dates at the start and end of a day pacific
61DO NOT INCLUDE ANY OTHER TEXT BESIDES VALID JSON`;// Network-exposed MCP server; any client can send arbitrary queries. Prompt injection could lead to LLM output manipulation.
The user-provided 'query' parameter is directly interpolated into the LLM prompt without any sanitization or validation. An attacker could craft a query that manipulates the LLM to ignore instructions, output malicious content, or leak information. This is a classic prompt injection vulnerability.
ImpactAn attacker could potentially make the LLM return arbitrary data, bypass restrictions, or leak sensitive information from the prompt context (e.g., calendar event details). In a network-exposed scenario, this could lead to data exfiltration or unintended actions.
FixSanitize or validate user input before including it in prompts. Use input validation to restrict query content (e.g., length, allowed characters). Consider using a separate, isolated LLM call for user input processing with strict system prompts.
254const timePrompt = `Convert the time "${time}" to 24-hour format (HH:MM).
255IMPORTANT: Return ONLY a JSON object with a single field "time" in HH:MM format.
256Example: {"time": "14:30"}
257DO NOT include any explanation or thinking process.
258DO NOT include any other text besides the JSON object.`;// Network-exposed MCP server; any client can provide arbitrary time strings.
The user-provided 'time' parameter is directly interpolated into the LLM prompt without sanitization. An attacker could inject instructions to manipulate the LLM output, potentially leading to incorrect time parsing or other unintended behavior.
ImpactAn attacker could cause the LLM to return a crafted JSON that bypasses validation, leading to creation of events at arbitrary times or with malicious content. This could also be used for prompt injection to leak information.
FixValidate the 'time' input format before passing it to the LLM. Use a regex to ensure it matches expected patterns (e.g., HH:MM). Consider using a deterministic time parser instead of an LLM for this task.
244name: z.string().describe("Name/title of the event"),
245date: z.string().describe("Date of the event in YYYY-MM-DD format"),
246time: z.string().describe("Time of the event in HH:MM format (24-hour)"),
247location: z.string().optional().describe("Location of the event (optional)")// Network-exposed MCP server; any client can send arbitrary input to these tools.
The Zod schema only specifies that inputs are strings, but does not enforce format constraints (e.g., regex for date format, length limits). An attacker could provide extremely long strings, special characters, or malformed data that could cause issues in downstream processing or logging.
ImpactAn attacker could inject special characters that break JSON parsing, cause denial of service via long strings, or potentially exploit vulnerabilities in the Google Calendar API or logging systems.
FixAdd format validation using Zod's .regex() or .min()/.max() constraints. For example, enforce YYYY-MM-DD format for dates, HH:MM format for times, and set maximum lengths for name and location.
425const match = items.find(e =>
426 e.summary && e.summary.toLowerCase().includes(query.toLowerCase())
427);// Network-exposed MCP server; an attacker could craft queries to delete or modify events they should not have access to.
The remove and update tools find the first event whose summary contains the query string (case-insensitive partial match). This could match unintended events if the query is too broad or ambiguous. There is no confirmation step before deletion or modification.
ImpactAn attacker could delete or modify events that were not intended, especially if the query matches multiple events. This could lead to data loss or disruption.
FixRequire exact match or provide a confirmation step. Alternatively, return a list of matching events and ask the user to specify which one to act on.