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
This commit is contained in:
709
docs/architecture-mcp-server.md
Normal file
709
docs/architecture-mcp-server.md
Normal file
@@ -0,0 +1,709 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user