## 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
710 lines
15 KiB
Markdown
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.
|