MCP Tools for Obsidian

import type { App } from "obsidian"; import { getAPI, LocalRestApiPublicApi } from "obsidian-local-rest-api"; import { distinct, interval, map, merge, scan, startWith, takeUntil, takeWhile, timer, } from "rxjs"; import type { SmartConnections, Templater } from "shared"; import type McpToolsPlugin from "src/main"; export interface Dependency<ID extends keyof App["plugins"]["plugins"], API> { id: keyof Dependencies; name: string; required: boolean; installed: boolean; url?: string; api?: API; plugin?: App["plugins"]["plugins"][ID]; } export interface Dependencies { "obsidian-local-rest-api": Dependency< "obsidian-local-rest-api", LocalRestApiPublicApi >; "smart-connections": Dependency< "smart-connections", SmartConnections.SmartSearch >; "templater-obsidian": Dependency<"templater-obsidian", Templater.ITemplater>; } // The Smart Connections plugin exposes a global variable `window.SmartSearch` but it's not guaranteed to be available. declare const window: { SmartSearch?: SmartConnections.SmartSearch; } & Window; export const loadSmartSearchAPI = (plugin: McpToolsPlugin) => interval(200).pipe( takeUntil(timer(5000)), map((): Dependencies["smart-connections"] => { const api = window.SmartSearch; return { id: "smart-connections", name: "Smart Connections", required: false, installed: !!api, api, plugin: plugin.app.plugins.plugins["smart-connections"], }; }), takeWhile((dependency) => !dependency.installed, true), distinct(({ installed }) => installed), ); export const loadLocalRestAPI = (plugin: McpToolsPlugin) => interval(200).pipe( takeUntil(timer(5000)), map((): Dependencies["obsidian-local-rest-api"] => { const api = getAPI(plugin.app, plugin.manifest); return { id: "obsidian-local-rest-api", name: "Local REST API", required: true, installed: !!api, api, plugin: plugin.app.plugins.plugins["obsidian-local-rest-api"], }; }), takeWhile((dependency) => !dependency.installed, true), distinct(({ installed }) => installed), ); export const loadTemplaterAPI = (plugin: McpToolsPlugin) => interval(200).pipe( takeUntil(timer(5000)), map((): Dependencies["templater-obsidian"] => { const api = plugin.app.plugins.plugins["templater-obsidian"]?.templater; return { id: "templater-obsidian", name: "Templater", required: false, installed: !!api, api, plugin: plugin.app.plugins.plugins["templater-obsidian"], }; }), takeWhile((dependency) => !dependency.installed, true), distinct(({ installed }) => installed), ); export const loadDependencies = (plugin: McpToolsPlugin) => { const dependencies: Dependencies = { "obsidian-local-rest-api": { id: "obsidian-local-rest-api", name: "Local REST API", required: true, installed: false, url: "https://github.com/coddingtonbear/obsidian-local-rest-api", }, "smart-connections": { id: "smart-connections", name: "Smart Connections", required: false, installed: false, url: "https://smartconnections.app/", }, "templater-obsidian": { id: "templater-obsidian", name: "Templater", required: false, installed: false, url: "https://silentvoid13.github.io/Templater/", }, }; return merge( loadLocalRestAPI(plugin), loadTemplaterAPI(plugin), loadSmartSearchAPI(plugin), ).pipe( scan((acc, dependency) => { // @ts-expect-error Dynamic key assignment acc[dependency.id] = { ...dependencies[dependency.id], ...dependency, }; return acc; }, dependencies), startWith(dependencies), ); }; export const loadDependenciesArray = (plugin: McpToolsPlugin) => loadDependencies(plugin).pipe( map((deps) => Object.values(deps) as Dependencies[keyof Dependencies][]), ); export * from "./logger";