Build a Google Calendar MCP Server
Connect Claude Desktop to Google Calendar using a custom Python MCP server.
Introduction
30 Second Summary
Managing your calendar takes more clicks than it should. Imagine just telling your AI assistant to schedule a meeting and having it actually appear on your real calendar.
In this project, you will build a custom MCP server in Python that connects Claude Desktop to your Google Calendar. Once it's running, you can chat naturally with Claude to create, list, and delete calendar events using plain language.
What You'll Build
A Python MCP server that gives Claude Desktop read and write access to your Google Calendar through three custom tools.
By the end of this project, you'll have:
- A working MCP server built with FastMCP that exposes three Google Calendar tools (list events, create events, delete events).
- Google Cloud OAuth 2.0 credentials configured to securely access your calendar data.
- Claude Desktop connected to your server so you can manage your calendar through natural language.
- Secret Mission: Add a "find free slots" tool that checks your calendar and suggests available meeting times.
Are there any prerequisites?
You'll need a Claude Pro subscription with Claude Desktop installed, Python proficiency, and a Google account. You should also have VS Code and Python already installed on your machine.
Set Up Your Development Environment
Before you can build an MCP server, you need the right tools in place.
In this step, you will install uv (a fast Python package manager) and set up your project with all the libraries needed to talk to Google Calendar and run an MCP server.
In this step, get ready to:
- Install uv and verify it works.
- Create a project directory with dependencies installed.
- Verify the MCP SDK imports correctly.
Install uv
uv is a modern Python package manager that handles virtual environments and dependency resolution in one tool. The official MCP documentation uses uv throughout, so you will use it too.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal (macOS) or PowerShell (Windows), and pressing Enter.
- Install uv by running the command for your operating system:
macOS
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
What does this command do?
This downloads and runs the official uv installer script from Astral (the company behind uv). It places the uv binary on your PATH so you can use it from any terminal window.
- Close and reopen your terminal so the PATH changes take effect.
- Verify uv installed correctly by running:
uv --version
✔️ I see a version number
uv is installed and ready to go.
ⓧ Command not found
Your terminal cannot find the uv binary. Try these fixes:
- Make sure you closed and reopened your terminal after running the install command.
- On macOS, run source ~/.zshrc to reload your shell profile.
- On Windows, open a new PowerShell window (not the same one you used to install).
- If it still does not work, try running the install command again.
Create the project directory and install dependencies
Your MCP server needs a dedicated project folder with a virtual environment and specific Python libraries. uv handles all of this with a few commands.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create and initialize your project folder by running these commands:
uv init calendar-mcp
cd calendar-mcp
What does this do?
uv init calendar-mcp creates a new folder called calendar-mcp with a pyproject.toml file (your project's configuration) and a virtual environment.
cd calendar-mcp moves your terminal into that folder so all future commands run inside your project.
- Install the MCP SDK and Google Calendar client libraries by running:
uv add "mcp[cli]" google-api-python-client google-auth-httplib2 google-auth-oauthlib
What are these packages?
mcp[cli] is the official Model Context Protocol SDK for Python. It includes FastMCP, the framework you will use to build your server.
The three google- packages are Google's official libraries for authenticating with OAuth 2.0 and calling the Google Calendar API.
- Confirm the dependencies were added by running:
uv run python -c "import mcp; print('Packages installed')"
You should see Packages installed printed in your terminal with no errors.
Seeing an installation error?
Make sure you are inside the calendar-mcp folder before running uv add. Run pwd (macOS) or cd (Windows) to check your current directory.
If you see a Python version error, the MCP SDK requires Python 3.10 or higher. Run python3 --version to check yours.
help me troubleshoot my uv add installation error for the MCP calendar project
Verify the MCP SDK imports correctly
The final check confirms that the FastMCP class (your server framework) is importable and ready to use.
- Run this import test to confirm everything is wired up:
uv run python -c "from mcp.server.fastmcp import FastMCP; print('MCP SDK ready')"
You should see MCP SDK ready printed with no errors.
Seeing a ModuleNotFoundError?
If you see an import error, the mcp[cli] package may not have installed correctly. Run uv add "mcp[cli]" again and check for error messages in the output.
help me fix my MCP SDK import error in my calendar-mcp project
- Open VS Code in the project folder by running:
code .
VS Code opens with the calendar-mcp folder loaded in the Explorer sidebar.
What is in your project so far?
Your calendar-mcp folder now contains a pyproject.toml file listing your four dependencies, a .venv folder with your virtual environment, and a uv.lock file that pins exact package versions.
Your development environment is locked and loaded. Next up, you will configure Google Calendar API credentials so your server can read and write real calendar events.
Configure Google Calendar API Credentials
Your development environment is ready with all the Python packages installed. Now you need to give your MCP server permission to access Google Calendar on your behalf.
Google requires OAuth 2.0 credentials for any app that reads or writes a user's calendar data. In this step, you'll create a Google Cloud project, enable the Calendar API, and download the credentials your server needs to authenticate.
In this step, get ready to:
- Create a Google Cloud project and enable the Google Calendar API.
- Create OAuth 2.0 credentials and download them to your project folder.
- Run a test script to confirm authentication works.
Create a Google Cloud project and enable the Calendar API
Google APIs live inside Google Cloud projects. You need a project to hold your API credentials and track usage.
- Open your browser and go to the Google Cloud Console at console.cloud.google.com.
- Sign in with the Google account whose calendar you want Claude to manage.
- Click the project dropdown at the top of the page (it may say "Select a project" or show an existing project name).
- Click New Project.
- Enter Calendar MCP as the project name.
- Click Create.
- Wait a few seconds for the project to be created. A notification appears when it is ready.
- Click Select Project in the notification (or select it from the project dropdown) to switch to your new project.
Now enable the Google Calendar API for this project.
- Open the navigation menu (hamburger icon in the top-left corner).
- Navigate to APIs & Services, then click Library.
- In the search bar, type Google Calendar API and press Enter.
- Click the Google Calendar API result.
- Click Enable.
What is the Google Calendar API?
The Google Calendar API lets applications create, read, update, and delete events on a user's Google Calendar. Your MCP server will use this API so Claude can manage your schedule.
Next, configure the OAuth consent screen. This is the screen Google shows when your app requests access to your calendar.
- Open the navigation menu again.
- Navigate to Google Auth platform, then click Branding.
- If you see a message saying "Google Auth platform not configured yet", click Get Started.
- Under App Information, enter Calendar MCP in the App name field.
- In User support email, select your email address from the dropdown.
- Click Next.
- Under Audience, select External.
- Click Next.
- Under Contact Information, enter your email address.
- Click Next.
- Under Finish, review the terms. Check the box to agree to the Google API Services User Data Policy.
- Click Continue, then click Create.
Why External and not Internal?
The "Internal" option is only available for Google Workspace organization accounts. Personal Gmail accounts must use "External". In test mode, only users you explicitly add can authorize the app.
Since your MCP server only runs locally for you, test mode is perfect. You will add your own email as a test user next.
Now add yourself as a test user so you can authorize the app.
- In the left sidebar under Google Auth platform, click Audience.
- Under the Test users section, click Add users.
- Enter your Gmail address and click Save.
Don't see Google Auth platform in the menu?
Make sure you have your new project selected in the project dropdown at the top. The Google Auth platform menu item appears after you enable an API that requires OAuth.
Help me find the OAuth consent screen settings in Google Cloud Console.
Create OAuth 2.0 Desktop App credentials
With the consent screen configured, you can now create the credentials your Python script will use to authenticate.
- In the left sidebar under Google Auth platform, click Clients.
- Click Create Client.
- For Application type, select Desktop app.
- In the Name field, enter Calendar MCP Client.
- Click Create.
A dialog appears with your new client ID. You need to download the credentials file.
- Click Download JSON (the down-arrow icon) to download the credentials file.
- Find the downloaded file in your Downloads folder. It has a long name starting with client_secret_.
- Rename the file to credentials.json.
- Move credentials.json into your calendar-mcp project folder on your Desktop.
What is in credentials.json?
This file contains your OAuth client ID and client secret. Your Python script uses these to prove its identity to Google when requesting access to your calendar. The file does not contain your password or calendar data.
- Confirm the file is in the right place by running this in your VS Code terminal:
ls credentials.json
You should see credentials.json listed in the output.
File not found?
Make sure your terminal is in the calendar-mcp directory. Run pwd to check your current location. Also confirm you renamed the downloaded file to exactly credentials.json (not credentials.json.json or the original long filename).
Help me get my credentials.json into the right folder.
Run a quick authentication test
Before building the full MCP server, verify that your credentials work by running a small test script. This script will open your browser for Google sign-in and confirm it can access your calendar.
- Create a new file called test_auth.py in your calendar-mcp folder by running:
touch test_auth.py
- Open test_auth.py in VS Code and paste the following code:
import datetime
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/calendar"]
CREDENTIALS_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_FILE = os.path.join(CREDENTIALS_DIR, "credentials.json")
TOKEN_FILE = os.path.join(CREDENTIALS_DIR, "token.json")
def main():
creds = None
if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open(TOKEN_FILE, "w") as token:
token.write(creds.to_json())
service = build("calendar", "v3", credentials=creds)
calendar = service.calendars().get(calendarId="primary").execute()
print(f"Successfully connected! Calendar ID: {calendar['id']}")
print(f"Calendar name: {calendar.get('summary', 'Primary Calendar')}")
if __name__ == "__main__":
main()
What does this code do?
The script uses your credentials.json to start an OAuth flow. It opens your browser so you can sign in and grant calendar access.
Once authorized, it saves a token.json file so you only need to sign in once. Then it fetches your primary calendar's name and ID to prove the connection works.
- Save test_auth.py.
- Run the test script:
uv run python test_auth.py
What should I see?
Your browser opens a Google sign-in page. The first time you run this, a warning appears saying the app is unverified. This is expected because your app is in test mode.
- In the browser, click Continue (or Advanced then Go to Calendar MCP (unsafe) if you see an "unverified app" warning).
- Sign in with the same Google account you added as a test user.
- Click Continue to grant calendar access.
After authorizing, check your terminal. You should see output like this:
Successfully connected! Calendar ID: yourname@gmail.com
Calendar name: yourname@gmail.com
- Confirm that token.json was created in your project folder by running:
ls token.json
You should see token.json listed. This file stores your access and refresh tokens so future runs skip the browser sign-in.
Seeing a "redirect_uri_mismatch" or connection error?
Make sure you selected Desktop app (not Web application) when creating your OAuth client. Desktop apps use a localhost redirect that run_local_server handles automatically.
If you chose the wrong type, go back to Google Auth platform > Clients in the Google Cloud Console, delete the incorrect client, and create a new one with the correct type.
Help me fix my OAuth authentication error.
✔️ Awesome, I've got everything!
Great. Double check you've saved test_auth.py and confirmed that both credentials.json and token.json exist in your calendar-mcp folder.
ⓧ I'd like to double check the full code
Here is the complete test_auth.py file:
import datetime
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/calendar"]
CREDENTIALS_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_FILE = os.path.join(CREDENTIALS_DIR, "credentials.json")
TOKEN_FILE = os.path.join(CREDENTIALS_DIR, "token.json")
def main():
creds = None
if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open(TOKEN_FILE, "w") as token:
token.write(creds.to_json())
service = build("calendar", "v3", credentials=creds)
calendar = service.calendars().get(calendarId="primary").execute()
print(f"Successfully connected! Calendar ID: {calendar['id']}")
print(f"Calendar name: {calendar.get('summary', 'Primary Calendar')}")
if __name__ == "__main__":
main()
Your credentials are working and Google trusts your app. Next up, you'll build the actual MCP server that gives Claude the power to create, list, and delete events on your calendar.
Build Your Calendar MCP Server
Your Google Cloud credentials are configured and authenticated. Now it's time to build the actual server that Claude Desktop will connect to.
An MCP server exposes "tools" that Claude can call on your behalf. Each tool is a Python function decorated with @mcp.tool(). You will create three tools: one to list events, one to create events, and one to delete events.
In this step, get ready to:
- Create the server file with authentication and the list events tool.
- Add the create event tool.
- Add the delete event tool and run the server.
Create the server file with authentication and event listing
The server file handles two responsibilities: authenticating with Google (using the same OAuth flow you already tested) and exposing calendar tools to Claude through FastMCP.
- In VS Code, create a new file called calendar_server.py in your calendar-mcp folder.
- Add the imports, constants, authentication helper, and the first tool by pasting this code:
import datetime
import os.path
import json
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from mcp.server.fastmcp import FastMCP
SCOPES = ["https://www.googleapis.com/auth/calendar"]
CREDENTIALS_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_FILE = os.path.join(CREDENTIALS_DIR, "credentials.json")
TOKEN_FILE = os.path.join(CREDENTIALS_DIR, "token.json")
mcp = FastMCP("google-calendar")
def get_calendar_service():
"""Authenticate and return a Google Calendar API service instance."""
creds = None
if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open(TOKEN_FILE, "w") as token:
token.write(creds.to_json())
return build("calendar", "v3", credentials=creds)
@mcp.tool()
def list_events(max_results: int = 10) -> str:
"""List upcoming events from the user's primary Google Calendar.
Args:
max_results: Maximum number of events to return (default 10)
"""
try:
service = get_calendar_service()
now = datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
events_result = (
service.events()
.list(
calendarId="primary",
timeMin=now,
maxResults=max_results,
singleEvents=True,
orderBy="startTime",
)
.execute()
)
events = events_result.get("items", [])
if not events:
return "No upcoming events found."
output = []
for event in events:
start = event["start"].get("dateTime", event["start"].get("date"))
summary = event.get("summary", "No title")
event_id = event["id"]
output.append(f"- {summary} | {start} | ID: {event_id}")
return "\n".join(output)
except HttpError as error:
return f"An error occurred: {error}"
What does this code do?
This file sets up the foundation of your MCP server. Each section handles a distinct responsibility:
- CREDENTIALS_DIR uses os.path.abspath(__file__) to build absolute paths to credentials.json and token.json. This ensures the server finds your credentials no matter which directory it is launched from.
- FastMCP("google-calendar") creates the MCP server instance. The name "google-calendar" is how Claude Desktop identifies this server.
- get_calendar_service() is the same authentication pattern from test_auth.py. It loads or refreshes your OAuth token and returns a ready-to-use Calendar API client.
- list_events() is your first tool. The @mcp.tool() decorator registers it with FastMCP. Claude reads the docstring to understand when and how to call it.
- Save calendar_server.py.
You won't see a change yet. The server needs a main() entry point (added at the end of this step) before it can run.
Seeing red underlines in VS Code?
If VS Code shows import errors for mcp or googleapiclient, make sure your Python interpreter is set to the virtual environment inside calendar-mcp. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type Python: Select Interpreter, and choose the one inside your project's .venv folder.
Help me fix my import errors in calendar_server.py
Add the create event tool
The second tool lets Claude create real events on your calendar. It accepts a title, start time, end time, and an optional description.
- In calendar_server.py, add the following code below the list_events function:
@mcp.tool()
def create_event(
summary: str,
start_time: str,
end_time: str,
description: str = "",
) -> str:
"""Create a new event on the user's primary Google Calendar.
Args:
summary: Title of the event
start_time: Start time in ISO 8601 format (e.g., 2026-06-24T14:00:00)
end_time: End time in ISO 8601 format (e.g., 2026-06-24T15:00:00)
description: Optional description for the event
"""
try:
service = get_calendar_service()
event = {
"summary": summary,
"description": description,
"start": {
"dateTime": start_time,
"timeZone": "America/New_York",
},
"end": {
"dateTime": end_time,
"timeZone": "America/New_York",
},
}
created_event = (
service.events().insert(calendarId="primary", body=event).execute()
)
return f"Event created: {created_event.get('summary')} | Link: {created_event.get('htmlLink')}"
except HttpError as error:
return f"An error occurred: {error}"
What does this code do?
- create_event() builds an event dictionary with the title, description, start/end times, and time zone, then calls the Google Calendar API's insert method.
- Claude reads the function signature and docstring to know what arguments it needs. When a user says "schedule a meeting," Claude figures out the correct ISO 8601 times and calls this tool.
- The function returns a confirmation message with the event link so Claude can share it with you.
Change the time zone to yours
The code uses America/New_York as the default time zone. Replace this with your own IANA time zone identifier (e.g., America/Los_Angeles, Europe/London, Asia/Tokyo). You can find your identifier by searching "IANA time zone" plus your city.
- Save calendar_server.py.
You won't see a change yet. The next chunk adds the final tool and the entry point that makes the server runnable.
Add the delete event tool and start the server
The third tool lets Claude remove events from your calendar using an event ID. You will also add the main() function that launches the server with stdio transport.
- In calendar_server.py, add the following code below the create_event function:
@mcp.tool()
def delete_event(event_id: str) -> str:
"""Delete an event from the user's primary Google Calendar.
Args:
event_id: The ID of the event to delete (get this from list_events)
"""
try:
service = get_calendar_service()
service.events().delete(calendarId="primary", eventId=event_id).execute()
return f"Event {event_id} deleted successfully."
except HttpError as error:
return f"An error occurred: {error}"
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
What does this code do?
- delete_event() takes an event ID (which Claude gets from list_events) and calls the Calendar API's delete method to remove it.
- main() calls mcp.run(transport="stdio"). This starts the server and tells it to communicate over standard input/output, which is how Claude Desktop sends and receives messages locally.
- The if __name__ == "__main__" guard ensures the server only starts when you run the file directly.
- Save calendar_server.py.
- Test that the server starts without errors by running this command in your terminal:
uv run python calendar_server.py
The server is now running and waiting for a client to connect over stdio. You will not see any output in the terminal. This is expected because stdio transport reserves standard output for protocol messages.
- Press Ctrl+C to stop the server.
Seeing an error when you run the server?
- ModuleNotFoundError means a dependency is missing. Make sure you ran the install commands from Step 1 inside the calendar-mcp directory.
- SyntaxError usually means a missing comma, colon, or parenthesis. Double check your code against the full file below.
- If you see a credentials error, confirm that credentials.json and token.json are in the same folder as calendar_server.py.
Help me debug my calendar MCP server startup error
✔️ Awesome, I've got everything!
Great. Double check you've saved calendar_server.py and that the server started without errors before you stopped it.
ⓧ I'd like to double check the full code
Here is the complete calendar_server.py file. Make sure yours matches exactly:
import datetime
import os.path
import json
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from mcp.server.fastmcp import FastMCP
SCOPES = ["https://www.googleapis.com/auth/calendar"]
CREDENTIALS_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_FILE = os.path.join(CREDENTIALS_DIR, "credentials.json")
TOKEN_FILE = os.path.join(CREDENTIALS_DIR, "token.json")
mcp = FastMCP("google-calendar")
def get_calendar_service():
"""Authenticate and return a Google Calendar API service instance."""
creds = None
if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open(TOKEN_FILE, "w") as token:
token.write(creds.to_json())
return build("calendar", "v3", credentials=creds)
@mcp.tool()
def list_events(max_results: int = 10) -> str:
"""List upcoming events from the user's primary Google Calendar.
Args:
max_results: Maximum number of events to return (default 10)
"""
try:
service = get_calendar_service()
now = datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
events_result = (
service.events()
.list(
calendarId="primary",
timeMin=now,
maxResults=max_results,
singleEvents=True,
orderBy="startTime",
)
.execute()
)
events = events_result.get("items", [])
if not events:
return "No upcoming events found."
output = []
for event in events:
start = event["start"].get("dateTime", event["start"].get("date"))
summary = event.get("summary", "No title")
event_id = event["id"]
output.append(f"- {summary} | {start} | ID: {event_id}")
return "\n".join(output)
except HttpError as error:
return f"An error occurred: {error}"
@mcp.tool()
def create_event(
summary: str,
start_time: str,
end_time: str,
description: str = "",
) -> str:
"""Create a new event on the user's primary Google Calendar.
Args:
summary: Title of the event
start_time: Start time in ISO 8601 format (e.g., 2026-06-24T14:00:00)
end_time: End time in ISO 8601 format (e.g., 2026-06-24T15:00:00)
description: Optional description for the event
"""
try:
service = get_calendar_service()
event = {
"summary": summary,
"description": description,
"start": {
"dateTime": start_time,
"timeZone": "America/New_York",
},
"end": {
"dateTime": end_time,
"timeZone": "America/New_York",
},
}
created_event = (
service.events().insert(calendarId="primary", body=event).execute()
)
return f"Event created: {created_event.get('summary')} | Link: {created_event.get('htmlLink')}"
except HttpError as error:
return f"An error occurred: {error}"
@mcp.tool()
def delete_event(event_id: str) -> str:
"""Delete an event from the user's primary Google Calendar.
Args:
event_id: The ID of the event to delete (get this from list_events)
"""
try:
service = get_calendar_service()
service.events().delete(calendarId="primary", eventId=event_id).execute()
return f"Event {event_id} deleted successfully."
except HttpError as error:
return f"An error occurred: {error}"
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
Your MCP server is built and confirmed working. Next up, you will connect it to Claude Desktop so Claude can actually call your three calendar tools.
Connect Your Server to Claude Desktop
Your MCP server is built and tested. Now you need to tell Claude Desktop how to find and launch it.
Claude Desktop uses a JSON config file to discover MCP servers. You will add your server's details to this file so that Claude can start it automatically whenever you open a new chat.
In this step, get ready to:
- Add your calendar server to the Claude Desktop configuration file.
- Restart Claude Desktop and verify the server connects.
- Troubleshoot connection issues using logs if needed.
Add your server to the Claude Desktop config
Claude Desktop looks for MCP servers in a config file called claude_desktop_config.json. You need to add an entry that tells Claude the command to run and where your project lives.
- Open Claude Desktop.
- Click the hamburger menu icon in the top left corner.
- Click Settings.
- Click the Developer tab in the left sidebar.
- Click Edit Config. This opens claude_desktop_config.json in your default text editor.
Where is this file?
The config file lives at different locations depending on your operating system:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
Before you paste the configuration, you need to find the absolute path to your calendar-mcp folder. Claude Desktop launches your server from an undefined working directory, so relative paths will not work.
- Find the absolute path to your calendar-mcp folder by running this command in your terminal:
macOS
cd ~/Desktop/calendar-mcp && pwd
Windows
cd ~\Desktop\calendar-mcp; cd
- Copy the path that prints out. You will need it in the next step.
- Replace the contents of claude_desktop_config.json with the following configuration:
macOS
{
"mcpServers": {
"google-calendar": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/calendar-mcp", "run", "calendar_server.py"]
}
}
}
Windows
{
"mcpServers": {
"google-calendar": {
"command": "uv",
"args": ["--directory", "C:/Users/YOURUSERNAME/Desktop/calendar-mcp", "run", "calendar_server.py"]
}
}
}
- Replace the path in the "--directory" value with the absolute path you copied earlier.
- Save the file.
What does this config do?
This JSON tells Claude Desktop that there is an MCP server named google-calendar. When Claude needs to use it, it launches uv with the --directory flag pointing to your project folder, then runs calendar_server.py.
The --directory flag ensures uv uses your project's virtual environment and dependencies regardless of where Claude Desktop starts the process.
Windows path formatting
On Windows, use forward slashes (/) or double backslashes (\\) in the JSON path. Single backslashes will cause a JSON parsing error.
Full path to uv
If Claude Desktop cannot find uv, you may need to replace "uv" in the command field with the full path to the uv executable. Find it by running which uv (macOS) or where uv (Windows) in your terminal.
Restart Claude Desktop and verify the connection
Claude Desktop only reads the config file at launch. You must fully quit and restart it for changes to take effect.
- Completely quit Claude Desktop. On macOS, press Cmd+Q. On Windows, right-click the system tray icon and choose Quit.
- Reopen Claude Desktop.
- In a new chat, click the plus icon (Add files, connectors, and more) in the chat input area.
- Hover over Connectors in the menu that appears.
- Confirm that google-calendar appears in the list with three tools available.
What should I see?
The connectors menu should show google-calendar as a connected server. When you click on it, you should see the three tools you defined: list_events, create_event, and delete_event.
Troubleshoot if the server does not appear
If google-calendar does not show up in the connectors menu, Claude Desktop logs will tell you what went wrong.
- Open the MCP logs by running this command in your terminal:
macOS
tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
Windows
type "$env:AppData\Claude\logs\mcp*.log"
What do these logs tell you?
The file mcp.log contains general connection information. Files named mcp-server-google-calendar.log contain error output from your server process specifically.
Server not appearing in connectors?
- Check that your claude_desktop_config.json is valid JSON. A missing comma or bracket will silently fail.
- Verify the path in --directory is the absolute path to your calendar-mcp folder. Relative paths will not work.
- Make sure token.json exists in your calendar-mcp folder. If it is missing, re-run the auth test from the previous step.
- On Windows, ensure you used forward slashes or double backslashes in the path.
Still stuck? Help me debug why my MCP server isn't connecting to Claude Desktop
Your MCP server is now connected to Claude Desktop. Next up, you will put it to the test by asking Claude to manage your calendar with natural language.
Test Your Calendar Assistant
Your MCP server is connected to Claude Desktop and ready to handle calendar operations. Now it's time to put it to work with real requests.
In this step, you'll talk to Claude in natural language and watch it translate your words into actual Google Calendar API calls through your server. You'll test all three tools: listing events, creating a new one, and deleting it.
In this step, get ready to:
- Ask Claude to list your upcoming calendar events.
- Create a new event through natural language.
- Delete the test event and verify it disappears from Google Calendar.
List your upcoming events
Every MCP tool call in Claude Desktop requires your explicit approval before it executes. When Claude wants to use a tool, it shows you the tool name and parameters, then waits for you to allow it.
- In Claude Desktop, start a new conversation.
- Type a natural language request like: "What's on my calendar for the next few days?"
- Claude will indicate it wants to use the list_events tool from your google-calendar server.
- Review the tool call details, then approve it.
Claude should respond with a formatted list of your upcoming events, including their titles, times, and event IDs.
What should I see?
Claude's response includes your real calendar data pulled from Google Calendar. Each event shows a summary, start time, and an ID string. If you have no upcoming events, Claude will say "No upcoming events found."
Claude not showing the tool call?
Make sure you see "google-calendar" in the connectors menu (the plus icon in the chat input area). If it doesn't appear, completely quit Claude Desktop and reopen it.
If the tool call fails with an authentication error, your token.json may have expired. Delete token.json from your project folder and try again. Claude Desktop will trigger a fresh browser sign-in.
help me troubleshoot why Claude can't connect to my google-calendar MCP server
Create a calendar event
Now you'll test the most powerful tool: creating real events on your Google Calendar through conversation.
- In the same Claude Desktop conversation, type something like: "Schedule a team standup tomorrow at 9am for 30 minutes."
- Claude will show it wants to use the create_event tool with a summary, start time, and end time. Review the parameters to confirm Claude interpreted your request correctly.
- Approve the tool call.
Claude should confirm the event was created and include a link to it.
- Open Google Calendar in your browser at calendar.google.com to verify the event appears on the correct day and time.
What does Claude do with my request?
Claude parses your natural language into structured parameters. "Tomorrow at 9am for 30 minutes" becomes an ISO 8601 start time and an end time 30 minutes later. Your MCP server passes these directly to the Google Calendar API.
The event uses the America/New_York time zone by default. If you're in a different time zone, you can update the timeZone value in calendar_server.py to match your local IANA time zone identifier (e.g., Europe/London, Asia/Tokyo).
Event not showing up in Google Calendar?
Make sure you're checking the correct Google account. The event is created on the primary calendar of the account you authorized during the OAuth flow in Step 2.
If Claude reports an error, check that it formatted the start and end times correctly. You can ask Claude to retry with more specific times like "Create an event called Team Standup on June 25 2026 from 9:00 AM to 9:30 AM."
help me figure out why my create_event MCP tool isn't creating events on Google Calendar
Delete the test event
The final tool to test is deletion. This confirms your server can handle the full lifecycle of a calendar event.
- Back in Claude Desktop, ask Claude to delete the event you just created. For example: "Delete the team standup event you just created."
- Claude may first call list_events to find the event ID, then call delete_event with that ID. Approve each tool call when prompted.
- Refresh calendar.google.com and confirm the event is gone.
Why does Claude call two tools?
The delete_event tool requires a specific event ID. Claude doesn't store IDs from earlier in the conversation (each tool call is independent), so it first lists your events to find the matching ID, then passes it to the delete tool. This is MCP's tool chaining in action.
Claude can't find the event to delete?
If Claude says it can't find the event, try asking it to list your events first, then reference the specific event by name. You can also copy the event ID from the earlier list_events response and tell Claude directly: "Delete the event with ID [paste the ID here]."
help me troubleshoot why Claude can't delete my calendar event through the MCP server
Secret mission
Add a Find Free Slots Tool
Your MCP server can create and delete events, but what about finding time for new ones? In this bonus challenge, you'll add a fourth tool that analyzes your calendar and surfaces available meeting slots. This requires working with datetime ranges and gap-detection logic.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally with a free Google Cloud project, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local calendar-mcp project folder on your Desktop (contains calendar_server.py, test_auth.py, credentials.json, token.json, and pyproject.toml).
- Google Cloud project with Google Calendar API enabled and OAuth credentials.
- OAuth token granting your MCP server read/write access to your Google Calendar.
- Claude Desktop MCP server configuration (claude_desktop_config.json entry).
Keep everything running
No action needed. Choose this if you plan to keep using Claude as your calendar assistant.
- Your MCP server will continue to work whenever Claude Desktop is open.
- The token.json file keeps your Google Calendar authorization active.
- The uv package manager remains installed on your system for future Python projects.
Pause - I'll come back to this later
Remove the server from Claude Desktop so it stops launching automatically, but keep your files for later.
- Open claude_desktop_config.json (the file you edited in the Developer settings).
- Delete the google-calendar entry from the mcpServers object, then save the file.
- Quit and restart Claude Desktop.
- Your project files stay on disk. When you want to reconnect, add the server entry back and restart Claude Desktop.
Delete - I don't want to use this again
Remove all project resources and revoke access completely.
Remove the Claude Desktop server entry
- Open claude_desktop_config.json and delete the google-calendar entry from the mcpServers object.
- Save the file, then quit and restart Claude Desktop.
Delete local project files
- Delete the calendar-mcp folder by running this command:
macOS
rm -rf ~/Desktop/calendar-mcp
Windows
Remove-Item -Recurse -Force ~\Desktop\calendar-mcp
Revoke OAuth access to your Google Calendar
- Go to https://myaccount.google.com/connections.
- Find your OAuth app under Access to your Google Account.
- Select it, then click Remove access and confirm.
Why revoke access?
Deleting the local token.json file removes your server's ability to authenticate. Revoking access in your Google Account ensures no cached token can be reused from anywhere.
Delete the Google Cloud project
- Open the Google Cloud Console and go to IAM & Admin then Settings.
- Select the project you created for this tutorial.
- Click Shut down.
- Enter the project ID to confirm, then click Shut down anyway.
Uninstall uv (optional)
If you no longer need uv for other Python projects, you can remove it from your system.
- Clean the cache and remove the binaries by running these commands:
macOS
uv cache clean
rm -r "$(uv python dir)"
rm -r "$(uv tool dir)"
rm ~/.local/bin/uv ~/.local/bin/uvx
Windows
uv cache clean
Remove-Item -Recurse -Force "$(uv python dir)"
Remove-Item -Recurse -Force "$(uv tool dir)"
Remove-Item ~\.local\bin\uv.exe, ~\.local\bin\uvx.exe
Nice Work!
Nice Work!
You just built a fully functional MCP server that gives Claude Desktop the power to manage your real Google Calendar. That's a production-grade AI integration running on your local machine.
You've learned how to:
- Build a custom MCP server in Python using FastMCP, and connect it to Claude Desktop via stdio transport.
- Configure OAuth 2.0 authentication with Google's Calendar API for secure read/write access.
- Implement three calendar tools (list_events, create_event, delete_event) that Claude calls through natural language.
- Secret Mission: Added a find_free_slots tool so Claude can intelligently suggest available meeting times based on your real calendar data.
Ready to quiz yourself?