Skip to main content
Glama
Keep MCP
by feuerdev

keep-mcp

MCP server for Google Keep

See CONTRIBUTING.md for contribution guidelines and required visual evidence.

keep-mcp

How to use

  1. Add the MCP server to your MCP servers:

  "mcpServers": {
    "keep-mcp-pipx": {
      "command": "pipx",
      "args": [
        "run",
        "keep-mcp"
      ],
      "env": {
        "GOOGLE_EMAIL": "Your Google Email",
        "GOOGLE_MASTER_TOKEN": "Your Google Master Token - see README.md"
      }
    }
  }

Or with uvx:

  "mcpServers": {
    "keep-mcp": {
      "command": "uvx",
      "args": [
        "keep-mcp"
      ],
      "env": {
        "GOOGLE_EMAIL": "Your Google Email",
        "GOOGLE_MASTER_TOKEN": "Your Google Master Token - see README.md"
      }
    }
  }
  1. Add your credentials:

  • GOOGLE_EMAIL: Your Google account email address

  • GOOGLE_MASTER_TOKEN: Your Google account master token

Obtain a Google master token

keep-mcp uses gkeepapi, which connects to Google Keep through an unofficial private API. A Google master token has full access to your account. Treat it like a password and never commit or share it.

You need Docker and a desktop browser.

  1. Open https://accounts.google.com/EmbeddedSetup and sign in with the Google account you use for Keep.

  2. Click I agree. The page may keep loading forever. Leave it open and continue.

  3. Copy the oauth_token cookie:

    • Chrome or Edge: Press F12, open the Application tab, then Cookies → https://accounts.google.com.

    • Firefox: Press F12, open the Storage tab, then Cookies → https://accounts.google.com.

    Find the row named oauth_token and copy its value. It starts with oauth2_4/.

  4. Right away, run this in a terminal and paste your email and the cookie value when asked:

    docker run --rm -it ghcr.io/astral-sh/uv:python3.13-bookworm-slim uv run --no-project --with gpsoauth python -c "import gpsoauth; r = gpsoauth.exchange_token(input('Email: '), input('oauth_token: '), '0123456789abcdef'); print(r.get('Token', r))"

    Without Docker, install uv and run the same command without the docker run --rm -it ghcr.io/astral-sh/uv:python3.13-bookworm-slim part.

  5. The command prints your master token. It starts with aas_et/. Use it as GOOGLE_MASTER_TOKEN.

If it prints an error such as BadAuthentication instead, the cookie has expired. Repeat steps 1 to 4 to get a fresh one.

More details

Older instructions may ask for your Google password or an app password and call perform_master_login(). That flow is unreliable and can return BadAuthentication. Use the browser-assisted flow above instead.

Related MCP server: Google Keep MCP

Features

Query and read tools

  • find: Search notes (case-insensitive by default) with optional filters for labels, colors, pinned, archived, trashed, creation/update date ranges (ISO 8601, UTC), and a result limit (default 50)

  • get_note: Get a single note by ID

Creation and update tools

  • create_note: Create a new note with title and text (automatically adds keep-mcp label)

  • create_list: Create a checklist note

  • update_note: Update a note's title and text

  • add_list_item: Add an item to a checklist note

  • update_list_item: Update checklist item text and checked state

  • delete_list_item: Delete a checklist item

Note state tools

  • set_note_color: Set a note color (valid values: DEFAULT, RED, ORANGE, YELLOW, GREEN, TEAL, BLUE, CERULEAN, PURPLE, PINK, BROWN, GRAY)

  • pin_note: Pin or unpin a note

  • archive_note: Archive or unarchive a note

  • trash_note: Move a note to trash

  • restore_note: Restore a trashed/deleted note

  • delete_note: Mark a note for deletion

Labels, collaborators, and media tools

  • list_labels: List labels

  • create_label: Create a label

  • delete_label: Delete a label

  • add_label_to_note: Add a label to a note

  • remove_label_from_note: Remove a label from a note

  • list_note_collaborators: List collaborator emails for a note

  • add_note_collaborator: Add a collaborator email to a note

  • remove_note_collaborator: Remove a collaborator email from a note

  • list_note_media: List media blobs for a note (with media links)

  • download_media: Download a note's media (images, drawings, audio) to a local directory through the authenticated session (the raw media links answer 403 to plain HTTP clients). Not available over Streamable HTTP, where the files would land on the server

By default, all destructive and modification operations are restricted to notes that have were created by the MCP server (i.e. have the keep-mcp label). Set UNSAFE_MODE to true to bypass this restriction.

"env": {
  ...
  "UNSAFE_MODE": "true"
}

Local development (uv + make)

If you prefer a JS-style workflow (npm i, npm start), use the included Makefile:

make install   # like npm i
make start     # like npm start
make test
make lint

Run the real-account smoke test with credentials:

GOOGLE_EMAIL="you@example.com" \
GOOGLE_MASTER_TOKEN="..." \
make smoke

Equivalent direct uv commands (without make):

UV_CACHE_DIR=/tmp/uv-cache uv venv --python 3.11 .venv
UV_CACHE_DIR=/tmp/uv-cache uv pip install --python .venv/bin/python -e .
UV_CACHE_DIR=/tmp/uv-cache uv run --no-sync --python .venv/bin/python -m server

Testing

Unit tests (default)

The project includes a lightweight unit test suite under tests/.

It validates:

  • note serialization shape for note and list objects (including labels, collaborators, media, and list items)

  • modification safety behavior (keep-mcp label requirement and UNSAFE_MODE=true override)

  • MCP tool behavior in src/server/cli.py using mocked Keep client objects (tool happy paths and key error paths)

Run locally:

make test

Smoke test against a real Keep account

For additional confidence, run a basic lifecycle smoke test against a dedicated test account:

GOOGLE_EMAIL="you@example.com" \
GOOGLE_MASTER_TOKEN="..." \
make smoke

What it does:

  • create note

  • update note

  • pin/unpin

  • archive/unarchive

  • trash/restore

  • delete

This script is intended for manual verification and is not run in CI.

CI checks

GitHub Actions runs on every pull request and executes:

  • lint (ruff check .)

  • unit tests with coverage (pytest -q --cov=src/server --cov-report=term-missing --cov-fail-under=70)

  • bytecode sanity (python -m compileall src)

Publishing

Automatic publish on merge to main (GitHub Actions)

This repo includes a release workflow at .github/workflows/release.yml that runs on every push to main (including merged PRs).

It will:

  • inspect commits since the last release tag (vX.Y.Z)

  • compute the next semantic version from Conventional Commit types

  • skip publishing when there are no releasable commit types

  • run lint and unit tests

  • build dist/*

  • publish to PyPI

  • create a GitHub release/tag v<computed-version> with generated notes

Version bump rules:

  • major: commit subject with ! (example: feat!: or fix(api)!:) or commit body containing BREAKING CHANGE

  • minor: feat:

  • patch: fix:, perf:, revert:

  • no release: docs:, chore:, ci:, test:, refactor: (unless the commit is marked as breaking)

Required repository secret:

  • PYPI_API_TOKEN: a PyPI API token (recommended scope: this project only)

Manual publish

To publish manually to PyPI:

  1. Update the version in pyproject.toml

  2. Build the package:

    pipx run build
  3. Upload to PyPI:

    pipx run twine upload --repository pypi dist/*

Run locally with MCP clients

This is useful when you want a client to run this server from your local checkout instead of PyPI.

  1. Create a local virtualenv and install in editable mode:

cd /ABSOLUTE/PATH/TO/keep-mcp
make install
  1. Add the server to your MCP client config.

config.toml clients (Codex, Goose, etc.)

[mcp_servers.keep_mcp]
command = "make"
args = ["-C", "/ABSOLUTE/PATH/TO/keep-mcp", "start"]

[mcp_servers.keep_mcp.env]
GOOGLE_EMAIL = "you@example.com"
GOOGLE_MASTER_TOKEN = "your-master-token"
UNSAFE_MODE = "false"

JSON mcpServers clients (Claude Desktop, Cursor, Cline, etc.)

{
  "mcpServers": {
    "keep-mcp-local": {
      "command": "make",
      "args": ["-C", "/ABSOLUTE/PATH/TO/keep-mcp", "start"],
      "env": {
        "GOOGLE_EMAIL": "you@example.com",
        "GOOGLE_MASTER_TOKEN": "your-master-token",
        "UNSAFE_MODE": "false"
      }
    }
  }
}

Alternative (without make):

[mcp_servers.keep_mcp]
command = "uv"
args = [
  "--directory", "/ABSOLUTE/PATH/TO/keep-mcp",
  "run", "--no-sync", "--python", ".venv/bin/python",
  "-m", "server"
]

Notes:

  • Run make install once before starting from an MCP client.

  • Only the repo root path is required (no absolute /.venv/bin/python path).

  • Ensure make and uv are in your PATH.

  • Restart your MCP client after updating config files.

  • UNSAFE_MODE is optional; keep it "false" unless you explicitly want to modify non-keep-mcp notes.

Running over Streamable HTTP

keep-mcp supports the MCP Streamable HTTP transport (/mcp endpoint) in addition to the default stdio transport:

# Start Streamable HTTP server on localhost (default: 127.0.0.1:8000)
keep-mcp --transport streamable-http

# Customize host or port
keep-mcp --transport streamable-http --host 127.0.0.1 --port 8000
NOTE

keep-mcp binds to 127.0.0.1 by default with DNS rebinding protection enabled. To expose the server to remote or web clients (such as Gemini web), place an authenticating proxy (such as mcp-auth-proxy) in front of the server.

Running in Docker

A Dockerfile is provided for running keep-mcp as a containerized Streamable HTTP server listening on port 8000.

  1. Build the container image:

    docker build -t keep-mcp .
  2. Run the container:

    docker run -d \
      -e GOOGLE_EMAIL="you@example.com" \
      -e GOOGLE_MASTER_TOKEN="aas_et/..." \
      -p 127.0.0.1:8000:8000 \
      keep-mcp
WARNING

The container has no built-in authentication. Publish it only to localhost (-p 127.0.0.1:8000:8000) or place it behind an authenticating reverse proxy if exposing it over a network.

Troubleshooting

Available Tools

23 tools
add_label_to_noteC

Add a label to a note.

Inputs: note_id (required), label_id (required). Returns: result

add_list_itemC

Add an item to a checklist note.

Inputs: note_id (required), text (required), checked. Returns: result

add_note_collaboratorC

Add a collaborator email to a note.

Inputs: note_id (required), email (required). Returns: result

archive_noteC

Archive or unarchive a note.

Inputs: note_id (required), archived. Returns: result

create_labelC

Create a label.

Inputs: name (required). Returns: result

create_listB
Create a new checklist note.

items should be objects like: {"text": "task", "checked": false}

Inputs: title, items. Returns: result

create_noteC

Create a new note with title and text.

Inputs: title, text. Returns: result

delete_labelB

Delete a label by ID.

Inputs: label_id (required). Returns: result

delete_list_itemC

Delete a checklist item.

Inputs: note_id (required), item_id (required). Returns: result

delete_noteC

Delete a note (mark for deletion).

Inputs: note_id (required). Returns: result

findB

Find notes using text and optional filters. labels should be label IDs. colors should be ColorValue strings (e.g. DEFAULT, RED, CERULEAN).

Inputs: query, labels, colors, pinned, +2 more. Returns: result

get_noteB

Get a note by ID.

Inputs: note_id (required). Returns: result

list_labelsA

List all labels.

Inputs: no named parameters. Returns: result

list_note_collaboratorsB

List collaborator emails for a note.

Inputs: note_id (required). Returns: result

list_note_mediaB

List note media blobs and direct media links when available.

Inputs: note_id (required). Returns: result

pin_noteC

Pin or unpin a note.

Inputs: note_id (required), pinned. Returns: result

remove_label_from_noteA

Remove a label from a note.

Inputs: note_id (required), label_id (required). Returns: result

remove_note_collaboratorC

Remove a collaborator email from a note.

Inputs: note_id (required), email (required). Returns: result

restore_noteB

Restore a trashed/deleted note.

Inputs: note_id (required). Returns: result

set_note_colorA

Set a note color. Valid values: DEFAULT (white), RED, ORANGE, YELLOW, GREEN, TEAL, BLUE, CERULEAN (dark blue), PURPLE, PINK, BROWN, GRAY.

Inputs: note_id (required), color (required). Returns: result

trash_noteC

Move a note to trash.

Inputs: note_id (required). Returns: result

update_list_itemB

Update checklist item text and/or checked state.

Inputs: note_id (required), item_id (required), text, checked. Returns: result

update_noteC

Update a note's properties.

Inputs: note_id (required), title, text. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3/5.0

    Scored across 23 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose, covering notes, labels, checklist items, collaboration, and media. There is minimal overlap; for example, archive, trash, delete, and restore are all separate actions. An agent can reliably select the correct tool.

    Naming Consistency4/5

    Most tools follow a consistent verb_noun pattern with snake_case (e.g., add_label_to_note, create_note). Minor inconsistency: 'find' is a bare verb instead of 'find_notes', and 'create_list' creates a checklist note rather than using 'create_checklist'. Overall, the pattern is predictable.

    Tool Count4/5

    23 tools is slightly above the typical 3-15 range but reasonable for a full-featured note-taking server. Each tool captures a specific operation, and the count is not overwhelming for the domain.

    Completeness3/5

    CRUD operations are covered for notes, labels, and checklist items. However, there is no tool to list all notes (only find with text search), and no explicit tool to unarchive or unpin (toggle behavior), which could cause agent failures if not inferred. Notable gaps exist.

    Maintenance

    ActivityMaintained
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers