🎉 Initial release of Obsidian MCP Server plugin
Core Features:
- MCP server implementation with HTTP transport
- JSON-RPC 2.0 message handling
- Protocol version 2024-11-05 support
MCP Tools:
- read_note, create_note, update_note, delete_note
- search_notes, list_notes, get_vault_info
Server Features:
- Configurable HTTP server (default port: 3000)
- Health check and MCP endpoints
- Auto-start option
Security:
- Origin header validation (DNS rebinding protection)
- Optional Bearer token authentication
- CORS configuration
UI:
- Settings panel with full configuration
- Status bar indicator and ribbon icon
- Start/Stop/Restart commands
Documentation:
- Comprehensive README with examples
- Quick Start Guide and Implementation Summary
- Test client script
276 lines
7.2 KiB
TypeScript
276 lines
7.2 KiB
TypeScript
import { App, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
|
import { MCPServer, MCPServerSettings } from './mcp-server';
|
|
|
|
interface MCPPluginSettings extends MCPServerSettings {
|
|
autoStart: boolean;
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: MCPPluginSettings = {
|
|
port: 3000,
|
|
enableCORS: true,
|
|
allowedOrigins: ['*'],
|
|
apiKey: '',
|
|
enableAuth: false,
|
|
autoStart: false
|
|
}
|
|
|
|
export default class MCPServerPlugin extends Plugin {
|
|
settings: MCPPluginSettings;
|
|
mcpServer: MCPServer | null = null;
|
|
statusBarItem: HTMLElement | null = null;
|
|
|
|
async onload() {
|
|
await this.loadSettings();
|
|
|
|
// Add status bar item
|
|
this.statusBarItem = this.addStatusBarItem();
|
|
this.updateStatusBar();
|
|
|
|
// Add ribbon icon to toggle server
|
|
this.addRibbonIcon('server', 'Toggle MCP Server', async () => {
|
|
if (this.mcpServer?.isRunning()) {
|
|
await this.stopServer();
|
|
} else {
|
|
await this.startServer();
|
|
}
|
|
});
|
|
|
|
// Add commands
|
|
this.addCommand({
|
|
id: 'start-mcp-server',
|
|
name: 'Start MCP Server',
|
|
callback: async () => {
|
|
await this.startServer();
|
|
}
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'stop-mcp-server',
|
|
name: 'Stop MCP Server',
|
|
callback: async () => {
|
|
await this.stopServer();
|
|
}
|
|
});
|
|
|
|
this.addCommand({
|
|
id: 'restart-mcp-server',
|
|
name: 'Restart MCP Server',
|
|
callback: async () => {
|
|
await this.stopServer();
|
|
await this.startServer();
|
|
}
|
|
});
|
|
|
|
// Add settings tab
|
|
this.addSettingTab(new MCPServerSettingTab(this.app, this));
|
|
|
|
// Auto-start if enabled
|
|
if (this.settings.autoStart) {
|
|
await this.startServer();
|
|
}
|
|
}
|
|
|
|
async onunload() {
|
|
await this.stopServer();
|
|
}
|
|
|
|
async startServer() {
|
|
if (this.mcpServer?.isRunning()) {
|
|
new Notice('MCP Server is already running');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.mcpServer = new MCPServer(this.app, this.settings);
|
|
await this.mcpServer.start();
|
|
new Notice(`MCP Server started on port ${this.settings.port}`);
|
|
this.updateStatusBar();
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
new Notice(`Failed to start MCP Server: ${message}`);
|
|
console.error('MCP Server start error:', error);
|
|
}
|
|
}
|
|
|
|
async stopServer() {
|
|
if (!this.mcpServer?.isRunning()) {
|
|
new Notice('MCP Server is not running');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.mcpServer.stop();
|
|
new Notice('MCP Server stopped');
|
|
this.updateStatusBar();
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
new Notice(`Failed to stop MCP Server: ${message}`);
|
|
console.error('MCP Server stop error:', error);
|
|
}
|
|
}
|
|
|
|
updateStatusBar() {
|
|
if (this.statusBarItem) {
|
|
const isRunning = this.mcpServer?.isRunning() ?? false;
|
|
this.statusBarItem.setText(
|
|
isRunning
|
|
? `MCP: Running (${this.settings.port})`
|
|
: 'MCP: Stopped'
|
|
);
|
|
this.statusBarItem.addClass('mcp-status-bar');
|
|
}
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
// Update server settings if it's running
|
|
if (this.mcpServer) {
|
|
this.mcpServer.updateSettings(this.settings);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MCPServerSettingTab extends PluginSettingTab {
|
|
plugin: MCPServerPlugin;
|
|
|
|
constructor(app: App, plugin: MCPServerPlugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
display(): void {
|
|
const {containerEl} = this;
|
|
|
|
containerEl.empty();
|
|
|
|
containerEl.createEl('h2', {text: 'MCP Server Settings'});
|
|
|
|
// Auto-start setting
|
|
new Setting(containerEl)
|
|
.setName('Auto-start server')
|
|
.setDesc('Automatically start the MCP server when Obsidian launches')
|
|
.addToggle(toggle => toggle
|
|
.setValue(this.plugin.settings.autoStart)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.autoStart = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// Port setting
|
|
new Setting(containerEl)
|
|
.setName('Port')
|
|
.setDesc('Port number for the HTTP server (requires restart)')
|
|
.addText(text => text
|
|
.setPlaceholder('3000')
|
|
.setValue(String(this.plugin.settings.port))
|
|
.onChange(async (value) => {
|
|
const port = parseInt(value);
|
|
if (!isNaN(port) && port > 0 && port < 65536) {
|
|
this.plugin.settings.port = port;
|
|
await this.plugin.saveSettings();
|
|
}
|
|
}));
|
|
|
|
// CORS setting
|
|
new Setting(containerEl)
|
|
.setName('Enable CORS')
|
|
.setDesc('Enable Cross-Origin Resource Sharing')
|
|
.addToggle(toggle => toggle
|
|
.setValue(this.plugin.settings.enableCORS)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.enableCORS = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// Allowed origins
|
|
new Setting(containerEl)
|
|
.setName('Allowed origins')
|
|
.setDesc('Comma-separated list of allowed origins (* for all)')
|
|
.addText(text => text
|
|
.setPlaceholder('*')
|
|
.setValue(this.plugin.settings.allowedOrigins.join(', '))
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.allowedOrigins = value
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(s => s.length > 0);
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// Authentication
|
|
new Setting(containerEl)
|
|
.setName('Enable authentication')
|
|
.setDesc('Require API key for requests')
|
|
.addToggle(toggle => toggle
|
|
.setValue(this.plugin.settings.enableAuth)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.enableAuth = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// API Key
|
|
new Setting(containerEl)
|
|
.setName('API Key')
|
|
.setDesc('API key for authentication (Bearer token)')
|
|
.addText(text => text
|
|
.setPlaceholder('Enter API key')
|
|
.setValue(this.plugin.settings.apiKey || '')
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.apiKey = value;
|
|
await this.plugin.saveSettings();
|
|
}));
|
|
|
|
// Server status
|
|
containerEl.createEl('h3', {text: 'Server Status'});
|
|
|
|
const statusEl = containerEl.createEl('div', {cls: 'mcp-server-status'});
|
|
const isRunning = this.plugin.mcpServer?.isRunning() ?? false;
|
|
|
|
statusEl.createEl('p', {
|
|
text: isRunning
|
|
? `✅ Server is running on http://127.0.0.1:${this.plugin.settings.port}/mcp`
|
|
: '⭕ Server is stopped'
|
|
});
|
|
|
|
// Control buttons
|
|
const buttonContainer = containerEl.createEl('div', {cls: 'mcp-button-container'});
|
|
|
|
if (isRunning) {
|
|
buttonContainer.createEl('button', {text: 'Stop Server'})
|
|
.addEventListener('click', async () => {
|
|
await this.plugin.stopServer();
|
|
this.display(); // Refresh display
|
|
});
|
|
|
|
buttonContainer.createEl('button', {text: 'Restart Server'})
|
|
.addEventListener('click', async () => {
|
|
await this.plugin.stopServer();
|
|
await this.plugin.startServer();
|
|
this.display(); // Refresh display
|
|
});
|
|
} else {
|
|
buttonContainer.createEl('button', {text: 'Start Server'})
|
|
.addEventListener('click', async () => {
|
|
await this.plugin.startServer();
|
|
this.display(); // Refresh display
|
|
});
|
|
}
|
|
|
|
// Connection info
|
|
if (isRunning) {
|
|
containerEl.createEl('h3', {text: 'Connection Information'});
|
|
|
|
const infoEl = containerEl.createEl('div', {cls: 'mcp-connection-info'});
|
|
infoEl.createEl('p', {text: 'MCP Endpoint:'});
|
|
infoEl.createEl('code', {text: `http://127.0.0.1:${this.plugin.settings.port}/mcp`});
|
|
|
|
infoEl.createEl('p', {text: 'Health Check:'});
|
|
infoEl.createEl('code', {text: `http://127.0.0.1:${this.plugin.settings.port}/health`});
|
|
}
|
|
}
|
|
}
|