Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c62e256331 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -10,6 +10,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.1.2] - 2025-11-15
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Code Review Issues**: Addressed all issues from Obsidian plugin submission review
|
||||||
|
- **Type Safety**: Added eslint-disable comments with justifications for all necessary `any` types in JSON-RPC tool argument handling
|
||||||
|
- **Command IDs**: Removed redundant plugin name prefix from command identifiers (BREAKING CHANGE):
|
||||||
|
- `start-mcp-server` → `start-server`
|
||||||
|
- `stop-mcp-server` → `stop-server`
|
||||||
|
- `restart-mcp-server` → `restart-server`
|
||||||
|
- **Promise Handling**: Added `void` operator for intentional fire-and-forget promise in notification queue processing
|
||||||
|
- **ESLint Directives**: Added descriptive explanations to all eslint-disable comments
|
||||||
|
- **Switch Statement Scope**: Wrapped case blocks in braces to fix lexical declaration warnings in glob pattern matcher
|
||||||
|
- **Regular Expression**: Added eslint-disable comment for control character validation in Windows path checking
|
||||||
|
- **Type Definitions**: Changed empty object type `{}` to `object` in MCP capabilities interface
|
||||||
|
- **Import Statements**: Added comprehensive justifications for `require()` usage in Electron/Node.js modules (synchronous access required)
|
||||||
|
- **Code Cleanup**: Removed unused imports (`MCPPluginSettings`, `TFile`, `VaultInfo`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Command IDs simplified to remove redundant plugin identifier (may affect users with custom hotkeys)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Enhanced inline code documentation for ESLint suppressions and require() statements
|
||||||
|
- Added detailed rationale for synchronous module loading requirements in Obsidian plugin context
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.1.1] - 2025-11-07
|
## [1.1.1] - 2025-11-07
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "mcp-server",
|
"id": "mcp-server",
|
||||||
"name": "MCP Server",
|
"name": "MCP Server",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
|
"description": "Exposes vault operations via Model Context Protocol (MCP) over HTTP.",
|
||||||
"author": "William Ballou",
|
"author": "William Ballou",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-server",
|
"name": "mcp-server",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
|
"description": "MCP (Model Context Protocol) server plugin - exposes vault operations via HTTP",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'start-mcp-server',
|
id: 'start-server',
|
||||||
name: 'Start server',
|
name: 'Start server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.startServer();
|
await this.startServer();
|
||||||
@@ -60,7 +60,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'stop-mcp-server',
|
id: 'stop-server',
|
||||||
name: 'Stop server',
|
name: 'Stop server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.stopServer();
|
await this.stopServer();
|
||||||
@@ -68,7 +68,7 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'restart-mcp-server',
|
id: 'restart-server',
|
||||||
name: 'Restart server',
|
name: 'Restart server',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.stopServer();
|
await this.stopServer();
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ export class MCPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleCallTool(params: JSONRPCParams): Promise<CallToolResult> {
|
private async handleCallTool(params: JSONRPCParams): Promise<CallToolResult> {
|
||||||
const paramsObj = params as { name: string; arguments: Record<string, unknown> };
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and need runtime validation
|
||||||
|
const paramsObj = params as { name: string; arguments: any };
|
||||||
return await this.toolRegistry.callTool(paramsObj.name, paramsObj.arguments);
|
return await this.toolRegistry.callTool(paramsObj.name, paramsObj.arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
|
import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
|
||||||
import { MCPPluginSettings } from './types/settings-types';
|
|
||||||
import MCPServerPlugin from './main';
|
import MCPServerPlugin from './main';
|
||||||
import { generateApiKey } from './utils/auth-utils';
|
import { generateApiKey } from './utils/auth-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ export class ToolRegistry {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and require runtime validation
|
||||||
async callTool(name: string, args: any): Promise<CallToolResult> {
|
async callTool(name: string, args: any): Promise<CallToolResult> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { App, TFile } from 'obsidian';
|
import { App } from 'obsidian';
|
||||||
import {
|
import {
|
||||||
CallToolResult,
|
CallToolResult,
|
||||||
ParsedNote,
|
ParsedNote,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TFile, TFolder } from 'obsidian';
|
import { TFile, TFolder } from 'obsidian';
|
||||||
import { CallToolResult, FileMetadata, DirectoryMetadata, VaultInfo, SearchResult, SearchMatch, StatResult, ExistsResult, ListResult, FileMetadataWithFrontmatter, FrontmatterSummary, WaypointSearchResult, FolderWaypointResult, FolderNoteResult, ValidateWikilinksResult, ResolveWikilinkResult, BacklinksResult } from '../types/mcp-types';
|
import { CallToolResult, FileMetadata, DirectoryMetadata, SearchResult, SearchMatch, StatResult, ExistsResult, ListResult, FileMetadataWithFrontmatter, FrontmatterSummary, WaypointSearchResult, FolderWaypointResult, FolderNoteResult, ValidateWikilinksResult, ResolveWikilinkResult, BacklinksResult } from '../types/mcp-types';
|
||||||
import { PathUtils } from '../utils/path-utils';
|
import { PathUtils } from '../utils/path-utils';
|
||||||
import { ErrorMessages } from '../utils/error-messages';
|
import { ErrorMessages } from '../utils/error-messages';
|
||||||
import { GlobUtils } from '../utils/glob-utils';
|
import { GlobUtils } from '../utils/glob-utils';
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ export type JSONValue =
|
|||||||
*/
|
*/
|
||||||
export type JSONRPCParams = { [key: string]: JSONValue } | JSONValue[];
|
export type JSONRPCParams = { [key: string]: JSONValue } | JSONValue[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool arguments are always objects (not arrays)
|
||||||
|
*/
|
||||||
|
export type ToolArguments = { [key: string]: JSONValue };
|
||||||
|
|
||||||
export interface JSONRPCRequest {
|
export interface JSONRPCRequest {
|
||||||
jsonrpc: "2.0";
|
jsonrpc: "2.0";
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
@@ -47,7 +52,7 @@ export enum ErrorCodes {
|
|||||||
export interface InitializeResult {
|
export interface InitializeResult {
|
||||||
protocolVersion: string;
|
protocolVersion: string;
|
||||||
capabilities: {
|
capabilities: {
|
||||||
tools?: {};
|
tools?: object;
|
||||||
};
|
};
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { MCPPluginSettings } from '../types/settings-types';
|
|||||||
export interface NotificationHistoryEntry {
|
export interface NotificationHistoryEntry {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
|
||||||
args: any;
|
args: any;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
@@ -75,7 +75,7 @@ export class NotificationManager {
|
|||||||
/**
|
/**
|
||||||
* Show notification for tool call start
|
* Show notification for tool call start
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
|
||||||
showToolCall(toolName: string, args: any, duration?: number): void {
|
showToolCall(toolName: string, args: any, duration?: number): void {
|
||||||
if (!this.shouldShowNotification()) {
|
if (!this.shouldShowNotification()) {
|
||||||
return;
|
return;
|
||||||
@@ -142,7 +142,7 @@ export class NotificationManager {
|
|||||||
/**
|
/**
|
||||||
* Format arguments for display
|
* Format arguments for display
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
|
||||||
private formatArgs(args: any): string {
|
private formatArgs(args: any): string {
|
||||||
if (!this.settings.showParameters) {
|
if (!this.settings.showParameters) {
|
||||||
return '';
|
return '';
|
||||||
@@ -196,9 +196,9 @@ export class NotificationManager {
|
|||||||
*/
|
*/
|
||||||
private queueNotification(notificationFn: () => void): void {
|
private queueNotification(notificationFn: () => void): void {
|
||||||
this.notificationQueue.push(notificationFn);
|
this.notificationQueue.push(notificationFn);
|
||||||
|
|
||||||
if (!this.isProcessingQueue) {
|
if (!this.isProcessingQueue) {
|
||||||
this.processQueue();
|
void this.processQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ function getCrypto(): Crypto {
|
|||||||
// Node.js environment (15+) - uses Web Crypto API standard
|
// Node.js environment (15+) - uses Web Crypto API standard
|
||||||
if (typeof global !== 'undefined') {
|
if (typeof global !== 'undefined') {
|
||||||
try {
|
try {
|
||||||
// Note: require() is necessary here for synchronous crypto access in Node.js
|
// Using require() is necessary for synchronous crypto access in Obsidian desktop plugins
|
||||||
// This module is loaded conditionally and esbuild will handle this correctly during bundling
|
// ES6 dynamic imports would create race conditions as crypto must be available synchronously
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Synchronous Node.js crypto API access required
|
||||||
const nodeCrypto = require('crypto') as typeof import('crypto');
|
const nodeCrypto = require('crypto') as typeof import('crypto');
|
||||||
if (nodeCrypto?.webcrypto) {
|
if (nodeCrypto?.webcrypto) {
|
||||||
return nodeCrypto.webcrypto as unknown as Crypto;
|
return nodeCrypto.webcrypto as unknown as Crypto;
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ interface ElectronSafeStorage {
|
|||||||
// Safely import safeStorage - may not be available in all environments
|
// Safely import safeStorage - may not be available in all environments
|
||||||
let safeStorage: ElectronSafeStorage | null = null;
|
let safeStorage: ElectronSafeStorage | null = null;
|
||||||
try {
|
try {
|
||||||
// Note: require() is necessary here for synchronous access to Electron's safeStorage
|
// Using require() is necessary for synchronous access to Electron's safeStorage API in Obsidian desktop plugins
|
||||||
// This module is loaded conditionally and may not be available in all environments
|
// ES6 dynamic imports would create race conditions as this module must be available synchronously
|
||||||
// esbuild will handle this correctly during bundling
|
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Synchronous Electron API access required for Obsidian plugin
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const electron = require('electron') as typeof import('electron');
|
const electron = require('electron') as typeof import('electron');
|
||||||
safeStorage = electron.safeStorage || null;
|
safeStorage = electron.safeStorage || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class GlobUtils {
|
|||||||
i++;
|
i++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '[':
|
case '[': {
|
||||||
// Character class
|
// Character class
|
||||||
const closeIdx = pattern.indexOf(']', i);
|
const closeIdx = pattern.indexOf(']', i);
|
||||||
if (closeIdx === -1) {
|
if (closeIdx === -1) {
|
||||||
@@ -57,8 +57,9 @@ export class GlobUtils {
|
|||||||
i = closeIdx + 1;
|
i = closeIdx + 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case '{':
|
|
||||||
|
case '{': {
|
||||||
// Alternatives {a,b,c}
|
// Alternatives {a,b,c}
|
||||||
const closeIdx2 = pattern.indexOf('}', i);
|
const closeIdx2 = pattern.indexOf('}', i);
|
||||||
if (closeIdx2 === -1) {
|
if (closeIdx2 === -1) {
|
||||||
@@ -67,13 +68,14 @@ export class GlobUtils {
|
|||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
const alternatives = pattern.substring(i + 1, closeIdx2).split(',');
|
const alternatives = pattern.substring(i + 1, closeIdx2).split(',');
|
||||||
regexStr += '(' + alternatives.map(alt =>
|
regexStr += '(' + alternatives.map(alt =>
|
||||||
this.escapeRegex(alt)
|
this.escapeRegex(alt)
|
||||||
).join('|') + ')';
|
).join('|') + ')';
|
||||||
i = closeIdx2 + 1;
|
i = closeIdx2 + 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case '/':
|
case '/':
|
||||||
case '.':
|
case '.':
|
||||||
case '(':
|
case '(':
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export class PathUtils {
|
|||||||
|
|
||||||
// Check for invalid characters (Windows restrictions)
|
// Check for invalid characters (Windows restrictions)
|
||||||
// Invalid chars: < > : " | ? * and ASCII control characters (0-31)
|
// Invalid chars: < > : " | ? * and ASCII control characters (0-31)
|
||||||
const invalidChars = /[<>:"|?*\u0000-\u001F]/;
|
// eslint-disable-next-line no-control-regex -- Control characters \x00-\x1F required for Windows path validation
|
||||||
|
const invalidChars = /[<>:"|?*\x00-\x1F]/;
|
||||||
if (invalidChars.test(normalized)) {
|
if (invalidChars.test(normalized)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user