Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions CONTRIBUTION_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Contribution Summary

## Fixed Issues

This contribution fixes three issues from the obsidian-mcp-tools repository:

### 1. Issue #37: Trailing slash bug causing HTTP 500 errors
**Problem**: When `list_vault_files` is called with a directory parameter containing a trailing slash (e.g., `"DevOps/"`), it creates a double slash in the URL (`/vault/DevOps//`) which causes a 404 error wrapped in a 500 response.

**Solution**:
- Created `normalizeDirectory()` utility function to strip trailing slashes
- Updated `list_vault_files` tool to normalize directory paths before constructing URLs
- Added comprehensive tests

**Files changed**:
- `packages/mcp-server/src/shared/normalizePath.ts` (new)
- `packages/mcp-server/src/shared/normalizePath.test.ts` (new)
- `packages/mcp-server/src/features/local-rest-api/index.ts`

---

### 2. Issue #36: Duplicate /home/<user> in download path
**Problem**: On systems with symlinked home directories, the install path contains duplicate path segments (e.g., `/home/user/home/user/vault/.obsidian/plugins/mcp-tools/bin`).

**Solution**:
- Created `normalizeDuplicateSegments()` utility to detect and remove repeating path patterns
- Applied normalization to install paths after symlink resolution
- Added comprehensive tests including real-world scenarios

**Files changed**:
- `packages/obsidian-plugin/src/features/mcp-server-install/utils/normalizePath.ts` (new)
- `packages/obsidian-plugin/src/features/mcp-server-install/utils/normalizePath.test.ts` (new)
- `packages/obsidian-plugin/src/features/mcp-server-install/services/status.ts`

---

### 3. Issue #26: Platform selection for MCP server binary
**Problem**: Users running Obsidian on Windows but wanting to use the Linux MCP server in WSL had no way to override the auto-detected platform.

**Solution**:
- Extended `McpToolsPluginSettings` with custom configuration options:
- `customPlatform`: Override detected OS platform
- `customArch`: Override detected architecture
- `customBinaryPath`: Custom binary location
- `customCommand`: Custom wrapper command (e.g., wsl.exe)
- `customEnvVars`: Additional environment variables
- `customHost`: Custom server host
- Updated `getPlatform()` and `getArch()` to check settings first
- Modified `updateClaudeConfig()` to use custom command and environment variables
- Added tests for platform override functionality

**Example use case** (WSL):
```typescript
{
customPlatform: "linux",
customCommand: "wsl.exe --distribution Ubuntu -- bash -c \"mcp-server-bin\"",
customEnvVars: {
OBSIDIAN_API_KEY: "your-key",
CUSTOM_VAR: "value"
},
customHost: "127.0.0.1"
}
```

**Files changed**:
- `packages/obsidian-plugin/src/types.ts`
- `packages/obsidian-plugin/src/features/mcp-server-install/services/install.ts`
- `packages/obsidian-plugin/src/features/mcp-server-install/services/status.ts`
- `packages/obsidian-plugin/src/features/mcp-server-install/services/config.ts`
- `packages/obsidian-plugin/src/features/mcp-server-install/services/install.test.ts` (new)

---

## Test Coverage

All fixes include comprehensive unit tests:

**Test files**:
- `packages/mcp-server/src/shared/normalizePath.test.ts` - 9 tests covering path normalization
- `packages/obsidian-plugin/src/features/mcp-server-install/utils/normalizePath.test.ts` - 10 tests for duplicate segment detection
- `packages/obsidian-plugin/src/features/mcp-server-install/services/install.test.ts` - 8 tests for platform/arch override

**Test results**: All 19 new tests pass ✅

---

## Build Verification

Both packages build successfully:
- ✅ `packages/mcp-server` - Compiled successfully
- ✅ `packages/obsidian-plugin` - TypeScript compilation successful

---

## Commits

The changes are organized into 4 commits:

1. `ad21259` - fix: normalize directory paths to prevent double slashes (#37)
2. `b09e51b` - fix: remove duplicate path segments in install path (#36)
3. `15db9ea` - feat: add platform and architecture selection for MCP server (#26)
4. `70e6f38` - chore: update bun.lock after dependency installation

---

## Development Approach

All fixes were developed using **Test-Driven Development (TDD)**:
1. Write failing tests first
2. Implement the minimal code to make tests pass
3. Refactor for clarity and maintainability
4. Verify all tests pass and build succeeds

---

## Next Steps

To create pull requests for these changes:

1. Fork the repository: `https://github.com/jacksteamdev/obsidian-mcp-tools`
2. Add your fork as a remote: `git remote add fork <your-fork-url>`
3. Push the branch: `git push fork fix/issues-37-36-26`
4. Create a pull request from your fork to the main repository

Alternatively, you can cherry-pick individual commits if you want to create separate PRs for each issue:
```bash
git cherry-pick ad21259 # For issue #37
git cherry-pick b09e51b # For issue #36
git cherry-pick 15db9ea # For issue #26
```

---

## Questions or Issues?

If you have questions about these changes, please reach out via:
- GitHub issues
- Discord: https://discord.gg/q59pTrN9AA

---

**Note**: All changes follow the project's contributing guidelines and maintain backward compatibility.
7 changes: 4 additions & 3 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"dependencies": {
Expand Down Expand Up @@ -269,7 +270,7 @@

"@types/body-parser": ["@types/[email protected]", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="],

"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],

"@types/codemirror": ["@types/[email protected]", "", { "dependencies": { "@types/tern": "*" } }, "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw=="],

Expand Down Expand Up @@ -403,7 +404,7 @@

"buffer-crc32": ["[email protected]", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],

"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],

"bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],

Expand Down Expand Up @@ -861,7 +862,7 @@

"object.assign": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],

"obsidian": ["obsidian@1.8.7", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA=="],
"obsidian": ["obsidian@1.10.3", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "sha512-VP+ZSxNMG7y6Z+sU9WqLvJAskCfkFrTz2kFHWmmzis+C+4+ELjk/sazwcTHrHXNZlgCeo8YOlM6SOrAFCynNew=="],

"obsidian-calendar-ui": ["[email protected]", "", { "dependencies": { "obsidian-daily-notes-interface": "0.8.4", "svelte": "3.35.0", "tslib": "2.1.0" } }, "sha512-hdoRqCPnukfRgCARgArXaqMQZ+Iai0eY7f0ZsFHHfywpv4gKg3Tx5p47UsLvRO5DD+4knlbrL7Gel57MkfcLTw=="],

Expand Down
26 changes: 25 additions & 1 deletion docs/features/mcp-server-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ src/features/mcp-server-install/
- Added robust symlink handling for binary paths
- Ensures correct operation even with complex vault setups
- Handles non-existent paths during resolution
- Normalizes duplicate path segments (e.g., /home/user/home/user/vault)
- Particularly helpful for symlinked home directories

3. Status Management
- Unified status interface with version tracking
Expand Down Expand Up @@ -214,6 +216,28 @@ Implemented robust platform detection and path handling:
- Windows: Handles UNC paths and environment variables
- macOS: Proper binary permissions and config paths
- Linux: Flexible configuration for various distributions
- WSL Support: Custom platform selection for running Linux binaries from Windows

### Custom Configuration
Added support for advanced configuration scenarios:
- **Custom Platform/Architecture**: Override auto-detection for WSL and cross-platform setups
- **Custom Binary Path**: Specify alternative binary locations
- **Custom Command**: Wrap server execution (e.g., `wsl.exe` for WSL scenarios)
- **Custom Environment Variables**: Pass additional environment configuration
- **Custom Host**: Configure server connection host

Example WSL configuration:
```typescript
{
customPlatform: "linux",
customArch: "x64",
customCommand: "wsl.exe --distribution Ubuntu -- bash -c \"mcp-server\"",
customEnvVars: {
OBSIDIAN_API_KEY: "your-key",
OBSIDIAN_HOST: "127.0.0.1"
}
}
```

### Future Considerations
1. Version Management
Expand All @@ -223,7 +247,7 @@ Implemented robust platform detection and path handling:

2. Configuration
- Add backup/restore of Claude config
- Support custom binary locations
- ~~Support custom binary locations~~ ✅ **Implemented** (see Custom Configuration)
- Allow custom log paths

3. Error Recovery
Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/src/features/local-rest-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { makeRequest, type ToolRegistry } from "$/shared";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { type } from "arktype";
import { LocalRestAPI } from "shared";
import { normalizeDirectory } from "$/shared/normalizePath";

export function registerLocalRestApiTools(tools: ToolRegistry, server: Server) {
// GET Status
Expand Down Expand Up @@ -251,7 +252,8 @@ export function registerLocalRestApiTools(tools: ToolRegistry, server: Server) {
"List files in the root directory or a specified subdirectory of your vault.",
),
async ({ arguments: args }) => {
const path = args.directory ? `${args.directory}/` : "";
const normalizedDir = normalizeDirectory(args.directory);
const path = normalizedDir ? `${normalizedDir}/` : "";
const data = await makeRequest(
LocalRestAPI.ApiVaultFileResponse.or(
LocalRestAPI.ApiVaultDirectoryResponse,
Expand Down
40 changes: 40 additions & 0 deletions packages/mcp-server/src/shared/normalizePath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, test } from "bun:test";
import { normalizeDirectory } from "./normalizePath";

describe("normalizeDirectory", () => {
test("removes single trailing slash", () => {
expect(normalizeDirectory("DevOps/")).toBe("DevOps");
});

test("removes multiple trailing slashes", () => {
expect(normalizeDirectory("DevOps///")).toBe("DevOps");
});

test("leaves path without trailing slash unchanged", () => {
expect(normalizeDirectory("DevOps")).toBe("DevOps");
});

test("handles nested paths with trailing slash", () => {
expect(normalizeDirectory("path/to/dir/")).toBe("path/to/dir");
});

test("handles nested paths without trailing slash", () => {
expect(normalizeDirectory("path/to/dir")).toBe("path/to/dir");
});

test("returns empty string for empty string", () => {
expect(normalizeDirectory("")).toBe("");
});

test("returns undefined for undefined input", () => {
expect(normalizeDirectory(undefined)).toBeUndefined();
});

test("handles root slash", () => {
expect(normalizeDirectory("/")).toBe("");
});

test("handles path with only slashes", () => {
expect(normalizeDirectory("///")).toBe("");
});
});
22 changes: 22 additions & 0 deletions packages/mcp-server/src/shared/normalizePath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Normalizes a directory path by removing trailing slashes.
* This prevents double slashes when constructing API paths.
*
* @param directory - The directory path to normalize (can be undefined)
* @returns The normalized directory path without trailing slashes, or undefined if input is undefined
*
* @example
* normalizeDirectory("DevOps/") // "DevOps"
* normalizeDirectory("DevOps") // "DevOps"
* normalizeDirectory("path/to/dir///") // "path/to/dir"
* normalizeDirectory(undefined) // undefined
* normalizeDirectory("") // ""
*/
export function normalizeDirectory(directory?: string): string | undefined {
if (directory === undefined) {
return undefined;
}

// Remove all trailing slashes
return directory.replace(/\/+$/, "");
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fsp from "fs/promises";
import { Plugin } from "obsidian";
import { Plugin, type McpToolsPluginSettings } from "obsidian";
import os from "os";
import path from "path";
import { logger } from "$/shared/logger";
Expand Down Expand Up @@ -77,17 +77,36 @@ export async function updateClaudeConfig(
// File doesn't exist, use default empty config
}

// Get plugin settings for custom configuration
const settings = (plugin as any).settings as McpToolsPluginSettings;

// Determine command to use (custom command or default server path)
const command = settings?.customCommand || serverPath;

// Build environment variables
const env: Record<string, string | undefined> = {
OBSIDIAN_API_KEY: apiKey,
};

// Add custom host if specified
if (settings?.customHost) {
env.OBSIDIAN_HOST = settings.customHost;
}

// Merge any custom environment variables
if (settings?.customEnvVars) {
Object.assign(env, settings.customEnvVars);
}

// Update config with our server entry
config.mcpServers["obsidian-mcp-tools"] = {
command: serverPath,
env: {
OBSIDIAN_API_KEY: apiKey,
},
command,
env,
};

// Write updated config
await fsp.writeFile(configPath, JSON.stringify(config, null, 2));
logger.info("Updated Claude config", { configPath });
logger.info("Updated Claude config", { configPath, command });
} catch (error) {
logger.error("Failed to update Claude config:", { error });
throw new Error(
Expand Down
Loading