Skip to main content
Glama
PyaePhyo-Win

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.