MCP Note App
A full-stack note-taking application with a React UI, Express/SQLite backend, real-time Server-Sent Events, JWT auth, and AI agent integration through the Model Context Protocol (MCP).
What You Get
- Create, edit, delete, tag, and search notes in a modern React interface.
- Live UI refreshes through SSE when notes change through REST or MCP tools.
- JWT-protected REST note routes with local API-key login.
- MCP SSE transport for AI clients and a Python Gemini-powered CLI agent.
- SQLite persistence via a repository layer.
Architecture
┌─────────────────┐ MCP/SSE ┌──────────────────┐
│ Python Agent │ ◄──────────────► │ │
│ (Gemini AI) │ │ Node.js Server │
│ │ Tools │ (MCP + REST) │
└─────────────────┘ │ │
│ SQLite DB │
┌─────────────────┐ REST/SSE │ │
│ React Frontend │ ◄──────────────► │ │
│ (Vite+Tailwind) │ └──────────────────┘
└─────────────────┘
- Backend: TypeScript, Express, MCP server, SQLite (
better-sqlite3), Zod, JWT. - Frontend: React 18, Vite, Tailwind CSS, TanStack React Query, Zustand, SSE.
- Agent: Python MCP client connected to Google Gemini (
gemini-2.5-flashby default).
Prerequisites
- Node.js 20+ recommended.
- npm.
- Python 3.10+ recommended.
- A Gemini API key for the optional Python agent.
Quick Start
1. Install dependencies
Install everything from the repository root:
npm run install:all
Or install each layer manually:
npm install --prefix server
npm install --prefix client-ui
python3 -m venv agent/.venv
agent/.venv/bin/python -m pip install -r agent/requirements.txt
Install the root orchestration dependency only if needed:
npm install
2. Configure environment
Create server/.env:
PORT=3000
NODE_ENV=development
JWT_SECRET=change-this-to-a-long-random-secret-at-least-32-chars
JWT_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d
DATABASE_PATH=./data/notes.db
CORS_ORIGIN=http://localhost:5173
AUTH_TOKEN=dev-auth-token-change-in-production
Create agent/.env if you plan to use the Gemini agent:
GEMINI_API_KEY=your-gemini-api-key
Optional stdio transport variables for the agent:
MCP_STDIO_CMD=npx
MCP_STDIO_ARGS=tsx,server/src/index.ts
Do not commit
.envfiles, API keys, JWT secrets, or local SQLite database files.
3. Start the app
Start backend and frontend together:
npm run dev
Or run them separately:
npm run dev:server
npm run dev:client
Then open the frontend at http://localhost:5173.
4. Start the AI agent
Make sure the backend is running first, then run:
npm run dev:agent
One-shot example:
agent/.venv/bin/python agent/agent.py --one-shot "Create a note titled Demo with content Hello from Gemini"
Scripts
Run these from the repository root unless noted otherwise.
| Command | Description |
|---|---|
npm run install:all |
Install server, client, and Python agent dependencies. |
npm run install:agent |
Create agent/.venv and install Python dependencies. |
npm run dev |
Start backend and frontend concurrently. |
npm run dev:server |
Start only the Express/MCP server. |
npm run dev:client |
Start only the Vite frontend. |
npm run dev:agent |
Start the Python Gemini MCP agent. |
npm run build |
Build server and client. |
npm run build --prefix server |
Type-check/build backend. |
npm run build --prefix client-ui |
Type-check/build frontend. |
npm run test --prefix client-ui |
Run frontend unit tests with Vitest. |
npm run test:e2e --prefix client-ui |
Run Playwright end-to-end tests. |
Project Structure
mcp-note-app/
├── server/ # Express REST API, MCP server, SQLite persistence
│ ├── src/
│ │ ├── auth/ # JWT signing and verification
│ │ ├── config/ # Environment validation
│ │ ├── db/ # SQLite connection and schema
│ │ ├── events/ # Event bus and SSE handlers
│ │ ├── mcp/ # MCP tool server and transports
│ │ ├── middleware/ # Auth, CORS, error handling
│ │ ├── repositories/ # NoteRepository + SQLite implementation
│ │ ├── routes/ # Notes REST routes and live updates
│ │ ├── types.ts # Zod schemas and shared backend types
│ │ └── index.ts # Express entry point
│ └── package.json
├── client-ui/ # React frontend
│ ├── e2e/ # Playwright tests
│ ├── src/
│ │ ├── components/ # Presentational and UI components
│ │ ├── hooks/ # React Query and SSE hooks
│ │ ├── services/ # API client and query client
│ │ ├── stores/ # Zustand auth/UI state
│ │ ├── test/ # Vitest setup and component tests
│ │ ├── types/ # Frontend note types
│ │ ├── App.tsx
│ │ └── main.tsx
│ └── package.json
├── agent/ # Python MCP + Gemini CLI agent
│ ├── agent.py
│ ├── mcp_gemini_adapter.py
│ └── requirements.txt
├── AGENTS.md # Coding-agent project guidance
├── README.md
└── package.json # Root orchestration scripts
Environment Variables
Server
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Express server port. |
NODE_ENV |
development |
Runtime environment. |
JWT_SECRET |
required | Secret for JWT signing. Must be at least 32 characters. |
JWT_EXPIRY |
15m |
Access token lifetime. |
JWT_REFRESH_EXPIRY |
7d |
Refresh token lifetime. |
DATABASE_PATH |
./data/notes.db |
SQLite database path, relative to server/ when running server scripts. |
CORS_ORIGIN |
http://localhost:5173 |
Allowed frontend origin. |
AUTH_TOKEN |
required | Development API key used by /api/login. |
Agent
| Variable | Default | Description |
|---|---|---|
GEMINI_API_KEY |
required for agent | Google Gemini API key. |
MCP_STDIO_CMD |
npx |
Command used for agent stdio transport. |
MCP_STDIO_ARGS |
tsx,server/src/index.ts |
Comma-separated stdio command args. |
API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/login |
API key | Login with { "apiKey": "..." }; returns access and refresh tokens. |
POST |
/api/refresh |
Refresh token | Body: { "refreshToken": "..." }; returns a new access token. |
GET |
/api/notes |
JWT | List notes. Query params: query, limit, offset. |
POST |
/api/notes |
JWT | Create a note with title, optional content, optional tags. |
GET |
/api/notes/:id |
JWT | Get one note. |
PUT |
/api/notes/:id |
JWT | Update note fields. |
DELETE |
/api/notes/:id |
JWT | Delete a note. |
GET |
/api/live-updates |
none | SSE stream for frontend refresh events. |
GET |
/sse |
none | MCP SSE connection endpoint. |
POST |
/messages |
none | MCP JSON-RPC message endpoint. |
GET |
/api/health |
none | Health check. |
Note payloads
Create note:
{
"title": "Meeting notes",
"content": "Discuss launch plan",
"tags": ["work", "planning"]
}
Update note:
{
"title": "Updated title",
"content": "Updated content",
"tags": ["updated"]
}
MCP Tools
The MCP server exposes these tools to AI clients:
| Tool | Description |
|---|---|
list_notes |
List notes with optional search query, limit, and offset. |
read_note |
Read a single note by ID. |
create_note |
Create a note with title, optional content, and optional tags. |
update_note |
Update selected fields on an existing note. |
delete_note |
Delete a note by ID. |
REST and MCP mutations both emit note update events so connected frontends can refresh through SSE.
Agent Usage
Interactive mode:
npm run dev:agent
Direct Python invocation:
agent/.venv/bin/python agent/agent.py --transport sse --url http://localhost:3000/sse
One-shot mode:
agent/.venv/bin/python agent/agent.py --one-shot "List my notes about planning"
Options:
--transport sse|stdio Transport protocol (default: sse)
--url URL MCP server URL (default: http://localhost:3000/sse)
--model MODEL Gemini model (default: gemini-2.0-flash)
--api-key KEY Gemini API key; prefer GEMINI_API_KEY in agent/.env
--one-shot QUERY Single query mode
Frontend Notes
- The Vite dev server runs on
http://localhost:5173and proxies API calls to the backend. - The app logs in with the development API key by default for local use.
- Note operations use React Query and invalidate
['notes']after mutations. useSSElistens for backend update events and refreshes note queries live.
Validation
Recommended checks before committing application changes:
npm run build
npm run test --prefix client-ui
npm run test:e2e --prefix client-ui
For targeted changes:
npm run build --prefix server
npm run build --prefix client-ui
Troubleshooting
Server fails on startup with environment errors
Check server/.env. JWT_SECRET must be at least 32 characters and AUTH_TOKEN must be set.
Frontend cannot load notes
Make sure the backend is running on http://localhost:3000, the frontend is running through Vite, and CORS_ORIGIN matches http://localhost:5173.
Agent asks for a Gemini key
Create agent/.env with GEMINI_API_KEY=..., or pass --api-key for a one-off run.
Agent cannot connect to MCP
Start the backend first and verify http://localhost:3000/api/health returns {"status":"ok",...}. The default MCP endpoint is http://localhost:3000/sse.
Development Login
Default local API key:
dev-auth-token-change-in-production
Change this in server/.env for any non-local environment.