mcp-note-app
by PyaePhyo-Win
README.md
# 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
```text
┌─────────────────┐ 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-flash` by 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:
```bash
npm run install:all
```
Or install each layer manually:
```bash
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:
```bash
npm install
```
### 2. Configure environment
Create `server/.env`:
```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:
```env
GEMINI_API_KEY=your-gemini-api-key
```
Optional stdio transport variables for the agent:
```env
MCP_STDIO_CMD=npx
MCP_STDIO_ARGS=tsx,server/src/index.ts
```
> Do not commit `.env` files, API keys, JWT secrets, or local SQLite database files.
### 3. Start the app
Start backend and frontend together:
```bash
npm run dev
```
Or run them separately:
```bash
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:
```bash
npm run dev:agent
```
One-shot example:
```bash
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
```text
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:
```json
{
"title": "Meeting notes",
"content": "Discuss launch plan",
"tags": ["work", "planning"]
}
```
Update note:
```json
{
"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:
```bash
npm run dev:agent
```
Direct Python invocation:
```bash
agent/.venv/bin/python agent/agent.py --transport sse --url http://localhost:3000/sse
```
One-shot mode:
```bash
agent/.venv/bin/python agent/agent.py --one-shot "List my notes about planning"
```
Output formats:
```bash
# Script-friendly text, the default for --one-shot
agent/.venv/bin/python agent/agent.py --one-shot "List my notes" --format plain
# Rich Markdown, tool status, and note tables
agent/.venv/bin/python agent/agent.py --one-shot "List my notes" --format pretty
# One JSON object for scripts and integrations
agent/.venv/bin/python agent/agent.py --one-shot "List my notes" --format json
```
Interactive mode uses the rich terminal renderer by default. It supports persistent command
history, completion for slash commands, and multiline prompts: press `Esc` then `Enter` to
insert a newline, and press `Enter` to submit. Prompts in the same running process share
conversation context, including relevant tool calls and results. Use `/reset` to forget the
model context, or `/history` to inspect a bounded summary. Context is automatically trimmed
at complete turn boundaries when it exceeds the memory budget; it is lost when the process
exits. Use `/help` to view available commands.
Note IDs are full UUIDs. For delete, update, or read requests, you can identify a note by
title instead of copying an ID: the agent will search with `list_notes` and use the exact ID
from that result. Never use a shortened ID from a manually copied table value.
Options:
```text
--transport sse|stdio Transport protocol (default: sse)
--url URL MCP server URL (default: http://localhost:3000/sse)
--model MODEL Gemini model (default: gemini-2.5-flash)
--api-key KEY Gemini API key; prefer GEMINI_API_KEY in agent/.env
--one-shot QUERY Single query mode
--format FORMAT Output format: plain, pretty, or json
--verbose Show connection, turn, and tool-result details
--no-color Disable ANSI color in pretty output
--history-file PATH Override the interactive prompt history path
--memory-max-chars N Approximate maximum model context size across turns (default: 24000)
```
Interactive commands:
```text
/help Show command help
/tools List available MCP tools
/clear Clear the terminal
/reset Forget the current model conversation
/history Show a summary of model conversation memory
/format [FORMAT] View or set plain, pretty, or json output
/model [MODEL] View or set the Gemini model for future prompts
/verbose [on|off] View or set detailed logging
/quit Exit the agent
```
## Frontend Notes
- The Vite dev server runs on `http://localhost:5173` and 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.
- `useSSE` listens for backend update events and refreshes note queries live.
## Validation
Recommended checks before committing application changes:
```bash
npm run build
npm run test --prefix client-ui
npm run test:e2e --prefix client-ui
```
For targeted changes:
```bash
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:
```text
dev-auth-token-change-in-production
```
Change this in `server/.env` for any non-local environment.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues