Keep/docs/architecture-mcp-server.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

710 lines
15 KiB
Markdown

# Architecture - mcp-server (MCP Server)
## Overview
Architecture documentation for the Memento MCP (Model Context Protocol) server, an Express-based microservice that provides AI assistant and automation integration for the Memento note-taking application.
**Architecture Pattern:** Microservice API
**Framework:** Express.js 4.22.1
**Protocol:** MCP SDK 1.0.4
**Language:** JavaScript (ES modules)
**Database:** Shared SQLite via Prisma
---
## Technology Stack
### Core
| Technology | Version | Purpose |
|------------|---------|---------|
| Node.js | 20+ | Runtime |
| Express.js | 4.22.1 | Web framework |
| MCP SDK | 1.0.4 | Model Context Protocol |
| Prisma | 5.22.0 | ORM |
### Transport
- **Primary:** Stdio (standard input/output)
- **Alternative:** Server-Sent Events (SSE) via `index-sse.js`
### Database
- **ORM:** Prisma 5.22.0
- **Database:** SQLite (shared with keep-notes)
- **File:** `../keep-notes/prisma/dev.db`
---
## Architecture Pattern: Microservice
### System Architecture
```
┌─────────────────┐
│ MCP Client │
│ (AI Assistant) │
│ N8N Workflow │
└────────┬────────┘
│ MCP Protocol
┌─────────────────┐
│ mcp-server │
│ │
│ MCP Tools: │
│ - create_note │
│ - get_notes │
│ - search_notes │
│ - update_note │
│ - delete_note │
│ - toggle_pin │
│ - toggle_archive│
│ - get_labels │
└────────┬────────┘
┌─────────────────┐
│ Prisma ORM │
│ │
│ Shared SQLite │
└─────────────────┘
```
### Communication Flow
1. **MCP Client** (AI assistant, N8N) connects via stdio
2. **Request:** Tool invocation with parameters
3. **Processing:** Server executes business logic
4. **Database:** Prisma queries/updates
5. **Response:** JSON result returned via stdio
---
## Server Structure
### File Organization
```
mcp-server/
├── index.js # Main MCP server (stdio transport)
├── index-sse.js # SSE variant (HTTP + SSE)
├── package.json # Dependencies
├── README.md # Server documentation
├── README-SSE.md # SSE documentation
├── N8N-CONFIG.md # N8N setup guide
└── prisma/ # Prisma client (shared)
├── schema.prisma # Schema reference
└── [generated client]
```
### Entry Points
**Primary: index.js**
```javascript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// ... tool definitions
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Alternative: index-sse.js**
```javascript
// HTTP server with Server-Sent Events
// For web-based MCP clients
```
---
## MCP Tool Architecture
### Tool Registration Pattern
```javascript
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'create_note',
description: 'Create a new note in Memento',
inputSchema: {
type: 'object',
properties: { /* ... */ },
required: ['content']
}
},
// ... 7 more tools
]
}
});
```
### Tool Execution Pattern
```javascript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create_note': {
const note = await prisma.note.create({ /* ... */ });
return {
content: [{ type: 'text', text: JSON.stringify(parseNote(note)) }]
};
}
// ... handle other tools
}
} catch (error) {
throw new McpError(ErrorCode.InternalError, error.message);
}
});
```
---
## Available MCP Tools
### Note Management (6 tools)
| Tool | Purpose | Required Params |
|------|---------|----------------|
| **create_note** | Create note | content |
| **get_notes** | List all notes | (optional) includeArchived, search |
| **get_note** | Get single note | id |
| **update_note** | Update note | id, (optional) fields to update |
| **delete_note** | Delete note | id |
| **search_notes** | Search notes | query |
### Note Operations (2 tools)
| Tool | Purpose | Required Params |
|------|---------|----------------|
| **toggle_pin** | Toggle pin status | id |
| **toggle_archive** | Toggle archive status | id |
### Data Query (1 tool)
| Tool | Purpose | Required Params |
|------|---------|----------------|
| **get_labels** | Get all unique labels | (none) |
**Total Tools:** 9 tools
---
## Database Integration
### Connection Details
```javascript
const prisma = new PrismaClient({
datasources: {
db: {
url: `file:${join(__dirname, '../keep-notes/prisma/dev.db')}`
}
}
});
```
**Path Resolution:**
- From `mcp-server/index.js`
- To: `../keep-notes/prisma/dev.db`
- Absolute: `D:/dev_new_pc/Keep/keep-notes/prisma/dev.db`
### Database Access Pattern
**Read Operations:**
```javascript
const notes = await prisma.note.findMany({
where: { /* conditions */ },
orderBy: [/* sorting */]
});
```
**Write Operations:**
```javascript
const note = await prisma.note.create({
data: { /* note data */ }
});
```
**Update Operations:**
```javascript
const note = await prisma.note.update({
where: { id: args.id },
data: { /* updates */ }
});
```
**Delete Operations:**
```javascript
await prisma.note.delete({
where: { id: args.id }
});
```
---
## Data Processing
### JSON Field Parsing
**Helper Function:**
```javascript
function parseNote(dbNote) {
return {
...dbNote,
checkItems: dbNote.checkItems ? JSON.parse(dbNote.checkItems) : null,
labels: dbNote.labels ? JSON.parse(dbNote.labels) : null,
images: dbNote.images ? JSON.parse(dbNote.images) : null,
};
}
```
**Purpose:** Convert JSON strings from DB to JavaScript objects
**Fields Parsed:**
- `checkItems`: Array of checklist items
- `labels`: Array of label names
- `images`: Array of image URLs/data
---
## Error Handling
### MCP Error Pattern
```javascript
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
// Not found
throw new McpError(ErrorCode.InvalidRequest, 'Note not found');
// Server error
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error.message}`);
// Unknown tool
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
```
### Error Types
| Error Code | Usage |
|------------|-------|
| `InvalidRequest` | Invalid parameters, resource not found |
| `MethodNotFound` | Unknown tool requested |
| `InternalError` | Server-side failures (DB, parsing, etc.) |
---
## Tool Schemas
### create_note
**Input Schema:**
```json
{
"type": "object",
"properties": {
"title": { "type": "string" },
"content": { "type": "string" },
"color": { "type": "string", "enum": ["default","red",...] },
"type": { "type": "string", "enum": ["text","checklist"] },
"checkItems": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"text": { "type": "string" },
"checked": { "type": "boolean" }
},
"required": ["id","text","checked"]
}
},
"labels": { "type": "array", "items": { "type": "string" } },
"images": { "type": "array", "items": { "type": "string" } },
"isPinned": { "type": "boolean" },
"isArchived": { "type": "boolean" }
},
"required": ["content"]
}
```
### update_note
**Input Schema:**
```json
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"content": { "type": "string" },
"color": { "type": "string" },
"checkItems": { "type": "array", "items": {/*...*/} },
"labels": { "type": "array", "items": { "type": "string" } },
"isPinned": { "type": "boolean" },
"isArchived": { "type": "boolean" },
"images": { "type": "array", "items": { "type": "string" } }
},
"required": ["id"]
}
```
**Behavior:** Only updates fields provided (partial update)
---
## Transport Layer
### Stdio Transport (Primary)
**Implementation:**
```javascript
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Use Cases:**
- AI assistants (ChatGPT, Claude, etc.)
- N8N MCP integration
- Command-line tools
- Desktop applications
**Communication:**
- **Input:** STDIN (JSON-RPC messages)
- **Output:** STDOUT (JSON-RPC responses)
- **Logging:** STDERR (diagnostic messages)
### SSE Transport (Alternative)
**File:** `index-sse.js`
**Implementation:** HTTP server with Server-Sent Events
**Use Cases:**
- Web-based clients
- Browser integrations
- Real-time notifications
- Long-running operations
---
## Concurrency Model
### Single-Threaded Event Loop
- Node.js event loop
- No parallel execution
- Sequential request handling
### Database Concurrency
- SQLite handles concurrent reads
- Single writer limitation
- Prisma manages connection
### Scalability
**Current:** Single instance
**Limitations:**
- No load balancing
- No automatic failover
- Single point of failure
**Future Enhancements:**
- Multiple instances with connection pooling
- PostgreSQL for better concurrency
- Redis for session management
- Message queue for async operations
---
## Security Architecture
### Current: No Authentication
**Rationale:**
- Trusted environment (localhost)
- AI assistants need full access
- Simplified integration
### Security Considerations
**Risks:**
- No access control
- No rate limiting
- No audit logging
- Direct database access
**Recommendations for Production:**
1. Add API key authentication
2. Implement rate limiting
3. Add request logging
4. Restrict to localhost or VPN
5. Use reverse proxy (nginx) for SSL
### Future Security
- JWT tokens for authentication
- Role-based access control
- IP whitelisting
- Request signing
- Audit logging
---
## Performance Characteristics
### Latency
- **Direct DB access:** ~1-5ms per query
- **JSON parsing:** ~0.1-0.5ms
- **Total tool execution:** ~5-20ms
### Throughput
- **Single-threaded:** Limited by CPU
- **SQLite:** ~1000-5000 ops/sec
- **Bottleneck:** Database I/O
### Optimization Opportunities
1. **Connection pooling:** Reuse Prisma client
2. **Query optimization:** Add indexes
3. **Caching:** Redis for frequent queries
4. **Batching:** Batch multiple operations
---
## Monitoring & Observability
### Current: Basic Logging
```javascript
console.error('Memento MCP server running on stdio');
```
**Logged to:** STDERR (won't interfere with stdio transport)
### Future Monitoring Needs
1. **Request Logging:** Log all tool invocations
2. **Error Tracking:** Sentry, Rollbar
3. **Performance Monitoring:** Query latency
4. **Metrics:** Tool usage statistics
5. **Health Checks:** `/health` endpoint
---
## Deployment Architecture
### Development
```bash
cd mcp-server
npm install
npm start
# Connects via stdio
```
### Production Options
**Option 1: Standalone Process**
```bash
node /path/to/mcp-server/index.js
```
**Option 2: Docker Container**
```dockerfile
FROM node:20-alpine
COPY mcp-server/ /app
WORKDIR /app
RUN npm install
CMD ["node", "index.js"]
```
**Option 3: Docker Compose**
```yaml
services:
mcp-server:
build: ./mcp-server
volumes:
- ./keep-notes/prisma:/app/db
```
**Option 4: Process Manager**
- PM2
- Systemd service
- Supervisord
---
## Integration Patterns
### N8N Workflow Integration
**Setup:** See `N8N-CONFIG.md`
**Usage:**
1. Add MCP node in N8N
2. Configure connection to mcp-server
3. Select tools (create_note, search_notes, etc.)
4. Build workflow
**Example Workflow:**
- Trigger: Webhook
- Tool: create_note
- Parameters: From webhook data
- Output: Created note
### AI Assistant Integration
**Supported Assistants:**
- ChatGPT (via MCP plugin)
- Claude (via MCP plugin)
- Custom AI agents
**Usage Pattern:**
1. User asks assistant: "Create a note about..."
2. Assistant calls MCP tools
3. Tools execute on Memento DB
4. Results returned to assistant
5. Assistant responds to user
---
## Versioning & Compatibility
### Current Version
**Server:** 1.0.0
**MCP Protocol:** 1.0.4
### Backward Compatibility
- Tool schemas evolve
- New tools added (non-breaking)
- Existing tools maintained
### Versioning Strategy
- Semantic versioning (MAJOR.MINOR.PATCH)
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes
---
## Testing Strategy
### Current: Manual Testing
- N8N workflow testing
- Direct stdio invocation
- SSE variant testing
### Recommended Tests
1. **Unit Tests:** Tool execution logic
2. **Integration Tests:** Prisma operations
3. **E2E Tests:** Full MCP protocol flow
4. **Load Tests:** Concurrent tool execution
### Test Tools
- Jest for unit tests
- Supertest for HTTP endpoints
- Playwright for E2E
- Artillery for load testing
---
## Configuration Management
### Environment Variables
**Current:** Hardcoded (dev.db path)
**Recommended:**
```bash
DATABASE_URL="file:../keep-notes/prisma/dev.db"
LOG_LEVEL="info"
PORT="3000" # For SSE variant
```
### Configuration File
**Future:** `config.json`
```json
{
"database": {
"url": "file:../keep-notes/prisma/dev.db"
},
"logging": {
"level": "info"
},
"server": {
"name": "memento-mcp-server",
"version": "1.0.0"
}
}
```
---
## Maintenance & Operations
### Startup
```bash
node index.js
# Output to stderr: "Memento MCP server running on stdio"
```
### Shutdown
- Send SIGTERM (Ctrl+C)
- Graceful shutdown
- Close database connections
### Health Checks
**Future:** `/health` endpoint
```javascript
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() })
})
```
---
## Troubleshooting
### Common Issues
**1. Database Connection Failed**
- **Symptom:** "Unable to connect to database"
- **Cause:** Incorrect path or missing DB file
- **Fix:** Verify `../keep-notes/prisma/dev.db` exists
**2. Permission Denied**
- **Symptom:** "EACCES: permission denied"
- **Cause:** File permissions on SQLite DB
- **Fix:** chmod 644 dev.db
**3. Stdio Not Working**
- **Symptom:** No response from server
- **Cause:** Client not connected to stdin/stdout
- **Fix:** Ensure proper stdio redirection
---
## Future Enhancements
### Short Term
1. Add authentication
2. Implement rate limiting
3. Add request logging
4. Health check endpoint
5. Configuration file support
### Long Term
1. WebSocket support for real-time
2. GraphQL integration
3. Batch operations
4. Transaction support
5. Multi-database support
---
## Summary
The mcp-server is a lightweight, focused microservice that:
- **Exposes 9 MCP tools** for note management
- **Connects directly to SQLite** via Prisma
- **Uses stdio transport** for AI/automation integration
- **Provides N8N workflow** integration
- **Shares database** with keep-notes web app
- **Offers SSE variant** for web clients
This architecture provides a clean separation of concerns while maintaining data consistency through the shared database layer.