8 Commits

Author SHA1 Message Date
7f82902b5e 1.0.0 2025-10-26 17:07:19 -04:00
d1eb545fed fix: properly preserve UI state in settings panel
This fixes the issues introduced in the previous commit where:
- Authentication section would collapse when switching config tabs and couldn't be reopened
- Notification toggle would disappear after enabling notifications

Root cause: The previous update methods were removing or re-querying DOM elements incorrectly.

Solution:
- Store direct references to configContainerEl and notificationToggleEl
- Wrap notification toggle in dedicated container to preserve it during updates
- updateNotificationSection() now preserves both summary AND toggle, only removes additional settings
- updateConfigSection() uses stored reference instead of querying, preventing collapse

Both sections now maintain their open/closed state correctly during targeted updates.
2025-10-26 17:03:39 -04:00
a02ebb85d5 fix: prevent settings panel sections from resetting state
Fixed two UI bugs in the settings panel:
- Authentication section no longer collapses when switching between Windsurf/Claude Code config tabs
- Notification settings now properly appear/disappear when toggling the notifications enable switch

Changes:
- Added authDetailsEl reference to track authentication details element state
- Created updateConfigSection() method to update only config tabs without full page re-render
- Fixed updateNotificationSection() child removal logic to properly clear settings before rebuild
- Both methods now preserve the open/closed state of their respective collapsible sections
2025-10-26 16:57:18 -04:00
c8014bd8c9 1.0.0-alpha.7 2025-10-26 16:49:35 -04:00
cc4e71f920 refactor: remove 'obsidian' from plugin ID and update branding
- Change plugin ID from 'obsidian-mcp-server' to 'mcp-server'
- Remove 'Obsidian' from plugin description per guidelines
- Update documentation to use new plugin folder name
- Update installation paths to .obsidian/plugins/mcp-server/
- Update package name to match new plugin ID
- Simplify README title and description
2025-10-26 16:47:36 -04:00
175aebb218 1.0.0 2025-10-26 14:05:49 -04:00
52a5b4ce54 1.0.0-alpha.6 2025-10-26 13:52:40 -04:00
87d04ee834 fix: remove jq dependency from Gitea release step
Use grep and sed instead of jq to parse JSON response, as jq
is not available on all Gitea runners.
2025-10-26 13:52:40 -04:00
8 changed files with 159 additions and 30 deletions

View File

@@ -116,7 +116,7 @@ jobs:
TAG_VERSION="${GITHUB_REF#refs/tags/}"
# Create release via API
RELEASE_ID=$(curl -X POST \
RESPONSE=$(curl -s -X POST \
-H "Accept: application/json" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
@@ -125,12 +125,15 @@ jobs:
{
"tag_name": "$TAG_VERSION",
"name": "$TAG_VERSION",
"body": "Release $TAG_VERSION\n\n## Changes\n\n*Add release notes here before publishing*\n\n## Installation\n\n1. Download main.js, manifest.json, and styles.css\n2. Create a folder in .obsidian/plugins/obsidian-mcp-server/\n3. Copy the three files into the folder\n4. Reload Obsidian\n5. Enable the plugin in Settings → Community Plugins",
"body": "Release $TAG_VERSION\n\n## Changes\n\n*Add release notes here before publishing*\n\n## Installation\n\n1. Download main.js, manifest.json, and styles.css\n2. Create a folder in .obsidian/plugins/mcp-server/\n3. Copy the three files into the folder\n4. Reload Obsidian\n5. Enable the plugin in Settings → Community Plugins",
"draft": true,
"prerelease": false
}
EOF
)" | jq -r '.id')
)")
# Extract release ID using grep and sed (no jq dependency)
RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
echo "Created release with ID: $RELEASE_ID"

View File

@@ -27,7 +27,7 @@ The build command includes TypeScript type checking via `tsc -noEmit -skipLibChe
### Installing in Obsidian
After building, the plugin outputs `main.js` to the root directory. To test in Obsidian:
1. Copy `main.js`, `manifest.json`, and `styles.css` to your vault's `.obsidian/plugins/obsidian-mcp-server/` directory
1. Copy `main.js`, `manifest.json`, and `styles.css` to your vault's `.obsidian/plugins/mcp-server/` directory
2. Reload Obsidian (Ctrl/Cmd + R in dev mode)
3. Enable the plugin in Settings → Community Plugins

View File

@@ -1,6 +1,6 @@
# Contributing to Obsidian MCP Server Plugin
# Contributing to MCP Server Plugin
Thank you for your interest in contributing to the Obsidian MCP Server Plugin! This document provides guidelines and information for contributors.
Thank you for your interest in contributing to the MCP Server Plugin! This document provides guidelines and information for contributors.
## Table of Contents
@@ -64,12 +64,12 @@ For feature requests, please describe:
**Linux/macOS:**
```bash
ln -s /path/to/your/dev/obsidian-mcp-server /path/to/vault/.obsidian/plugins/obsidian-mcp-server
ln -s /path/to/your/dev/obsidian-mcp-server /path/to/vault/.obsidian/plugins/mcp-server
```
**Windows (Command Prompt as Administrator):**
```cmd
mklink /D "C:\path\to\vault\.obsidian\plugins\obsidian-mcp-server" "C:\path\to\your\dev\obsidian-mcp-server"
mklink /D "C:\path\to\vault\.obsidian\plugins\mcp-server" "C:\path\to\your\dev\obsidian-mcp-server"
```
4. **Start development build:**

View File

@@ -1,6 +1,6 @@
# Obsidian MCP Server Plugin
# MCP Server Plugin
An Obsidian plugin that makes your vault accessible via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) over HTTP. This allows AI assistants and other MCP clients to interact with your Obsidian vault programmatically.
A plugin that makes your vault accessible via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) over HTTP. This allows AI assistants and other MCP clients to interact with your vault programmatically.
**Version:** 1.0.0 | **Tested with:** Obsidian v1.9.14 | **License:** MIT

View File

@@ -1,9 +1,9 @@
{
"id": "obsidian-mcp-server",
"id": "mcp-server",
"name": "MCP Server",
"version": "1.0.0-alpha.5",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Exposes Obsidian vault operations via Model Context Protocol (MCP) over HTTP.",
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
"author": "William Ballou",
"isDesktopOnly": true,
"fundingUrl": {

8
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-mcp-server",
"version": "1.0.0-alpha.5",
"name": "mcp-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-mcp-server",
"version": "1.0.0-alpha.5",
"name": "mcp-server",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",

View File

@@ -1,7 +1,7 @@
{
"name": "obsidian-mcp-server",
"version": "1.0.0-alpha.5",
"description": "MCP (Model Context Protocol) server plugin for Obsidian - exposes vault operations via HTTP",
"name": "mcp-server",
"version": "1.0.0",
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",

View File

@@ -6,6 +6,9 @@ import { generateApiKey } from './utils/auth-utils';
export class MCPServerSettingTab extends PluginSettingTab {
plugin: MCPServerPlugin;
private notificationDetailsEl: HTMLDetailsElement | null = null;
private notificationToggleEl: HTMLElement | null = null;
private authDetailsEl: HTMLDetailsElement | null = null;
private configContainerEl: HTMLElement | null = null;
private activeConfigTab: 'windsurf' | 'claude-code' = 'windsurf';
constructor(app: App, plugin: MCPServerPlugin) {
@@ -118,8 +121,11 @@ export class MCPServerSettingTab extends PluginSettingTab {
containerEl.empty();
// Clear notification details reference for fresh render
// Clear references for fresh render
this.notificationDetailsEl = null;
this.notificationToggleEl = null;
this.authDetailsEl = null;
this.configContainerEl = null;
containerEl.createEl('h2', {text: 'MCP Server Settings'});
@@ -198,6 +204,9 @@ export class MCPServerSettingTab extends PluginSettingTab {
authSummary.style.cursor = 'pointer';
authSummary.setText('Authentication & Configuration');
// Store reference for targeted updates
this.authDetailsEl = authDetails;
// API Key Display (always show - auth is always enabled)
new Setting(authDetails)
.setName('API Key Management')
@@ -254,6 +263,9 @@ export class MCPServerSettingTab extends PluginSettingTab {
const configContainer = authDetails.createDiv({cls: 'mcp-config-snippet'});
configContainer.style.marginBottom = '20px';
// Store reference for targeted updates
this.configContainerEl = configContainer;
// Tab buttons for switching between clients
const tabContainer = configContainer.createDiv({cls: 'mcp-config-tabs'});
tabContainer.style.display = 'flex';
@@ -273,7 +285,7 @@ export class MCPServerSettingTab extends PluginSettingTab {
windsurfTab.style.fontWeight = this.activeConfigTab === 'windsurf' ? 'bold' : 'normal';
windsurfTab.addEventListener('click', () => {
this.activeConfigTab = 'windsurf';
this.display();
this.updateConfigSection();
});
// Claude Code tab button
@@ -288,7 +300,7 @@ export class MCPServerSettingTab extends PluginSettingTab {
claudeCodeTab.style.fontWeight = this.activeConfigTab === 'claude-code' ? 'bold' : 'normal';
claudeCodeTab.addEventListener('click', () => {
this.activeConfigTab = 'claude-code';
this.display();
this.updateConfigSection();
});
// Get configuration for active tab
@@ -353,8 +365,11 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Store reference for targeted updates
this.notificationDetailsEl = notifDetails;
// Enable notifications
new Setting(notifDetails)
// Enable notifications - create container for the toggle setting
const notificationToggleContainer = notifDetails.createDiv({cls: 'mcp-notification-toggle'});
this.notificationToggleEl = notificationToggleContainer;
new Setting(notificationToggleContainer)
.setName('Enable notifications')
.setDesc('Show when MCP tools are called')
.addToggle(toggle => toggle
@@ -376,7 +391,7 @@ export class MCPServerSettingTab extends PluginSettingTab {
* Update only the notification section without re-rendering entire page
*/
private updateNotificationSection(): void {
if (!this.notificationDetailsEl) {
if (!this.notificationDetailsEl || !this.notificationToggleEl) {
// Fallback to full re-render if reference lost
this.display();
return;
@@ -385,13 +400,16 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Store current open state
const wasOpen = this.notificationDetailsEl.open;
// Find and remove all child elements except the summary
// Remove all children except the summary and the toggle container
const summary = this.notificationDetailsEl.querySelector('summary');
while (this.notificationDetailsEl.lastChild && this.notificationDetailsEl.lastChild !== summary) {
this.notificationDetailsEl.removeChild(this.notificationDetailsEl.lastChild);
const children = Array.from(this.notificationDetailsEl.children);
for (const child of children) {
if (child !== summary && child !== this.notificationToggleEl) {
this.notificationDetailsEl.removeChild(child);
}
}
// Rebuild notification settings
// Rebuild notification settings only if enabled
if (this.plugin.settings.notificationsEnabled) {
this.renderNotificationSettings(this.notificationDetailsEl);
}
@@ -399,4 +417,112 @@ export class MCPServerSettingTab extends PluginSettingTab {
// Restore open state
this.notificationDetailsEl.open = wasOpen;
}
/**
* Update only the config section without re-rendering entire page
*/
private updateConfigSection(): void {
if (!this.configContainerEl) {
// Fallback to full re-render if reference lost
this.display();
return;
}
// Store current open state of the auth details
const wasOpen = this.authDetailsEl?.open ?? false;
// Clear the config container
this.configContainerEl.empty();
// Tab buttons for switching between clients
const tabContainer = this.configContainerEl.createDiv({cls: 'mcp-config-tabs'});
tabContainer.style.display = 'flex';
tabContainer.style.gap = '8px';
tabContainer.style.marginBottom = '16px';
tabContainer.style.borderBottom = '1px solid var(--background-modifier-border)';
// Windsurf tab button
const windsurfTab = tabContainer.createEl('button', {text: 'Windsurf'});
windsurfTab.style.padding = '8px 16px';
windsurfTab.style.border = 'none';
windsurfTab.style.background = 'none';
windsurfTab.style.cursor = 'pointer';
windsurfTab.style.borderBottom = this.activeConfigTab === 'windsurf'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
windsurfTab.style.fontWeight = this.activeConfigTab === 'windsurf' ? 'bold' : 'normal';
windsurfTab.addEventListener('click', () => {
this.activeConfigTab = 'windsurf';
this.updateConfigSection();
});
// Claude Code tab button
const claudeCodeTab = tabContainer.createEl('button', {text: 'Claude Code'});
claudeCodeTab.style.padding = '8px 16px';
claudeCodeTab.style.border = 'none';
claudeCodeTab.style.background = 'none';
claudeCodeTab.style.cursor = 'pointer';
claudeCodeTab.style.borderBottom = this.activeConfigTab === 'claude-code'
? '2px solid var(--interactive-accent)'
: '2px solid transparent';
claudeCodeTab.style.fontWeight = this.activeConfigTab === 'claude-code' ? 'bold' : 'normal';
claudeCodeTab.addEventListener('click', () => {
this.activeConfigTab = 'claude-code';
this.updateConfigSection();
});
// Get configuration for active tab
const {filePath, config, usageNote} = this.generateConfigForClient(this.activeConfigTab);
// Tab content area
const tabContent = this.configContainerEl.createDiv({cls: 'mcp-config-content'});
tabContent.style.marginTop = '16px';
// File location label
const fileLocationLabel = tabContent.createEl('p', {text: 'Configuration file location:'});
fileLocationLabel.style.marginBottom = '4px';
fileLocationLabel.style.fontSize = '0.9em';
fileLocationLabel.style.color = 'var(--text-muted)';
// File path display
const filePathDisplay = tabContent.createEl('div', {text: filePath});
filePathDisplay.style.padding = '8px';
filePathDisplay.style.backgroundColor = 'var(--background-secondary)';
filePathDisplay.style.borderRadius = '4px';
filePathDisplay.style.fontFamily = 'monospace';
filePathDisplay.style.fontSize = '0.9em';
filePathDisplay.style.marginBottom = '12px';
filePathDisplay.style.color = 'var(--text-muted)';
// Copy button
const copyConfigButton = tabContent.createEl('button', {text: '📋 Copy Configuration'});
copyConfigButton.style.marginBottom = '12px';
copyConfigButton.addEventListener('click', async () => {
await navigator.clipboard.writeText(JSON.stringify(config, null, 2));
new Notice('✅ Configuration copied to clipboard');
});
// Config JSON display
const configDisplay = tabContent.createEl('pre');
configDisplay.style.padding = '12px';
configDisplay.style.backgroundColor = 'var(--background-secondary)';
configDisplay.style.borderRadius = '4px';
configDisplay.style.fontSize = '0.85em';
configDisplay.style.overflowX = 'auto';
configDisplay.style.userSelect = 'text';
configDisplay.style.cursor = 'text';
configDisplay.style.marginBottom = '12px';
configDisplay.textContent = JSON.stringify(config, null, 2);
// Usage note
const usageNoteDisplay = tabContent.createEl('p', {text: usageNote});
usageNoteDisplay.style.fontSize = '0.9em';
usageNoteDisplay.style.color = 'var(--text-muted)';
usageNoteDisplay.style.fontStyle = 'italic';
// Restore open state (only if authDetailsEl is available)
if (this.authDetailsEl) {
this.authDetailsEl.open = wasOpen;
}
}
}