Personal MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Personal MCP Serversearch my inbox for unread emails from John"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Personal MCP Server
A private, single-user MCP (Model Context Protocol) server that gives AI agents access to your email, Slack notifications, and a personal knowledge base ("soul docs"). Written in TypeScript, runs over Streamable HTTP or stdio.
Capabilities at a glance:
Search and read Gmail or any IMAP mailbox
Send email via Gmail API or custom SMTP (with a prepare→confirm safety flow)
Post Slack notifications via incoming webhooks
CRUD a personal knowledge base backed by SQLite or Turso/libSQL
Configure all credentials at runtime via MCP tools — no
.envediting required
Tools
Mail — Gmail
Tool | Summary |
| Search Gmail with typed filters (from, to, subject, text, label, unread, date range). Returns summaries. |
| Fetch a full Gmail message by ID — headers, plain text body, HTML body. |
Mail — Custom IMAP / SMTP
Tool | Summary |
| Search a custom IMAP mailbox with the same typed filters. |
| Fetch a full message from the custom IMAP mailbox by UID. |
| Validate and stage an email draft. Returns a confirmation ID; does not send. |
| Send a previously staged email using its confirmation ID. The two-step flow prevents accidental sends. |
Slack
Tool | Summary |
| Post a message (plain text or Block Kit blocks) to a configured Slack incoming webhook. |
Soul Docs (personal knowledge base)
Tool | Summary |
| Search docs by full-text query (title, content, source) or filter by tag. Returns newest first. |
| Create or update a doc. Pass an |
Setup (runtime configuration)
When MCP_ENABLE_SETUP_TOOLS=true (the default), these tools let you configure every service at runtime and test connections immediately. All changes are in-memory — they override .env values but reset on restart.
Tool | Summary |
| Show which services are configured (no secrets exposed). |
| Set a Turso/libSQL URL or local file path, then test the connection. Accepts |
| Generate a Google OAuth authorization URL. Optionally override client ID, secret, and redirect URI. |
| Exchange the OAuth authorization code for a refresh token and store it in the runtime config. |
| Set IMAP host, port, credentials, and mailbox — then test the connection. |
| Set SMTP host, port, credentials, and default from address — then verify the connection. |
| Set a Slack incoming webhook URL and send a test notification. |
Set MCP_ENABLE_SETUP_TOOLS=false to disable all setup tools and use .env-only configuration.
Related MCP server: email-mcp
Configuration reference
Every setting can be provided via .env or overridden at runtime by the corresponding setup_* tool.
Runtime
Variable | Default | Notes |
|
|
|
|
| HTTP listen port |
|
| Set to |
| — | Optional. When set, requires |
|
| Comma-separated origins or |
|
| Set to |
|
|
|
Database
Variable | Default | Notes |
|
|
|
| — | Required for Turso remote databases |
| — | Sync endpoint for embedded replicas |
| — | Sync interval in ms for embedded replicas |
Gmail OAuth
Variable | Required | Notes |
| Yes | From Google Cloud Console |
| Yes | From Google Cloud Console |
| Yes | e.g. |
| — | Obtained via OAuth flow; stored at runtime by |
| — | Optional CSRF state string |
Scopes requested: gmail.readonly, gmail.send.
Custom IMAP
Variable | Default | Notes |
| — | IMAP server hostname |
|
| |
|
| TLS |
| — | Usually the full email address |
| — | App password recommended |
|
|
Custom SMTP
Variable | Default | Notes |
| — | SMTP server hostname |
|
| |
|
|
|
| — | Optional |
| — | Optional |
| — | Default sender address |
|
| Expiry for staged-but-unsent emails (24 h default, so drafts survive across sessions) |
Slack
Variable | Notes |
| Incoming webhook URL |
Getting started
Path A: .env (static config)
npm install
cp .env.example .env
# Edit .env with your credentials
npm run build
npm start # HTTP on port 3000
# or: npm run start:stdio # stdio transportPath B: setup tools (runtime config)
npm install
npm run build
npm startThen, from your MCP client, call the setup_* tools in any order:
setup_database— point to your DB (or skip;file:local.dbis the default)setup_gmail_oauth_start→ open the URL →setup_gmail_oauth_completewith the codesetup_custom_mail_imap+setup_custom_mail_smtp— configure mailsetup_slack_webhook— configure Slacksetup_status— verify everything is wired up
Endpoints
Method | Path | Description |
|
| MCP Streamable HTTP — requires |
|
| Health check |
|
| Start Gmail OAuth flow (HTTP mode only) |
|
| Gmail OAuth callback (HTTP mode only) |
Architecture
src/
├── config.ts Env parsing (Zod), defaults, setConfigValue helper
├── index.ts Entry point — loads config, creates services, starts transport
├── runtime.ts Service factory — wires up all services with shared config
├── server.ts MCP server — registers all tools, conditionally includes setup tools
├── setup-tools.ts Runtime config tools — mutate config, test connections
├── tools.ts Core tool handlers — email, Slack, soul docs
├── errors.ts Custom error classes
├── logger.ts Structured JSON logging
├── types.ts Shared TypeScript interfaces
├── transports/
│ ├── http.ts Streamable HTTP transport + OAuth callback routes
│ └── stdio.ts Stdio transport
├── services/
│ ├── database.ts Turso/libSQL client — soul_docs, send_confirmations, audit_log
│ ├── gmail-api.ts Thin Gmail REST wrapper via google-auth-library + fetch (replaces googleapis)
│ ├── gmail.ts Gmail service — search, read, send, OAuth
│ ├── custom-mail.ts IMAP (imapflow) + SMTP (nodemailer) — search, read, send
│ ├── email-sender.tsComposite — delegates to Gmail or SMTP based on provider
│ └── slack.ts Slack incoming webhook via fetch
└── utils/
├── email.ts MIME builder, base64url helpers
└── mcp.ts jsonText() response formatterKey design choices:
Shared mutable config — All services hold a reference to the same
AppConfigobject. Setup tools mutate it directly; lazy services (Gmail, IMAP, SMTP, Slack) pick up changes on the next call. OnlyDatabaseServiceneeds an explicitreconnect()since it creates the libSQL client eagerly.Audit logging — Every tool call is logged to the
audit_logtable with success/failure, args (secrets redacted), and a timestamp.Structured logging — Every tool call (with redacted args and duration), email send, and error is also emitted as a JSON log line on stderr, so stdout stays clean for the stdio transport. Set
LOG_LEVEL=debugfor verbose service-level detail (IMAP/SMTP/Gmail requests, confirmation lifecycle).Two-step email send —
email_prepare_sendstages a draft (stored in DB with a TTL),email_confirm_sendconsumes it. Prevents accidental sends and gives the agent a chance to review.OAuth works in both transports — Streamable HTTP mode has dedicated callback routes; stdio mode uses the
setup_gmail_oauth_start/setup_gmail_oauth_completetools where the user copies the code manually.
Database
Three tables are created automatically on startup:
Table | Purpose |
| Personal knowledge base — title, content, tags, source, metadata, timestamps |
| Staged email drafts with expiry and single-use tokens |
| Immutable record of every tool invocation |
Local vs remote
# Local SQLite (relative to working directory)
TURSO_DATABASE_URL=file:local.db
# Local SQLite (absolute — for pods/containers that need persistent state)
TURSO_DATABASE_URL=file:/data/my-server.db
# Remote Turso DB
TURSO_DATABASE_URL=libsql://your-db.turso.io
TURSO_AUTH_TOKEN=your-tokenPlain paths passed to setup_database are auto-prefixed with file: — so /data/db.sqlite becomes file:/data/db.sqlite.
Deployment
Docker (GHCR)
Pre-built multi-arch images (linux/amd64, linux/arm64) are published to GitHub Container Registry on every push to main.
# Pull and run
docker run -d --name personal-mcp \
--env-file .env \
-p 3000:3000 \
-v "$(pwd)/data:/app/data" \
ghcr.io/olamide226/personal-mcp-server:latest
# Or with a specific version
docker run -d --name personal-mcp \
--env-file .env \
-p 3000:3000 \
-v "$(pwd)/data:/app/data" \
ghcr.io/olamide226/personal-mcp-server:0.1.0Docker Compose (local build)
docker compose up --buildTo pull from GHCR instead of building locally, swap the build: . line in docker-compose.yml for:
image: ghcr.io/olamide226/personal-mcp-server:latestBare metal / pod
npm ci --omit=dev
npm run build
MCP_HOST=0.0.0.0 MCP_PORT=3000 TURSO_DATABASE_URL=file:/data/server.db node dist/index.jsFor production, set a strong MCP_BEARER_TOKEN and restrict MCP_ALLOWED_ORIGINS to your client origin(s).
Image tags
Tag | When |
| Every push to |
| Every push to |
| Version tag (e.g. |
| Major.minor alias (e.g. |
| Major alias (e.g. |
latest and <full-sha> are published by the docker job in ci.yml on every push to main. The semver tags (0.1.0, 0.1, 0) are published by the docker job in release.yml when a release tag is created — the leading v from the git tag is dropped on the image tags (Docker/OCI convention), so v0.1.0 → 0.1.0. Semantic releases start at v0.1.0.
Note — why the build lives in
release.yml: thev*tag is pushed using the defaultGITHUB_TOKEN, and GitHub does not trigger downstream workflows fromGITHUB_TOKENactivity (to prevent loops). Soci.yml'stags: ["v*"]trigger never fires for auto-created tags — the image is built in the samerelease.ymlrun that creates the tag instead.
Rebuild an existing tag's image: if a git tag already exists but its image wasn't published (e.g.
v0.1.0predates this workflow), run the Semantic Release workflow manually — Actions → Semantic Release → Run workflow →v0.1.0— to build and push0.1.0/0.1/0for that tag.
Semantic releases
Merging a PR to main triggers the release.yml workflow which parses conventional commits since the last tag:
Commit prefix | Version bump |
| minor |
| patch |
| major |
If any meaningful commits are found, a vX.Y.Z tag is created and the release.yml docker job builds & pushes the matching semver image tags (X.Y.Z, X.Y, X) to GHCR. (The tag push uses GITHUB_TOKEN, so ci.yml is not triggered by it — hence the build runs here.)
Health check
curl http://localhost:3000/healthz
# {"ok":true,"name":"personal-mcp-server","version":"0.1.0"}Development
npm install
npm run dev # tsx watch — auto-reload on changes
npm run build # tsc
npm test # vitest
npm run lint # eslint
npm run typecheck # tsc --noEmit (includes tests/)This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityDmaintenanceA local MCP server that provides LLM clients with read/write access to email and calendar data from Gmail, iCloud, and generic IMAP providers. It runs entirely on your machine, keeping data private while enabling email management, calendar operations, and task handling through natural language.Last updated39MIT
- AlicenseAqualityBmaintenanceLocal MCP server for multi-account IMAP/SMTP email (iCloud + Gmail via app-specific passwords). Never marks mail read. Cross-folder search, idempotent sends, TLS verified.Last updated8MIT
- Flicense-qualityCmaintenanceCustom MCP server for Gmail integration, enabling email listing, reading, searching, and sending via natural language.Last updated
- Flicense-qualityDmaintenanceCustom MCP server that provides a toolset for an Artificial Virtual Assistant (AVA) to interact with Gmail, including email management through Google OAuth authentication.Last updated1
Related MCP Connectors
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.
A basic MCP server to operate on the Postman API.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/olamide226/personal-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server