├── .env.example ├── .github └── dependabot.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── biome.jsonc ├── bun.lock ├── package.json ├── src ├── config.ts ├── index.ts ├── obsidian │ ├── helpers.ts │ ├── index.ts │ └── types.ts └── tools.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | OBSIDIAN_API_KEY= 2 | OBSIDIAN_PROTOCOL=http 3 | OBSIDIAN_HOST=localhost 4 | OBSIDIAN_PORT=27123 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bun 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies (bun install) 2 | node_modules 3 | 4 | # output 5 | out 6 | dist 7 | *.tgz 8 | 9 | # code coverage 10 | coverage 11 | *.lcov 12 | 13 | # logs 14 | logs 15 | _.log 16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # caches 26 | .eslintcache 27 | .cache 28 | *.tsbuildinfo 29 | 30 | # IntelliJ based IDEs 31 | .idea 32 | 33 | # Finder (MacOS) folder config 34 | .DS_Store 35 | 36 | .env 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "biomejs.biome", 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "source.organizeImports.biome": "explicit" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Markus Pfundstein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCP server for Obsidian (TypeScript + Bun) 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/%40fazer-ai%2Fmcp-obsidian)](https://www.npmjs.com/package/@fazer-ai/mcp-obsidian) 4 | 5 | > A Model-Context-Protocol (MCP) server that lets Claude (or any MCP-compatible LLM) interact with your Obsidian vault through the [**Local REST API**](https://github.com/coddingtonbear/obsidian-local-rest-api) community plugin – written in **TypeScript** and runnable with **bunx**. 6 | 7 | --- 8 | 9 | ## ✨ Components 10 | 11 | ### Tools 12 | 13 | | Tool name | Description | 14 | |-----------|-------------| 15 | | **obsidian_status** | Returns basic details about the Obsidian Local REST API server and your authentication status | 16 | | **obsidian_delete_active** | Deletes the note that is currently active in the Obsidian UI | 17 | | **obsidian_get_active** | Retrieves the full content of the active note (Markdown or JSON view) | 18 | | **obsidian_patch_active** | Inserts, replaces or prepends content in the active note relative to a heading, block reference, or front-matter field | 19 | | **obsidian_post_active** | Appends Markdown to the end of the active note | 20 | | **obsidian_put_active** | Replaces the entire body of the active note | 21 | | **obsidian_get_commands** | Lists every command available in Obsidian’s command palette | 22 | | **obsidian_execute_command** | Executes a specific Obsidian command by its ID | 23 | | **obsidian_open_file** | Opens the given file inside Obsidian (creates it if missing); optional flag to open in a new leaf | 24 | | **obsidian_delete_periodic** | Deletes the current daily / weekly / monthly / quarterly / yearly note for the requested period | 25 | | **obsidian_get_periodic** | Returns the content of the current periodic note for the requested period | 26 | | **obsidian_patch_periodic** | Inserts / replaces content in a periodic note relative to a heading, block reference, or front-matter field | 27 | | **obsidian_post_periodic** | Appends Markdown to the periodic note (creates it if it doesn’t exist) | 28 | | **obsidian_put_periodic** | Replaces the entire body of a periodic note | 29 | | **obsidian_search_dataview** | Runs a Dataview-DQL query across the vault and returns matching rows | 30 | | **obsidian_search_json_logic** | Runs a JsonLogic query against structured note metadata | 31 | | **obsidian_simple_search** | Performs a plain-text fuzzy search with optional surrounding context | 32 | | **obsidian_list_vault_root** | Lists all files and directories at the **root** of your vault | 33 | | **obsidian_list_vault_directory** | Lists files and directories inside a **specific folder** of the vault | 34 | | **obsidian_delete_file** | Deletes a specific file (or directory) in the vault | 35 | | **obsidian_get_file** | Retrieves the content of a file in the vault (Markdown or JSON view) | 36 | | **obsidian_patch_file** | Inserts / replaces content in an arbitrary file relative to a heading, block reference, or front-matter field | 37 | | **obsidian_post_file** | Appends Markdown to a file (creates it if it doesn’t exist) | 38 | | **obsidian_put_file** | Creates a new file or replaces the entire body of an existing file | 39 | 40 | *See Obsidian's [Local REST API specifications](https://coddingtonbear.github.io/obsidian-local-rest-api) for more details.* 41 | 42 | --- 43 | 44 | ### Example prompts 45 | 46 | ```text 47 | # Summarize the latest “architecture call” note 48 | # (Claude will transparently call list_files_in_vault → get_file_contents) 49 | Get the contents of the last “architecture call” note and summarize them. 50 | 51 | # Find all mentions of Cosmos DB 52 | Search for all files where “Azure CosmosDb” is mentioned and explain the context briefly. 53 | 54 | # Create a summary note 55 | Summarize yesterday’s meeting and save it as “summaries/2025-04-24-meeting.md”. Add a short intro suitable for e-mail. 56 | ``` 57 | 58 | --- 59 | 60 | ## ⚙️ Configuration 61 | 62 | ### Obsidian REST API key 63 | 64 | There are two ways to pass the Obsidian API key to the server: 65 | 66 | 1. **Server config (recommended)** – pass it via the `env` field in your Claude (or other client) MCP-server declaration: 67 | 68 | ```jsonc 69 | // claude_desktop_config.json 70 | { 71 | "mcpServers": { 72 | "@fazer-ai/mcp-obsidian": { 73 | "command": "bunx", 74 | "args": ["@fazer-ai/mcp-obsidian@latest"], 75 | "env": { 76 | "OBSIDIAN_API_KEY": "your-obsidian-api-key" 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | >[!NOTE] 84 | > Use `@fazer-ai/mcp-obsidian@latest` to ensure you always run the most up to date version of the server. 85 | 86 | 2. Alternatively, you can use an **`.env` file**. Place the key in the `.env` you created above. Note it must be placed in the working directory where the MCP server is running. 87 | 88 | 89 | ### Environment variables 90 | 91 | You can use the `.env.example` file as reference to create your own `.env` file. 92 | 93 | ```bash 94 | OBSIDIAN_API_KEY= # Obtain this from the plugin settings in Obsidian 95 | OBSIDIAN_PROTOCOL=http 96 | OBSIDIAN_HOST=localhost 97 | OBSIDIAN_PORT=27123 # Port the Local REST API plugin is bound to 98 | ``` 99 | 100 | --- 101 | 102 | ## 🛠 Development 103 | 104 | ### Running local version on Claude Desktop 105 | 106 | After cloning this repo, you can update Claude's config to run your local version of the server instead of pulling from npm. 107 | This is useful for quickly testing changes before publishing. 108 | 109 | >[!NOTE] 110 | >Keep in mind any changes you make on the code will only take effect after restarting the Claude Desktop app. 111 | 112 | 1. Clone this repo and run `bun install` to install dependencies. 113 | 1. Update your `claude_desktop_config.json` to point to your local version of the server: 114 | 115 | ```jsonc 116 | // claude_desktop_config.json 117 | { 118 | "mcpServers": { 119 | "@fazer-ai/mcp-obsidian": { 120 | "command": "bun", 121 | "args": ["/path/to/repo/src/index.ts"], 122 | "env": { 123 | "OBSIDIAN_API_KEY": "your-obsidian-api-key" 124 | } 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | >[!IMPORTANT] 131 | >Note we use `bun` instead of `bunx` here. 132 | 133 | ### Debugging 134 | 135 | MCP servers talk over **stdio**, so normal debuggers aren’t helpful. 136 | Use the **[MCP Inspector](https://github.com/modelcontextprotocol/inspector)**: 137 | 138 | ```bash 139 | npx @modelcontextprotocol/inspector bun /path/to/repo/src/index.ts 140 | ``` 141 | 142 | Open the URL it prints to step through requests (usually http://localhost:6274), inspect tool calls, and watch logs in real time. 143 | 144 | --- 145 | 146 | ## 📦 Publishing 147 | 148 | 1. Update the version in `package.json`. 149 | 1. Create GitHub release. 150 | 1. Run `bun publish`. 151 | 152 | --- 153 | 154 | ## License 155 | 156 | MIT – see [LICENSE](LICENSE). 157 | -------------------------------------------------------------------------------- /biome.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "formatter": { 4 | "enabled": true, 5 | "indentStyle": "space", 6 | "indentWidth": 2, 7 | "lineEnding": "lf", 8 | "include": ["src/**/*.ts"] 9 | }, 10 | "organizeImports": { 11 | "enabled": true, 12 | "include": ["src/**/*.ts"] 13 | }, 14 | "linter": { 15 | "enabled": true, 16 | "rules": { 17 | "all": true, 18 | "style": { 19 | "useNamingConvention": "off" 20 | }, 21 | "correctness": { 22 | // NOTE: Biome does not recognize aliased imports. 23 | "noUndeclaredDependencies": "off", 24 | "useImportExtensions": "off", 25 | // NOTE: This isn't React... 26 | "useHookAtTopLevel": "off" 27 | }, 28 | "performance": { 29 | "useTopLevelRegex": "off" 30 | }, 31 | "suspicious": { 32 | // NOTE: This is redundant with `noConsole`. 33 | "noConsoleLog": "off" 34 | } 35 | }, 36 | "include": ["src/**/*.ts"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "mcp-obsidian", 6 | "dependencies": { 7 | "@modelcontextprotocol/sdk": "^1.11.4", 8 | }, 9 | "devDependencies": { 10 | "@biomejs/biome": "1.9.4", 11 | "@types/bun": "latest", 12 | }, 13 | "peerDependencies": { 14 | "typescript": "^5", 15 | }, 16 | }, 17 | }, 18 | "packages": { 19 | "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], 20 | 21 | "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], 22 | 23 | "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], 24 | 25 | "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], 26 | 27 | "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], 28 | 29 | "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], 30 | 31 | "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], 32 | 33 | "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], 34 | 35 | "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], 36 | 37 | "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.11.4", "", { "dependencies": { "ajv": "^8.17.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-OTbhe5slIjiOtLxXhKalkKGhIQrwvhgCDs/C2r8kcBTy5HR/g43aDQU0l7r8O0VGbJPTNJvDc7ZdQMdQDJXmbw=="], 38 | 39 | "@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], 40 | 41 | "@types/node": ["@types/node@22.15.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-99S8dWD2DkeE6PBaEDw+In3aar7hdoBvjyJMR6vaKBTzpvR0P00ClzJMOoVrj9D2+Sy/YCwACYHnBTpMhg1UCA=="], 42 | 43 | "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], 44 | 45 | "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], 46 | 47 | "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], 48 | 49 | "bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], 50 | 51 | "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], 52 | 53 | "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 54 | 55 | "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 56 | 57 | "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], 58 | 59 | "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 60 | 61 | "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 62 | 63 | "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], 64 | 65 | "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], 66 | 67 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 68 | 69 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 70 | 71 | "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], 72 | 73 | "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 74 | 75 | "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 76 | 77 | "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], 78 | 79 | "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 80 | 81 | "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 82 | 83 | "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 84 | 85 | "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], 86 | 87 | "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], 88 | 89 | "eventsource": ["eventsource@3.0.6", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA=="], 90 | 91 | "eventsource-parser": ["eventsource-parser@3.0.1", "", {}, "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA=="], 92 | 93 | "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], 94 | 95 | "express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="], 96 | 97 | "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 98 | 99 | "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], 100 | 101 | "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], 102 | 103 | "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], 104 | 105 | "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], 106 | 107 | "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 108 | 109 | "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 110 | 111 | "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 112 | 113 | "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 114 | 115 | "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 116 | 117 | "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 118 | 119 | "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], 120 | 121 | "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], 122 | 123 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 124 | 125 | "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], 126 | 127 | "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], 128 | 129 | "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 130 | 131 | "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 132 | 133 | "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 134 | 135 | "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], 136 | 137 | "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], 138 | 139 | "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 140 | 141 | "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], 142 | 143 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 144 | 145 | "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], 146 | 147 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 148 | 149 | "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], 150 | 151 | "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], 152 | 153 | "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 154 | 155 | "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], 156 | 157 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 158 | 159 | "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], 160 | 161 | "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], 162 | 163 | "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], 164 | 165 | "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], 166 | 167 | "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], 168 | 169 | "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], 170 | 171 | "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], 172 | 173 | "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], 174 | 175 | "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 176 | 177 | "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 178 | 179 | "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], 180 | 181 | "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], 182 | 183 | "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], 184 | 185 | "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 186 | 187 | "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 188 | 189 | "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], 190 | 191 | "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], 192 | 193 | "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], 194 | 195 | "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], 196 | 197 | "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], 198 | 199 | "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 200 | 201 | "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], 202 | 203 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 204 | 205 | "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 206 | 207 | "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], 208 | 209 | "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], 210 | 211 | "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 212 | 213 | "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 214 | 215 | "zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], 216 | 217 | "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fazer-ai/mcp-obsidian", 3 | "version": "1.1.1", 4 | "module": "src/index.ts", 5 | "main": "dist/index.js", 6 | "bin": "dist/index.js", 7 | "type": "module", 8 | "files": [ 9 | "dist" 10 | ], 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/fazer-ai/mcp-obsidian" 15 | }, 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "keywords": [ 20 | "mcp", 21 | "mcp-obsidian", 22 | "modelcontextprotocol", 23 | "claude", 24 | "obsidian", 25 | "fazer-ai" 26 | ], 27 | "scripts": { 28 | "dev": "bun run --watch src/index.ts", 29 | "build": "bun build src/index.ts --target node --outdir=dist", 30 | "lint": "bun biome check", 31 | "format": "bun biome check --write", 32 | "prepublishOnly": "bun run build" 33 | }, 34 | "devDependencies": { 35 | "@biomejs/biome": "1.9.4", 36 | "@types/bun": "latest" 37 | }, 38 | "peerDependencies": { 39 | "typescript": "^5" 40 | }, 41 | "dependencies": { 42 | "@modelcontextprotocol/sdk": "^1.11.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | const { OBSIDIAN_API_KEY, OBSIDIAN_PROTOCOL, OBSIDIAN_HOST, OBSIDIAN_PORT } = 2 | process.env; 3 | 4 | const config = { 5 | obsidian: { 6 | apiKey: OBSIDIAN_API_KEY || "", 7 | protocol: (OBSIDIAN_PROTOCOL || "http") as "http" | "https", 8 | host: OBSIDIAN_HOST || "localhost", 9 | port: Number(OBSIDIAN_PORT) || 27123, 10 | }, 11 | }; 12 | 13 | // biome-ignore lint/style/noDefaultExport: 14 | export default config; 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { registerTools } from "@/tools"; 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { name, version } from "../package.json"; 5 | 6 | const server = new McpServer( 7 | { name, version }, 8 | { 9 | instructions: 10 | "This is a MCP server for Obsidian. It is a simple server that can be used to run commands and get responses from the client running Local REST API community plugin.", 11 | }, 12 | ); 13 | registerTools(server); 14 | 15 | async function main() { 16 | const transport = new StdioServerTransport(); 17 | await server.connect(transport); 18 | 19 | // biome-ignore lint/suspicious/noConsole: 20 | console.error(`Running ${name}@${version} MCP Server on stdio`); 21 | } 22 | 23 | main().catch((error) => { 24 | // biome-ignore lint/suspicious/noConsole: 25 | console.error("Fatal error in main():", error); 26 | process.exit(1); 27 | }); 28 | -------------------------------------------------------------------------------- /src/obsidian/helpers.ts: -------------------------------------------------------------------------------- 1 | export function sanitizeAndEncodePath(path: string): string { 2 | return encodeURIComponent(path.replace(/^\/|\/$/g, "")); 3 | } 4 | -------------------------------------------------------------------------------- /src/obsidian/index.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeAndEncodePath } from "@/obsidian/helpers"; 2 | import type { 3 | CommandsResponse, 4 | ErrorResponse, 5 | ListFilesResponse, 6 | NoteJson, 7 | ObsidianOptions, 8 | PatchActiveOptions, 9 | PatchFileOptions, 10 | PatchPeriodOptions, 11 | Period, 12 | SearchResult, 13 | SimpleSearchResponse, 14 | StatusResponse, 15 | } from "@/obsidian/types"; 16 | 17 | export class Obsidian { 18 | private apiKey: string; 19 | private protocol: "http" | "https"; 20 | private host: string; 21 | private port: number; 22 | 23 | constructor({ apiKey, protocol, host, port }: ObsidianOptions) { 24 | this.apiKey = apiKey; 25 | this.protocol = protocol || "http"; 26 | this.host = host || "localhost"; 27 | this.port = port || 27123; 28 | } 29 | 30 | private get baseUrl() { 31 | return `${this.protocol}://${this.host}:${this.port}`; 32 | } 33 | 34 | private get headers() { 35 | return { 36 | Authorization: `Bearer ${this.apiKey}`, 37 | }; 38 | } 39 | 40 | private async fetch( 41 | path: string, 42 | { 43 | method, 44 | headers, 45 | body, 46 | }: { 47 | method: "GET" | "PATCH" | "POST" | "PUT" | "DELETE"; 48 | headers?: Record; 49 | body?: string; 50 | }, 51 | ): Promise { 52 | // biome-ignore lint/suspicious/noConsole: 53 | console.error(`[${method}] ${this.baseUrl}${path}`); 54 | 55 | const response = await fetch(`${this.baseUrl}${path}`, { 56 | method: method, 57 | headers: { ...this.headers, ...headers }, 58 | body, 59 | }); 60 | 61 | const { ok, statusText, status } = response; 62 | if (!ok) { 63 | let parsedError: ErrorResponse | null = null; 64 | try { 65 | parsedError = (await response.json()) as ErrorResponse; 66 | } catch { 67 | throw new Error(`Error: ${statusText} (${status})`); 68 | } 69 | throw new Error( 70 | `Error: ${parsedError.message} (${parsedError.errorCode})`, 71 | ); 72 | } 73 | 74 | return (await response.json()) as T; 75 | } 76 | 77 | status() { 78 | return this.fetch("/", { method: "GET" }); 79 | } 80 | 81 | deleteActive() { 82 | return this.fetch("/active/", { 83 | method: "DELETE", 84 | }); 85 | } 86 | 87 | getActive() { 88 | return this.fetch("/active/", { 89 | method: "GET", 90 | headers: { Accept: "application/vnd.olrapi.note+json" }, 91 | }); 92 | } 93 | 94 | patchActive({ 95 | operation, 96 | targetType, 97 | target, 98 | content, 99 | trimTargetWhitespace, 100 | targetDelimiter, 101 | contentType, 102 | }: PatchActiveOptions) { 103 | return this.fetch("/active/", { 104 | method: "PATCH", 105 | headers: { 106 | Operation: operation, 107 | "Target-Type": targetType, 108 | Target: target, 109 | ...(trimTargetWhitespace && { "Trim-Target-Whitespace": "true" }), 110 | ...(targetDelimiter && { "Target-Delimiter": targetDelimiter }), 111 | ...(contentType && { "Content-Type": contentType }), 112 | }, 113 | body: content, 114 | }); 115 | } 116 | 117 | postActive({ content }: { content: string }) { 118 | return this.fetch("/active/", { 119 | method: "POST", 120 | headers: { "Content-Type": "text/markdown" }, 121 | body: content, 122 | }); 123 | } 124 | 125 | putActive({ content }: { content: string }) { 126 | return this.fetch("/active/", { 127 | method: "PUT", 128 | headers: { "Content-Type": "text/markdown" }, 129 | body: content, 130 | }); 131 | } 132 | 133 | getCommands() { 134 | return this.fetch("/commands/", { method: "GET" }); 135 | } 136 | 137 | executeCommand({ commandId }: { commandId: string }) { 138 | return this.fetch(`/commands/${encodeURIComponent(commandId)}/`, { 139 | method: "POST", 140 | }); 141 | } 142 | 143 | openFile({ 144 | filename, 145 | newLeaf, 146 | }: { filename: string; newLeaf?: boolean | null }) { 147 | const qs = newLeaf ? "?newLeaf=true" : ""; 148 | return this.fetch(`/open/${sanitizeAndEncodePath(filename)}${qs}`, { 149 | method: "POST", 150 | }); 151 | } 152 | 153 | deletePeriodic({ period }: { period: Period }) { 154 | return this.fetch(`/periodic/${period}/`, { 155 | method: "DELETE", 156 | }); 157 | } 158 | 159 | getPeriodic({ period }: { period: Period }) { 160 | return this.fetch(`/periodic/${period}/`, { 161 | method: "GET", 162 | headers: { Accept: "application/vnd.olrapi.note+json" }, 163 | }); 164 | } 165 | 166 | patchPeriodic({ 167 | period, 168 | operation, 169 | targetType, 170 | target, 171 | content, 172 | trimTargetWhitespace, 173 | targetDelimiter, 174 | contentType, 175 | }: PatchPeriodOptions) { 176 | return this.fetch(`/periodic/${period}/`, { 177 | method: "PATCH", 178 | headers: { 179 | Operation: operation, 180 | "Target-Type": targetType, 181 | Target: target, 182 | ...(trimTargetWhitespace && { "Trim-Target-Whitespace": "true" }), 183 | ...(targetDelimiter && { "Target-Delimiter": targetDelimiter }), 184 | ...(contentType && { "Content-Type": contentType }), 185 | }, 186 | body: content, 187 | }); 188 | } 189 | 190 | postPeriodic({ period, content }: { period: Period; content: string }) { 191 | return this.fetch(`/periodic/${period}/`, { 192 | method: "POST", 193 | headers: { "Content-Type": "text/markdown" }, 194 | body: content, 195 | }); 196 | } 197 | 198 | putPeriodic({ period, content }: { period: Period; content: string }) { 199 | return this.fetch(`/periodic/${period}/`, { 200 | method: "PUT", 201 | headers: { "Content-Type": "text/markdown" }, 202 | body: content, 203 | }); 204 | } 205 | 206 | searchDataview({ query }: { query: string }) { 207 | return this.fetch("/search/", { 208 | method: "POST", 209 | headers: { "Content-Type": "application/vnd.olrapi.dataview.dql+txt" }, 210 | body: query, 211 | }); 212 | } 213 | 214 | searchJsonLogic(logic: unknown) { 215 | return this.fetch("/search/", { 216 | method: "POST", 217 | headers: { "Content-Type": "application/vnd.olrapi.jsonlogic+json" }, 218 | body: JSON.stringify(logic), 219 | }); 220 | } 221 | 222 | simpleSearch({ 223 | query, 224 | contextLength, 225 | }: { query: string; contextLength?: number }) { 226 | const params = new URLSearchParams({ 227 | query, 228 | contextLength: contextLength?.toString() || "100", 229 | }); 230 | return this.fetch(`/search/simple/?${params}`, { 231 | method: "POST", 232 | }); 233 | } 234 | 235 | listVaultRoot() { 236 | return this.fetch("/vault/", { method: "GET" }); 237 | } 238 | 239 | listVaultDirectory({ pathToDirectory }: { pathToDirectory: string }) { 240 | return this.fetch( 241 | `/vault/${sanitizeAndEncodePath(pathToDirectory)}/`, 242 | { method: "GET" }, 243 | ); 244 | } 245 | 246 | deleteFile({ filename }: { filename: string }) { 247 | return this.fetch(`/vault/${sanitizeAndEncodePath(filename)}`, { 248 | method: "DELETE", 249 | }); 250 | } 251 | 252 | getFile({ filename }: { filename: string }) { 253 | return this.fetch( 254 | `/vault/${sanitizeAndEncodePath(filename)}`, 255 | { 256 | method: "GET", 257 | headers: { Accept: "application/vnd.olrapi.note+json" }, 258 | }, 259 | ); 260 | } 261 | 262 | patchFile({ 263 | filename, 264 | operation, 265 | targetType, 266 | target, 267 | content, 268 | trimTargetWhitespace, 269 | targetDelimiter, 270 | contentType, 271 | }: PatchFileOptions) { 272 | return this.fetch(`/vault/${sanitizeAndEncodePath(filename)}`, { 273 | method: "PATCH", 274 | headers: { 275 | Operation: operation, 276 | "Target-Type": targetType, 277 | Target: target, 278 | ...(trimTargetWhitespace && { "Trim-Target-Whitespace": "true" }), 279 | ...(targetDelimiter && { "Target-Delimiter": targetDelimiter }), 280 | ...(contentType && { "Content-Type": contentType }), 281 | }, 282 | body: content, 283 | }); 284 | } 285 | 286 | postFile({ filename, content }: { filename: string; content: string }) { 287 | return this.fetch(`/vault/${sanitizeAndEncodePath(filename)}`, { 288 | method: "POST", 289 | headers: { "Content-Type": "text/markdown" }, 290 | body: content, 291 | }); 292 | } 293 | 294 | putFile({ filename, content }: { filename: string; content: string }) { 295 | return this.fetch(`/vault/${sanitizeAndEncodePath(filename)}`, { 296 | method: "PUT", 297 | headers: { "Content-Type": "text/markdown" }, 298 | body: content, 299 | }); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/obsidian/types.ts: -------------------------------------------------------------------------------- 1 | export interface ObsidianOptions { 2 | apiKey: string; 3 | protocol?: "http" | "https"; 4 | host?: string; 5 | port?: number; 6 | } 7 | 8 | export interface StatusResponse { 9 | status: string; 10 | manifest: { 11 | id: string; 12 | name: string; 13 | version: string; 14 | minAppVersion: string; 15 | description: string; 16 | author: string; 17 | authorUrl: string; 18 | isDesktopOnly: boolean; 19 | dir: string; 20 | }; 21 | versions: { 22 | obsidian: string; 23 | self: string; 24 | }; 25 | service: string; 26 | authenticated: boolean; 27 | certificateInfo: { 28 | validityDays: number; 29 | regenerateRecommended: boolean; 30 | }; 31 | apiExtensions: unknown; 32 | } 33 | 34 | export interface ErrorResponse { 35 | errorCode: number; 36 | message: string; 37 | } 38 | 39 | export interface PatchActiveOptions { 40 | operation: Operation; 41 | targetType: TargetType; 42 | target: string; 43 | content: string; 44 | trimTargetWhitespace?: boolean; 45 | targetDelimiter?: string; 46 | contentType?: string; 47 | } 48 | 49 | export interface ListFilesResponse { 50 | files: string[]; 51 | } 52 | 53 | export interface NoteJson { 54 | content: string; 55 | frontmatter: unknown; 56 | path: string; 57 | stat: { 58 | ctime: number; 59 | mtime: number; 60 | size: number; 61 | }; 62 | tags: string[]; 63 | } 64 | 65 | export interface CommandItem { 66 | id: string; 67 | name: string; 68 | } 69 | 70 | export interface CommandsResponse { 71 | commands: CommandItem[]; 72 | } 73 | 74 | export interface PatchPeriodOptions { 75 | period: Period; 76 | operation: Operation; 77 | targetType: TargetType; 78 | target: string; 79 | content: string; 80 | trimTargetWhitespace?: boolean; 81 | targetDelimiter?: string; 82 | contentType?: string; 83 | } 84 | 85 | export interface SearchResult { 86 | filename: string; 87 | result: string | number | unknown[] | Record | boolean; 88 | } 89 | 90 | export interface SimpleSearchResponse { 91 | filename: string; 92 | matches: { match: { start: number; end: number }; context: string }[]; 93 | score: number; 94 | } 95 | 96 | export interface PatchFileOptions { 97 | filename: string; 98 | operation: Operation; 99 | targetType: TargetType; 100 | target: string; 101 | content: string; 102 | trimTargetWhitespace?: boolean; 103 | targetDelimiter?: string; 104 | contentType?: string; 105 | } 106 | 107 | export type Operation = "append" | "prepend" | "replace"; 108 | export type TargetType = "heading" | "block" | "frontmatter"; 109 | export type Period = "daily" | "weekly" | "monthly" | "quarterly" | "yearly"; 110 | -------------------------------------------------------------------------------- /src/tools.ts: -------------------------------------------------------------------------------- 1 | import config from "@/config"; 2 | import { Obsidian } from "@/obsidian"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { z } from "zod"; 5 | 6 | const obsidian = new Obsidian(config.obsidian); 7 | 8 | export function registerTools(server: McpServer) { 9 | server.tool( 10 | "obsidian_status", 11 | "Returns basic details about the server.", 12 | async () => { 13 | const status = await obsidian.status(); 14 | return { 15 | content: [ 16 | { 17 | type: "text", 18 | text: JSON.stringify(status), 19 | }, 20 | ], 21 | }; 22 | }, 23 | ); 24 | 25 | server.tool( 26 | "obsidian_delete_active", 27 | "Deletes the currently-active file.", 28 | async () => { 29 | await obsidian.deleteActive(); 30 | return { 31 | content: [{ type: "text", text: "OK" }], 32 | }; 33 | }, 34 | ); 35 | 36 | server.tool( 37 | "obsidian_get_active", 38 | "Returns the content of the currently-active file.", 39 | async () => { 40 | const note = await obsidian.getActive(); 41 | return { 42 | content: [{ type: "text", text: JSON.stringify(note) }], 43 | }; 44 | }, 45 | ); 46 | 47 | server.tool( 48 | "obsidian_patch_active", 49 | "Inserts content into the active file relative to a target.", 50 | { 51 | operation: z.enum(["append", "prepend", "replace"]), 52 | targetType: z.enum(["heading", "block", "frontmatter"]), 53 | target: z.string(), 54 | content: z.string(), 55 | trimTargetWhitespace: z.boolean().optional(), 56 | targetDelimiter: z.string().optional(), 57 | contentType: z.string().optional(), 58 | }, 59 | async (args) => { 60 | const res = await obsidian.patchActive(args); 61 | return { 62 | content: [{ type: "text", text: JSON.stringify(res) }], 63 | }; 64 | }, 65 | ); 66 | 67 | server.tool( 68 | "obsidian_post_active", 69 | "Appends content to the active file.", 70 | { content: z.string() }, 71 | async (args) => { 72 | await obsidian.postActive(args); 73 | return { 74 | content: [{ type: "text", text: "OK" }], 75 | }; 76 | }, 77 | ); 78 | 79 | server.tool( 80 | "obsidian_put_active", 81 | "Replaces content of the active file.", 82 | { content: z.string() }, 83 | async (args) => { 84 | await obsidian.putActive(args); 85 | return { 86 | content: [{ type: "text", text: "OK" }], 87 | }; 88 | }, 89 | ); 90 | 91 | server.tool( 92 | "obsidian_get_commands", 93 | "Returns a list of available commands.", 94 | async () => { 95 | const commands = await obsidian.getCommands(); 96 | return { 97 | content: [ 98 | { 99 | type: "text", 100 | text: JSON.stringify(commands), 101 | }, 102 | ], 103 | }; 104 | }, 105 | ); 106 | 107 | server.tool( 108 | "obsidian_execute_command", 109 | "Executes a specified command.", 110 | { commandId: z.string() }, 111 | async (args) => { 112 | await obsidian.executeCommand(args); 113 | return { 114 | content: [{ type: "text", text: "OK" }], 115 | }; 116 | }, 117 | ); 118 | 119 | server.tool( 120 | "obsidian_open_file", 121 | "Opens a file, optionally in a new leaf.", 122 | { filename: z.string(), newLeaf: z.boolean().nullish() }, 123 | async (args) => { 124 | await obsidian.openFile(args); 125 | return { 126 | content: [{ type: "text", text: "OK" }], 127 | }; 128 | }, 129 | ); 130 | 131 | server.tool( 132 | "obsidian_delete_periodic", 133 | "Deletes the periodic note for a given period.", 134 | { 135 | period: z.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]), 136 | }, 137 | async (args) => { 138 | await obsidian.deletePeriodic(args); 139 | return { 140 | content: [{ type: "text", text: "OK" }], 141 | }; 142 | }, 143 | ); 144 | 145 | server.tool( 146 | "obsidian_get_periodic", 147 | "Returns the periodic note for a given period.", 148 | { 149 | period: z.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]), 150 | }, 151 | async (args) => { 152 | const note = await obsidian.getPeriodic(args); 153 | return { 154 | content: [{ type: "text", text: JSON.stringify(note) }], 155 | }; 156 | }, 157 | ); 158 | 159 | server.tool( 160 | "obsidian_patch_periodic", 161 | "Inserts content into a periodic note relative to a target.", 162 | { 163 | period: z.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]), 164 | operation: z.enum(["append", "prepend", "replace"]), 165 | targetType: z.enum(["heading", "block", "frontmatter"]), 166 | target: z.string(), 167 | content: z.string(), 168 | trimTargetWhitespace: z.boolean().optional(), 169 | targetDelimiter: z.string().optional(), 170 | contentType: z.string().optional(), 171 | }, 172 | async (args) => { 173 | await obsidian.patchPeriodic(args); 174 | return { 175 | content: [{ type: "text", text: "OK" }], 176 | }; 177 | }, 178 | ); 179 | 180 | server.tool( 181 | "obsidian_post_periodic", 182 | "Appends content to the periodic note.", 183 | { 184 | period: z.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]), 185 | content: z.string(), 186 | }, 187 | async (args) => { 188 | await obsidian.postPeriodic(args); 189 | return { 190 | content: [{ type: "text", text: "OK" }], 191 | }; 192 | }, 193 | ); 194 | 195 | server.tool( 196 | "obsidian_put_periodic", 197 | "Replaces content of the periodic note.", 198 | { 199 | period: z.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]), 200 | content: z.string(), 201 | }, 202 | async (args) => { 203 | await obsidian.putPeriodic(args); 204 | return { 205 | content: [{ type: "text", text: "OK" }], 206 | }; 207 | }, 208 | ); 209 | 210 | server.tool( 211 | "obsidian_search_dataview", 212 | "Searches using a Dataview query.", 213 | { query: z.string() }, 214 | async (args) => { 215 | const results = await obsidian.searchDataview(args); 216 | return { 217 | content: [{ type: "text", text: JSON.stringify(results) }], 218 | }; 219 | }, 220 | ); 221 | 222 | server.tool( 223 | "obsidian_search_json_logic", 224 | "Searches using a JsonLogic query.", 225 | { logic: z.unknown() }, 226 | async ({ logic }) => { 227 | const results = await obsidian.searchJsonLogic(logic); 228 | return { 229 | content: [{ type: "text", text: JSON.stringify(results) }], 230 | }; 231 | }, 232 | ); 233 | 234 | server.tool( 235 | "obsidian_simple_search", 236 | "Searches for text in vault with optional context.", 237 | { query: z.string(), contextLength: z.number().optional() }, 238 | async (args) => { 239 | const results = await obsidian.simpleSearch(args); 240 | return { 241 | content: [{ type: "text", text: JSON.stringify(results) }], 242 | }; 243 | }, 244 | ); 245 | 246 | server.tool( 247 | "obsidian_list_vault_root", 248 | "Lists files in the root of the vault.", 249 | async () => { 250 | const files = await obsidian.listVaultRoot(); 251 | return { 252 | content: [{ type: "text", text: JSON.stringify(files) }], 253 | }; 254 | }, 255 | ); 256 | 257 | server.tool( 258 | "obsidian_list_vault_directory", 259 | "Lists files in a specified directory.", 260 | { pathToDirectory: z.string() }, 261 | async (args) => { 262 | const files = await obsidian.listVaultDirectory(args); 263 | return { 264 | content: [{ type: "text", text: JSON.stringify(files) }], 265 | }; 266 | }, 267 | ); 268 | 269 | server.tool( 270 | "obsidian_delete_file", 271 | "Deletes a file in the vault.", 272 | { filename: z.string() }, 273 | async (args) => { 274 | await obsidian.deleteFile(args); 275 | return { 276 | content: [{ type: "text", text: "OK" }], 277 | }; 278 | }, 279 | ); 280 | 281 | server.tool( 282 | "obsidian_get_file", 283 | "Returns content of a vault file.", 284 | { filename: z.string() }, 285 | async (args) => { 286 | const file = await obsidian.getFile(args); 287 | return { 288 | content: [{ type: "text", text: JSON.stringify(file) }], 289 | }; 290 | }, 291 | ); 292 | 293 | server.tool( 294 | "obsidian_patch_file", 295 | "Inserts content into a vault file relative to a target.", 296 | { 297 | filename: z.string(), 298 | operation: z.enum(["append", "prepend", "replace"]), 299 | targetType: z.enum(["heading", "block", "frontmatter"]), 300 | target: z.string(), 301 | content: z.string(), 302 | trimTargetWhitespace: z.boolean().optional(), 303 | targetDelimiter: z.string().optional(), 304 | contentType: z.string().optional(), 305 | }, 306 | async (args) => { 307 | await obsidian.patchFile(args); 308 | return { 309 | content: [{ type: "text", text: "OK" }], 310 | }; 311 | }, 312 | ); 313 | 314 | server.tool( 315 | "obsidian_post_file", 316 | "Appends content to a vault file.", 317 | { filename: z.string(), content: z.string() }, 318 | async (args) => { 319 | await obsidian.postFile(args); 320 | return { 321 | content: [{ type: "text", text: "OK" }], 322 | }; 323 | }, 324 | ); 325 | 326 | server.tool( 327 | "obsidian_put_file", 328 | "Creates or replaces a vault file.", 329 | { filename: z.string(), content: z.string() }, 330 | async (args) => { 331 | await obsidian.putFile(args); 332 | return { 333 | content: [{ type: "text", text: "OK" }], 334 | }; 335 | }, 336 | ); 337 | } 338 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | 23 | // Some stricter flags (disabled by default) 24 | "noUnusedLocals": false, 25 | "noUnusedParameters": false, 26 | "noPropertyAccessFromIndexSignature": false, 27 | "baseUrl": ".", 28 | "paths": { 29 | "@/*": ["src/*"] 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------