feat: implement Obsidian MCP Bundle MVP (Phase 1-3)

- Complete project setup with TypeScript, Jest, MCPB manifest
- Implement foundational infrastructure (CLI executor, logger, error handler)
- Add 9 file operation tools for User Story 1
- Full MCP protocol compliance with stdio transport
- Input validation and sanitization for security
- Comprehensive error handling with actionable messages
- Constitutional compliance: all 6 principles satisfied

MVP includes:
- obsidian_create_note, read, append, prepend, delete, move, rename, open, file_info
- Zod validation schemas for all parameters
- 30s timeout configuration with per-command overrides
- Stderr-only logging with sanitized output
- Graceful shutdown handling

Build:  0 errors, 0 vulnerabilities
Tasks: 48/167 complete (MVP milestone)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-22 11:21:38 -05:00
parent e9e0112240
commit 622b28e42c
35 changed files with 5139 additions and 35 deletions

61
src/config/timeouts.ts Normal file
View File

@@ -0,0 +1,61 @@
/**
* Timeout configuration for CLI commands
* Constitutional Principle IV: Defensive Programming
* FR-018: 30-second default timeout with per-command overrides
*/
import { TimeoutConfig } from '../utils/types.js';
/**
* Default timeout configuration
*/
export const timeoutConfig: TimeoutConfig = {
// Default timeout for all commands (30 seconds)
default: 30000,
// Per-command overrides (in milliseconds)
perCommand: {
// Search operations may take longer on large vaults
search: 45000,
'search-tags': 45000,
'search-properties': 45000,
// Sync operations may take longer
'sync-start': 60000,
'sync-status': 10000,
// Quick operations can have shorter timeouts
'vault-list': 5000,
'note-read': 10000,
open: 5000,
// File operations are typically fast
'create-note': 10000,
'delete-note': 10000,
'move-note': 10000,
'rename-note': 10000,
},
};
/**
* Get timeout for a specific command
*/
export function getCommandTimeout(command: string): number {
// Extract base command name (remove subcommands and flags)
const baseCommand = command.split(' ')[0];
return timeoutConfig.perCommand[baseCommand] || timeoutConfig.default;
}
/**
* Set custom timeout for a command
*/
export function setCommandTimeout(command: string, timeout: number): void {
if (timeout < 1000) {
throw new Error('Timeout must be at least 1000ms (1 second)');
}
if (timeout > 300000) {
throw new Error('Timeout must not exceed 300000ms (5 minutes)');
}
timeoutConfig.perCommand[command] = timeout;
}