obsidian-mcp-server 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +18 -12
- package/README.md +10 -10
- package/changelog/3.1.x/3.1.0.md +26 -0
- package/changelog/3.1.x/3.1.1.md +21 -0
- package/dist/config/server-config.js +1 -1
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +124 -11
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/index.js +7 -5
- package/dist/mcp-server/tools/definitions/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +14 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +11 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +11 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +11 -1
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +8 -4
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +10 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +11 -1
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +3 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +7 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +9 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +34 -4
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
- package/dist/services/obsidian/obsidian-service.d.ts +10 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
- package/dist/services/obsidian/obsidian-service.js +60 -17
- package/dist/services/obsidian/obsidian-service.js.map +1 -1
- package/package.json +2 -4
- package/server.json +5 -5
package/CLAUDE.md
CHANGED
|
@@ -34,7 +34,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
|
|
|
34
34
|
- **Check `ctx.elicit`** for presence before calling — used by `obsidian_delete_note` to confirm destructive ops.
|
|
35
35
|
- **All Obsidian access goes through `getObsidianService()`.** No direct `fetch()` calls to the Local REST API in tools/resources — the service centralizes auth, TLS, timeouts, and `ctx.signal` propagation.
|
|
36
36
|
- **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
|
|
37
|
-
-
|
|
37
|
+
- **Command-palette tools are opt-in.** `obsidian_list_commands` and `obsidian_execute_command` are registered together only when `OBSIDIAN_ENABLE_COMMANDS=true` — Obsidian commands are opaque and can be destructive.
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -177,25 +177,33 @@ The framework also provides `ctx.state`, `ctx.sample`, and `ctx.progress`. They
|
|
|
177
177
|
|
|
178
178
|
Handlers throw — the framework catches, classifies, and formats.
|
|
179
179
|
|
|
180
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
180
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated) — it's the single source of truth for the recovery hint that flows to the wire. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (the framework mirrors `data.recovery.hint` into `content[]` text). Override with explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
181
181
|
|
|
182
182
|
```ts
|
|
183
183
|
errors: [
|
|
184
|
-
{ reason: 'note_missing', code: JsonRpcErrorCode.NotFound,
|
|
185
|
-
|
|
184
|
+
{ reason: 'note_missing', code: JsonRpcErrorCode.NotFound,
|
|
185
|
+
when: 'No note matched the path',
|
|
186
|
+
recovery: 'Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.' },
|
|
187
|
+
{ reason: 'plugin_unreachable', code: JsonRpcErrorCode.ServiceUnavailable,
|
|
188
|
+
when: 'Local REST API plugin is offline', retryable: true,
|
|
189
|
+
recovery: 'Confirm Obsidian is running with the Local REST API plugin enabled.' },
|
|
186
190
|
],
|
|
187
191
|
async handler(input, ctx) {
|
|
188
192
|
const note = await svc.getNote(input.path, ctx);
|
|
189
|
-
|
|
193
|
+
// Static recovery — pulled from the contract via ctx.recoveryFor.
|
|
194
|
+
if (!note) throw ctx.fail('note_missing', `Note ${input.path} not found`, {
|
|
195
|
+
...ctx.recoveryFor('note_missing'),
|
|
196
|
+
});
|
|
190
197
|
return note;
|
|
191
198
|
}
|
|
192
199
|
```
|
|
193
200
|
|
|
194
|
-
Services
|
|
201
|
+
Services that accept `ctx` use the same resolver for parity. The Obsidian service threads `ctx` into `#throwForStatus` and spreads `ctx.recoveryFor(reason)` per status branch, so service-side throws carry the calling tool's contract recovery onto the wire:
|
|
195
202
|
|
|
196
203
|
```ts
|
|
197
204
|
// inside obsidian-service.ts
|
|
198
|
-
throw notFound(
|
|
205
|
+
throw notFound(`Not found: ${display}`, data('note_missing'));
|
|
206
|
+
// where data(reason) does: { path, reason, ...ctx.recoveryFor(reason), upstream? }
|
|
199
207
|
```
|
|
200
208
|
|
|
201
209
|
**Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code without a contract): use error factories.
|
|
@@ -228,8 +236,8 @@ src/
|
|
|
228
236
|
mcp-server/
|
|
229
237
|
tools/definitions/
|
|
230
238
|
_shared/schemas.ts # Shared TargetSchema + SectionSchema reused across tools
|
|
231
|
-
index.ts # baseToolDefinitions[] + conditional
|
|
232
|
-
obsidian-*.tool.ts # 14 tool definitions (
|
|
239
|
+
index.ts # baseToolDefinitions[] + conditional commandToolDefinitions[]
|
|
240
|
+
obsidian-*.tool.ts # 14 tool definitions (12 base + 2 opt-in command-palette pair)
|
|
233
241
|
resources/definitions/
|
|
234
242
|
index.ts # allResourceDefinitions[]
|
|
235
243
|
obsidian-vault-note.resource.ts # obsidian://vault/{+path}
|
|
@@ -306,8 +314,6 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
306
314
|
| `bun run format` | Auto-fix formatting (Biome) |
|
|
307
315
|
| `bun run lint:mcp` | Validate MCP definitions against the linter rules |
|
|
308
316
|
| `bun run test` | Run Vitest tests |
|
|
309
|
-
| `bun run dev:stdio` | Dev mode (stdio, watch) |
|
|
310
|
-
| `bun run dev:http` | Dev mode (HTTP, watch) |
|
|
311
317
|
| `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
|
|
312
318
|
| `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
|
|
313
319
|
| `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
|
|
@@ -359,6 +365,6 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
359
365
|
- [ ] If wrapping external API: raw/domain/output schemas reviewed against real upstream sparsity/nullability before finalizing required vs optional fields
|
|
360
366
|
- [ ] If wrapping external API: normalization and `format()` preserve uncertainty; do not fabricate facts from missing upstream data
|
|
361
367
|
- [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
|
|
362
|
-
- [ ] Registered in `createApp()` arrays (directly or via barrel exports). Conditional registration (e.g. `
|
|
368
|
+
- [ ] Registered in `createApp()` arrays (directly or via barrel exports). Conditional registration (e.g. `commandToolDefinitions` behind `OBSIDIAN_ENABLE_COMMANDS`) happens in `src/index.ts`, not in the barrel
|
|
363
369
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
364
370
|
- [ ] `bun run devcheck` passes
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](https://www.npmjs.com/package/obsidian-mcp-server) [](https://www.npmjs.com/package/obsidian-mcp-server) [](./CHANGELOG.md) [](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [](https://modelcontextprotocol.io/)
|
|
11
11
|
|
|
12
12
|
[](./LICENSE) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
13
13
|
|
|
@@ -24,9 +24,9 @@ Fourteen tools grouped by shape — readers fetch notes and metadata, writers cr
|
|
|
24
24
|
| `obsidian_get_note` | Read a note as raw content, full structured form (content + frontmatter + tags + stat), structural document map, or a single section. |
|
|
25
25
|
| `obsidian_list_notes` | List notes and subdirectories at a vault path with a recursive walk (default depth 2 — structural overview; max 20) bounded by a 1000-entry cap. Optional `extension` and `nameRegex` filters apply across the tree; regex-filtered directories are skipped without recursing into them. Returns flat `entries[]` plus a box-drawing tree in the rendered output; per-directory `truncated: true` flags where the depth limit cut off recursion. |
|
|
26
26
|
| `obsidian_list_tags` | List every tag found across the vault with usage counts, including hierarchical parents. |
|
|
27
|
-
| `obsidian_list_commands` | List Obsidian command-palette commands available for execution. |
|
|
27
|
+
| `obsidian_list_commands` | List Obsidian command-palette commands available for execution. **Opt-in via `OBSIDIAN_ENABLE_COMMANDS=true`** (paired with `obsidian_execute_command`). |
|
|
28
28
|
| `obsidian_search_notes` | Search the vault by text, Dataview DQL, or JSONLogic — capped at 100 hits with overflow indicator. |
|
|
29
|
-
| `obsidian_write_note` | Create
|
|
29
|
+
| `obsidian_write_note` | Create a note, replace a single section in place, or — with `overwrite: true` — clobber an existing file. Refuses whole-file writes against an existing path by default. |
|
|
30
30
|
| `obsidian_append_to_note` | Append content to a note, or to a specific heading/block/frontmatter section. |
|
|
31
31
|
| `obsidian_patch_note` | Surgical `append` / `prepend` / `replace` against a heading, block reference, or frontmatter field. |
|
|
32
32
|
| `obsidian_replace_in_note` | Body-wide search-replace inside a single note. Literal or regex matching, with `wholeWord`, `flexibleWhitespace`, `caseSensitive`, `replaceAll`, and `$1`/`$&` capture groups. |
|
|
@@ -63,12 +63,12 @@ Results are capped at 100 hits. When the upstream returns more, an `excluded` in
|
|
|
63
63
|
|
|
64
64
|
### `obsidian_write_note`
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Create or surgically replace, with a protective default against accidental whole-file overwrites.
|
|
67
67
|
|
|
68
|
-
- Without `section` — full-file `PUT
|
|
69
|
-
- With `section` — `PATCH`-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched
|
|
68
|
+
- Without `section` — full-file `PUT`. **Refuses to clobber an existing file** unless `overwrite: true` is set. The `file_exists` (`Conflict`) error suggests `obsidian_patch_note` / `obsidian_append_to_note` / `obsidian_replace_in_note` for in-place edits.
|
|
69
|
+
- With `section` — `PATCH`-with-replace against the named heading/block/frontmatter field, leaving the rest of the file untouched. The `overwrite` flag is ignored in section mode.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
The output reports `created: true` when the call brought a new file into existence; `false` when it replaced an existing one or targeted a section.
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
@@ -122,7 +122,7 @@ Permanently delete a note. When the client supports `elicit`, the server request
|
|
|
122
122
|
|
|
123
123
|
Dispatch an Obsidian command-palette command by ID (discoverable via `obsidian_list_commands`). Behavior is command-dependent — some commands open UI, others delete files or close the vault.
|
|
124
124
|
|
|
125
|
-
**Off by default.**
|
|
125
|
+
**Off by default.** Both `obsidian_execute_command` and its discovery partner `obsidian_list_commands` register only when the operator sets `OBSIDIAN_ENABLE_COMMANDS=true`; both are omitted from the surface otherwise.
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
@@ -155,7 +155,7 @@ Obsidian-specific:
|
|
|
155
155
|
- Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping fenced code blocks)
|
|
156
156
|
- Search across three modes: text, Dataview DQL, JSONLogic — with overflow indicator when results exceed the 100-hit cap
|
|
157
157
|
- Optional human-in-the-loop confirmation for destructive deletes via `ctx.elicit`
|
|
158
|
-
- Opt-in `
|
|
158
|
+
- Opt-in command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`) — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`
|
|
159
159
|
- Forgiving path resolution on `obsidian_get_note` and `obsidian_open_in_ui` — silently retries case-mismatched paths against the canonical filename, throws `Conflict` on ambiguous case matches, and enriches `NotFound` with `Did you mean: …?` suggestions when only near-matches exist. `obsidian_delete_note` is deliberately excluded — a destructive op shouldn't silently rewrite the target path.
|
|
160
160
|
|
|
161
161
|
## Getting started
|
|
@@ -246,7 +246,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
246
246
|
| `OBSIDIAN_BASE_URL` | Base URL of the Local REST API plugin. Use `https://127.0.0.1:27124` for the always-on HTTPS port (self-signed cert). | `http://127.0.0.1:27123` |
|
|
247
247
|
| `OBSIDIAN_VERIFY_SSL` | Verify the TLS certificate. Default `false` because the plugin uses a self-signed cert. On Node, the dispatcher's `rejectUnauthorized` option handles this without any process-wide change. On Bun, the runtime ignores that option, so the service additionally sets `NODE_TLS_REJECT_UNAUTHORIZED=0` — that fallback is scoped to Bun only. | `false` |
|
|
248
248
|
| `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
|
|
249
|
-
| `OBSIDIAN_ENABLE_COMMANDS` | Opt-in flag for `obsidian_execute_command
|
|
249
|
+
| `OBSIDIAN_ENABLE_COMMANDS` | Opt-in flag for the command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`). Off by default — Obsidian commands are opaque and can be destructive. | `false` |
|
|
250
250
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
251
251
|
| `MCP_HTTP_HOST` | Host for the HTTP server. | `127.0.0.1` |
|
|
252
252
|
| `MCP_HTTP_PORT` | Port for the HTTP server. | `3010` |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: obsidian_write_note refuses to clobber existing notes by default — opt in with overwrite:true; obsidian_list_commands moves behind OBSIDIAN_ENABLE_COMMANDS alongside obsidian_execute_command.
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 3.1.0 — 2026-04-29
|
|
7
|
+
|
|
8
|
+
A safety pass on whole-file writes plus a tighter command-palette gate. Service-layer error classification picks up the framework's `httpErrorFromResponse` for the 4xx/5xx fallback so the canonical status → code mapping (and `Retry-After` capture) replaces the hand-rolled ladder.
|
|
9
|
+
|
|
10
|
+
## Added
|
|
11
|
+
|
|
12
|
+
- **`obsidian_write_note` `overwrite` flag (default `false`).** Whole-file writes against an existing note now fail with `file_exists` (`Conflict`) and an actionable message pointing at `obsidian_patch_note` / `obsidian_append_to_note` / `obsidian_replace_in_note` for in-place edits, or `overwrite: true` for a deliberate full replacement. Section-targeted writes are unaffected — the flag is ignored when `section` is set.
|
|
13
|
+
- **`obsidian_write_note` `created` field in the output.** `true` when the call brought a new file into existence; `false` when it replaced an existing one or targeted a section. Surfaces in both `structuredContent` and the rendered `content[]` twin.
|
|
14
|
+
- **Typed error contract on `obsidian_write_note`.** Declares `file_exists` so clients can switch on `error.data.reason` instead of parsing message text.
|
|
15
|
+
- **`ObsidianService.noteExists()`** — HEAD probe that returns `true` on 2xx, `false` on 404, and surfaces other statuses through the normal error classifier so a 401 doesn't masquerade as a missing file. Bypasses `withRetry` — a HEAD probe shouldn't retry on 404.
|
|
16
|
+
|
|
17
|
+
## Changed
|
|
18
|
+
|
|
19
|
+
- **`obsidian_list_commands` is now opt-in alongside `obsidian_execute_command`.** Both register only when `OBSIDIAN_ENABLE_COMMANDS=true`; both are hidden otherwise. Previously `obsidian_list_commands` was always-on and only execution was gated, which advertised a discovery surface for capabilities the operator had explicitly disabled. The pair now travels together as `commandToolDefinitions`.
|
|
20
|
+
- **Service-layer 4xx/5xx fallback routes through `httpErrorFromResponse`.** The default branch in `ObsidianService.#throwForStatus` delegates to the framework helper for unhandled statuses so the canonical mapping (`500/501 → InternalError`, `502/503 → ServiceUnavailable`, `504 → Timeout`) and `Retry-After` capture replace the local `serviceUnavailable` ladder. The body is consumed before the helper runs, so the call passes `captureBody: false` and forwards a truncated copy via `data.body`. Wire-visible: 500s now land as `InternalError` (`-32603`) instead of `ServiceUnavailable` (`-32004`).
|
|
21
|
+
- **Dependency bump:** `@cyanheads/mcp-ts-core` 0.8.1 → 0.8.2.
|
|
22
|
+
- **`skills/maintenance/SKILL.md` v1.9 → v2.0.** Step 6's framework-adoption rule moved from "default adopt" to "auto-adopt every applicable site, in this pass." Documents an explicit asymmetry against third-party adoptions, a hard rule against scope/effort/marginal-benefit deferrals, and a valid-vs-invalid deferral table. Step 8's "Open decisions" section is reframed accordingly — empty is now the expected outcome of a clean framework upgrade.
|
|
23
|
+
|
|
24
|
+
## Fixed
|
|
25
|
+
|
|
26
|
+
- **`obsidian_list_commands` description** no longer claims it is always available — calls out the `OBSIDIAN_ENABLE_COMMANDS` gate.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: Adopt the mcp-ts-core 0.8.6 recovery-hint contract — every error declares a recovery, ObsidianService threads it onto the wire, and a new periodic_disabled reason distinguishes a disabled period from a missing periodic note.
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 3.1.1 — 2026-04-29
|
|
7
|
+
|
|
8
|
+
A propagation pass for the typed-error contract upgrade in `@cyanheads/mcp-ts-core` 0.8.6. Every tool's `errors[]` now declares a `recovery` hint, the Obsidian service threads `ctx` through its status classifier so service-side throws inherit the calling tool's recovery, and `obsidian_open_in_ui` / `obsidian_write_note` lift long remediation guidance out of the error message into structured `data.recovery.hint`.
|
|
9
|
+
|
|
10
|
+
## Added
|
|
11
|
+
|
|
12
|
+
- **`periodic_disabled` error reason** on every tool that resolves a `periodic` target (`obsidian_get_note`, `obsidian_patch_note`, `obsidian_replace_in_note`, `obsidian_manage_frontmatter`, `obsidian_manage_tags`, `obsidian_delete_note`). Fired when the upstream Local REST API returns a 400 with `"Specified period is not enabled"` — the requested period (`daily` / `weekly` / `monthly` / `quarterly` / `yearly`) is turned off in Obsidian's Periodic Notes plugin settings. Distinct from `periodic_not_found` (404, the period is enabled but no note exists for the requested date), so clients can suggest enabling the period rather than creating a note.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **Every error contract entry now carries a `recovery` hint.** Required descriptive metadata under `mcp-ts-core` 0.8.6 — lint enforces it. Recovery flows to the wire via `ctx.recoveryFor(reason)` (spread into the `ctx.fail(...)` data bag) and the framework mirrors `data.recovery.hint` into the rendered `content[]` text, so clients that render structured error data and clients that parse text both see the same actionable next step.
|
|
17
|
+
- **`obsidian_open_in_ui` and `obsidian_write_note` lift long remediation text into `data.recovery.hint`.** The `note_missing` and `file_exists` error messages drop the `Did you mean:` suggestions, the `failIfMissing: false` tip, and the `obsidian_patch_note` / `obsidian_append_to_note` / `obsidian_replace_in_note` referrals — those move into a structured `recovery.hint` field. Wire-visible: clients that read `error.data.recovery.hint` see a cleaner separation of "what happened" from "how to fix it."
|
|
18
|
+
- **`ObsidianService.#throwForStatus` threads `ctx`.** The status classifier now spreads `ctx.recoveryFor(reason)` per branch, so service-side throws (`note_missing`, `no_active_file`, `periodic_not_found`, `periodic_disabled`, `command_unknown`, `path_is_directory`, `section_target_missing`) inherit the calling tool's contract recovery instead of needing per-tool plumbing.
|
|
19
|
+
- **Drop `dev:stdio` / `dev:http` scripts.** `bun --watch src/index.ts` works directly without the wrapper. The CLAUDE.md commands table loses the two duplicates.
|
|
20
|
+
- **Dependency bump:** `@cyanheads/mcp-ts-core` 0.8.2 → 0.8.6.
|
|
21
|
+
- **Skills synced** with framework patterns: `add-tool`, `add-service`, `api-context`, `api-errors`, `design-mcp-server`, `field-test`.
|
|
@@ -35,7 +35,7 @@ const ServerConfigSchema = z.object({
|
|
|
35
35
|
.describe('Per-request timeout in milliseconds.'),
|
|
36
36
|
enableCommands: envBoolean
|
|
37
37
|
.default(false)
|
|
38
|
-
.describe('Opt-in flag for obsidian_execute_command. Off by default — Obsidian commands are opaque and can be destructive.'),
|
|
38
|
+
.describe('Opt-in flag for the command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`). Off by default — Obsidian commands are opaque and can be destructive.'),
|
|
39
39
|
});
|
|
40
40
|
let _config;
|
|
41
41
|
export function getServerConfig() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;IACtC,IAAI,OAAO,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAEhB,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,sGAAsG,CACvG;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,wBAAwB,CAAC;SACjC,QAAQ,CACP,0QAA0Q,CAC3Q;IACH,SAAS,EAAE,UAAU;SAClB,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,qXAAqX,CACtX;IACH,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,sCAAsC,CAAC;IACnD,cAAc,EAAE,UAAU;SACvB,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;IACtC,IAAI,OAAO,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAEhB,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,sGAAsG,CACvG;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,wBAAwB,CAAC;SACjC,QAAQ,CACP,0QAA0Q,CAC3Q;IACH,SAAS,EAAE,UAAU;SAClB,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,qXAAqX,CACtX;IACH,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,sCAAsC,CAAC;IACnD,cAAc,EAAE,UAAU;SACvB,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CACP,yKAAyK,CAC1K;CACJ,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,mBAAmB;QAC5B,SAAS,EAAE,qBAAqB;QAChC,gBAAgB,EAAE,6BAA6B;QAC/C,cAAc,EAAE,0BAA0B;KAC3C,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,GAAG,SAAS,CAAC;AACtB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ import { createApp } from '@cyanheads/mcp-ts-core';
|
|
|
9
9
|
import { getServerConfig } from './config/server-config.js';
|
|
10
10
|
import { allPromptDefinitions } from './mcp-server/prompts/definitions/index.js';
|
|
11
11
|
import { allResourceDefinitions } from './mcp-server/resources/definitions/index.js';
|
|
12
|
-
import { baseToolDefinitions,
|
|
12
|
+
import { baseToolDefinitions, commandToolDefinitions, } from './mcp-server/tools/definitions/index.js';
|
|
13
13
|
import { initObsidianService } from './services/obsidian/obsidian-service.js';
|
|
14
14
|
const config = getServerConfig();
|
|
15
15
|
const tools = config.enableCommands
|
|
16
|
-
? [...baseToolDefinitions,
|
|
16
|
+
? [...baseToolDefinitions, ...commandToolDefinitions]
|
|
17
17
|
: baseToolDefinitions;
|
|
18
18
|
await createApp({
|
|
19
19
|
tools,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EACL,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;AACjC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc;IACjC,CAAC,CAAC,CAAC,GAAG,mBAAmB,EAAE,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EACL,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;AACjC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc;IACjC,CAAC,CAAC,CAAC,GAAG,mBAAmB,EAAE,GAAG,sBAAsB,CAAC;IACrD,CAAC,CAAC,mBAAmB,CAAC;AAExB,MAAM,SAAS,CAAC;IACd,KAAK;IACL,SAAS,EAAE,sBAAsB;IACjC,OAAO,EAAE,oBAAoB;IAC7B,KAAK;QACH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Tool registration barrel.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Tool registration barrel. The command-palette pair
|
|
3
|
+
* (`obsidian_list_commands` + `obsidian_execute_command`) is exported
|
|
4
|
+
* separately so callers can decide whether to register them based on the
|
|
5
|
+
* `OBSIDIAN_ENABLE_COMMANDS` flag — keeping this module free of eager
|
|
6
|
+
* config reads.
|
|
5
7
|
* @module mcp-server/tools/definitions/index
|
|
6
8
|
*/
|
|
7
|
-
import { obsidianExecuteCommand } from './obsidian-execute-command.tool.js';
|
|
8
9
|
/** Tools registered unconditionally on every server. */
|
|
9
10
|
export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
10
11
|
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
@@ -23,6 +24,40 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
23
24
|
}>;
|
|
24
25
|
date: import("zod").ZodOptional<import("zod").ZodString>;
|
|
25
26
|
}, import("zod/v4/core").$strip>], "type">;
|
|
27
|
+
content: import("zod").ZodString;
|
|
28
|
+
section: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
29
|
+
type: import("zod").ZodEnum<{
|
|
30
|
+
heading: "heading";
|
|
31
|
+
block: "block";
|
|
32
|
+
frontmatter: "frontmatter";
|
|
33
|
+
}>;
|
|
34
|
+
target: import("zod").ZodString;
|
|
35
|
+
}, import("zod/v4/core").$strip>>;
|
|
36
|
+
contentType: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
37
|
+
markdown: "markdown";
|
|
38
|
+
json: "json";
|
|
39
|
+
}>>;
|
|
40
|
+
createTargetIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
41
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
42
|
+
path: import("zod").ZodString;
|
|
43
|
+
sectionTargeted: import("zod").ZodBoolean;
|
|
44
|
+
}, import("zod/v4/core").$strip>, undefined> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
45
|
+
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
46
|
+
type: import("zod").ZodLiteral<"path">;
|
|
47
|
+
path: import("zod").ZodString;
|
|
48
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
49
|
+
type: import("zod").ZodLiteral<"active">;
|
|
50
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
51
|
+
type: import("zod").ZodLiteral<"periodic">;
|
|
52
|
+
period: import("zod").ZodEnum<{
|
|
53
|
+
daily: "daily";
|
|
54
|
+
weekly: "weekly";
|
|
55
|
+
monthly: "monthly";
|
|
56
|
+
quarterly: "quarterly";
|
|
57
|
+
yearly: "yearly";
|
|
58
|
+
}>;
|
|
59
|
+
date: import("zod").ZodOptional<import("zod").ZodString>;
|
|
60
|
+
}, import("zod/v4/core").$strip>], "type">;
|
|
26
61
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
27
62
|
path: import("zod").ZodString;
|
|
28
63
|
deleted: import("zod").ZodBoolean;
|
|
@@ -30,18 +65,27 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
30
65
|
readonly reason: "cancelled";
|
|
31
66
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.InvalidRequest;
|
|
32
67
|
readonly when: "User declined the deletion via interactive elicitation.";
|
|
68
|
+
readonly recovery: "Re-run the tool when the user is ready to confirm deletion.";
|
|
33
69
|
}, {
|
|
34
70
|
readonly reason: "note_missing";
|
|
35
71
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
36
72
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
73
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
37
74
|
}, {
|
|
38
75
|
readonly reason: "no_active_file";
|
|
39
76
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
40
77
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
78
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
41
79
|
}, {
|
|
42
80
|
readonly reason: "periodic_not_found";
|
|
43
81
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
44
82
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
83
|
+
readonly recovery: "Pass an explicit path target — periodic notes must already exist.";
|
|
84
|
+
}, {
|
|
85
|
+
readonly reason: "periodic_disabled";
|
|
86
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
87
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
88
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
45
89
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
46
90
|
format: import("zod").ZodEnum<{
|
|
47
91
|
content: "content";
|
|
@@ -113,24 +157,28 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
113
157
|
readonly reason: "section_required";
|
|
114
158
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
115
159
|
readonly when: "`format` is \"section\" but no `section` locator was provided.";
|
|
160
|
+
readonly recovery: "Pass `section: { type, target }` (e.g. `{ type: \"heading\", target: \"Intro\" }`), or use `format: \"full\"` / `\"document-map\"` instead.";
|
|
116
161
|
}, {
|
|
117
162
|
readonly reason: "note_missing";
|
|
118
163
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
119
164
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
165
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
120
166
|
}, {
|
|
121
167
|
readonly reason: "no_active_file";
|
|
122
168
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
123
169
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
170
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
124
171
|
}, {
|
|
125
172
|
readonly reason: "periodic_not_found";
|
|
126
173
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
127
174
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
175
|
+
readonly recovery: "Create the periodic note first or pass an explicit path target.";
|
|
176
|
+
}, {
|
|
177
|
+
readonly reason: "periodic_disabled";
|
|
178
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
179
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
180
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
181
|
+
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
134
182
|
path: import("zod").ZodOptional<import("zod").ZodString>;
|
|
135
183
|
extension: import("zod").ZodOptional<import("zod").ZodString>;
|
|
136
184
|
nameRegex: import("zod").ZodOptional<import("zod").ZodString>;
|
|
@@ -164,10 +212,12 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
164
212
|
readonly reason: "regex_invalid";
|
|
165
213
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
166
214
|
readonly when: "The supplied `nameRegex` is not a valid ECMAScript regex.";
|
|
215
|
+
readonly recovery: "Test the pattern (e.g. `^Project.*\\.md$`) in a JS regex tester, or omit nameRegex to disable filtering.";
|
|
167
216
|
}, {
|
|
168
217
|
readonly reason: "note_missing";
|
|
169
218
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
170
219
|
readonly when: "The supplied `path` does not exist in the vault. Sub-directories that disappear mid-walk are silently skipped — only the root path surfaces this error.";
|
|
220
|
+
readonly recovery: "List a parent directory to find the correct casing or check the spelling.";
|
|
171
221
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
172
222
|
tags: import("zod").ZodArray<import("zod").ZodObject<{
|
|
173
223
|
name: import("zod").ZodString;
|
|
@@ -219,18 +269,27 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
219
269
|
readonly reason: "value_required";
|
|
220
270
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
221
271
|
readonly when: "`operation` is \"set\" but no `value` was supplied.";
|
|
272
|
+
readonly recovery: "Pass `value` as any JSON-typed value: string, number, boolean, array, or object (e.g. `\"draft\"`, `42`, `true`, `[\"a\",\"b\"]`).";
|
|
222
273
|
}, {
|
|
223
274
|
readonly reason: "note_missing";
|
|
224
275
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
225
276
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
277
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
226
278
|
}, {
|
|
227
279
|
readonly reason: "no_active_file";
|
|
228
280
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
229
281
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
282
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
230
283
|
}, {
|
|
231
284
|
readonly reason: "periodic_not_found";
|
|
232
285
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
233
286
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
287
|
+
readonly recovery: "Create the periodic note first or pass an explicit path target.";
|
|
288
|
+
}, {
|
|
289
|
+
readonly reason: "periodic_disabled";
|
|
290
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
291
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
292
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
234
293
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
235
294
|
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
236
295
|
type: import("zod").ZodLiteral<"path">;
|
|
@@ -285,18 +344,27 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
285
344
|
readonly reason: "tags_required";
|
|
286
345
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
287
346
|
readonly when: "`operation` is \"add\" or \"remove\" but `tags` was empty or omitted.";
|
|
347
|
+
readonly recovery: "Pass a non-empty `tags` array (without `#`), e.g. `[\"draft\", \"wip\"]`.";
|
|
288
348
|
}, {
|
|
289
349
|
readonly reason: "note_missing";
|
|
290
350
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
291
351
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
352
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
292
353
|
}, {
|
|
293
354
|
readonly reason: "no_active_file";
|
|
294
355
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
295
356
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
357
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
296
358
|
}, {
|
|
297
359
|
readonly reason: "periodic_not_found";
|
|
298
360
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
299
361
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
362
|
+
readonly recovery: "Create the periodic note first or pass an explicit path target.";
|
|
363
|
+
}, {
|
|
364
|
+
readonly reason: "periodic_disabled";
|
|
365
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
366
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
367
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
300
368
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
301
369
|
path: import("zod").ZodString;
|
|
302
370
|
failIfMissing: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
@@ -309,6 +377,7 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
309
377
|
readonly reason: "note_missing";
|
|
310
378
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
311
379
|
readonly when: "`failIfMissing: true` (default) and the path does not exist in the vault. Pass `failIfMissing: false` to allow Obsidian to create the file on open.";
|
|
380
|
+
readonly recovery: "Verify the path with obsidian_list_notes or pass failIfMissing false to create on open.";
|
|
312
381
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
313
382
|
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
314
383
|
type: import("zod").ZodLiteral<"path">;
|
|
@@ -368,18 +437,27 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
368
437
|
readonly reason: "note_missing";
|
|
369
438
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
370
439
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
440
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
371
441
|
}, {
|
|
372
442
|
readonly reason: "no_active_file";
|
|
373
443
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
374
444
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
445
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
375
446
|
}, {
|
|
376
447
|
readonly reason: "periodic_not_found";
|
|
377
448
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
378
449
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
450
|
+
readonly recovery: "Create the periodic note first or pass an explicit path target.";
|
|
451
|
+
}, {
|
|
452
|
+
readonly reason: "periodic_disabled";
|
|
453
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
454
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
455
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
379
456
|
}, {
|
|
380
457
|
readonly reason: "section_target_missing";
|
|
381
458
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
382
459
|
readonly when: "The named heading/block/frontmatter field does not exist in the note. Use `obsidian_get_note` with `format: \"document-map\"` to discover available targets.";
|
|
460
|
+
readonly recovery: "Call obsidian_get_note with format document-map to discover the available targets.";
|
|
383
461
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
384
462
|
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
385
463
|
type: import("zod").ZodLiteral<"path">;
|
|
@@ -417,18 +495,27 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
417
495
|
readonly reason: "regex_invalid";
|
|
418
496
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
419
497
|
readonly when: "A `useRegex: true` replacement supplied a `search` pattern that is not a valid ECMAScript regex.";
|
|
498
|
+
readonly recovery: "Test the pattern in a JS regex tester, or set useRegex to false to match `search` as a literal string.";
|
|
420
499
|
}, {
|
|
421
500
|
readonly reason: "note_missing";
|
|
422
501
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
423
502
|
readonly when: "The vault path does not resolve to an existing note.";
|
|
503
|
+
readonly recovery: "Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.";
|
|
424
504
|
}, {
|
|
425
505
|
readonly reason: "no_active_file";
|
|
426
506
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
427
507
|
readonly when: "Target was `active` but no file is currently open in Obsidian.";
|
|
508
|
+
readonly recovery: "Open a note in Obsidian or pass an explicit path target instead.";
|
|
428
509
|
}, {
|
|
429
510
|
readonly reason: "periodic_not_found";
|
|
430
511
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
431
512
|
readonly when: "Target was `periodic` but no matching periodic note exists.";
|
|
513
|
+
readonly recovery: "Create the periodic note first or pass an explicit path target.";
|
|
514
|
+
}, {
|
|
515
|
+
readonly reason: "periodic_disabled";
|
|
516
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
517
|
+
readonly when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.";
|
|
518
|
+
readonly recovery: "Enable the period in Obsidian's Periodic Notes plugin settings, or pass an explicit path target instead.";
|
|
432
519
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
433
520
|
mode: import("zod").ZodEnum<{
|
|
434
521
|
text: "text";
|
|
@@ -485,14 +572,17 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
485
572
|
readonly reason: "path_prefix_invalid_mode";
|
|
486
573
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
487
574
|
readonly when: "`pathPrefix` was provided in a non-text mode (only `text` supports prefix filtering).";
|
|
575
|
+
readonly recovery: "Drop pathPrefix or switch mode to text for prefix filtering.";
|
|
488
576
|
}, {
|
|
489
577
|
readonly reason: "query_required";
|
|
490
578
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
491
579
|
readonly when: "`query` is missing for `text` or `dataview` mode (required for both).";
|
|
580
|
+
readonly recovery: "Pass `query` — search terms for text mode (e.g. \"TODO\"), or DQL like \"TABLE WHERE file.mtime > date(today)\" for dataview mode.";
|
|
492
581
|
}, {
|
|
493
582
|
readonly reason: "logic_required";
|
|
494
583
|
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.ValidationError;
|
|
495
584
|
readonly when: "`logic` is missing for `jsonlogic` mode.";
|
|
585
|
+
readonly recovery: "Pass a JSONLogic tree as `logic`, e.g. `{\"glob\": [{\"var\": \"path\"}, \"Projects/*.md\"]}`.";
|
|
496
586
|
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
497
587
|
target: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
498
588
|
type: import("zod").ZodLiteral<"path">;
|
|
@@ -523,9 +613,32 @@ export declare const baseToolDefinitions: (import("@cyanheads/mcp-ts-core").Tool
|
|
|
523
613
|
markdown: "markdown";
|
|
524
614
|
json: "json";
|
|
525
615
|
}>>;
|
|
616
|
+
overwrite: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
526
617
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
527
618
|
path: import("zod").ZodString;
|
|
528
619
|
sectionTargeted: import("zod").ZodBoolean;
|
|
620
|
+
created: import("zod").ZodBoolean;
|
|
621
|
+
}, import("zod/v4/core").$strip>, readonly [{
|
|
622
|
+
readonly reason: "file_exists";
|
|
623
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.Conflict;
|
|
624
|
+
readonly when: "Whole-file write was attempted against an existing note and `overwrite` was not set to `true`.";
|
|
625
|
+
readonly recovery: "Retry with overwrite true or use obsidian_patch_note for in-place edits.";
|
|
626
|
+
}]>)[];
|
|
627
|
+
/** Command-palette tools — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`. */
|
|
628
|
+
export declare const commandToolDefinitions: (import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{
|
|
629
|
+
commandId: import("zod").ZodString;
|
|
630
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
631
|
+
commandId: import("zod").ZodString;
|
|
632
|
+
executed: import("zod").ZodBoolean;
|
|
633
|
+
}, import("zod/v4/core").$strip>, readonly [{
|
|
634
|
+
readonly reason: "command_unknown";
|
|
635
|
+
readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
|
|
636
|
+
readonly when: "The supplied `commandId` is not registered in Obsidian. Use `obsidian_list_commands` to discover valid IDs.";
|
|
637
|
+
readonly recovery: "Call obsidian_list_commands to discover the registered command IDs.";
|
|
638
|
+
}]> | import("@cyanheads/mcp-ts-core").ToolDefinition<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
639
|
+
commands: import("zod").ZodArray<import("zod").ZodObject<{
|
|
640
|
+
id: import("zod").ZodString;
|
|
641
|
+
name: import("zod").ZodString;
|
|
642
|
+
}, import("zod/v4/core").$strip>>;
|
|
529
643
|
}, import("zod/v4/core").$strip>, undefined>)[];
|
|
530
|
-
export { obsidianExecuteCommand };
|
|
531
644
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,wDAAwD;AACxD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAa/B,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;+CAAiD,CAAC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Tool registration barrel.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Tool registration barrel. The command-palette pair
|
|
3
|
+
* (`obsidian_list_commands` + `obsidian_execute_command`) is exported
|
|
4
|
+
* separately so callers can decide whether to register them based on the
|
|
5
|
+
* `OBSIDIAN_ENABLE_COMMANDS` flag — keeping this module free of eager
|
|
6
|
+
* config reads.
|
|
5
7
|
* @module mcp-server/tools/definitions/index
|
|
6
8
|
*/
|
|
7
9
|
import { obsidianAppendToNote } from './obsidian-append-to-note.tool.js';
|
|
@@ -23,7 +25,6 @@ export const baseToolDefinitions = [
|
|
|
23
25
|
obsidianGetNote,
|
|
24
26
|
obsidianListNotes,
|
|
25
27
|
obsidianListTags,
|
|
26
|
-
obsidianListCommands,
|
|
27
28
|
obsidianSearchNotes,
|
|
28
29
|
obsidianWriteNote,
|
|
29
30
|
obsidianAppendToNote,
|
|
@@ -34,5 +35,6 @@ export const baseToolDefinitions = [
|
|
|
34
35
|
obsidianDeleteNote,
|
|
35
36
|
obsidianOpenInUi,
|
|
36
37
|
];
|
|
37
|
-
|
|
38
|
+
/** Command-palette tools — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`. */
|
|
39
|
+
export const commandToolDefinitions = [obsidianListCommands, obsidianExecuteCommand];
|
|
38
40
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,wDAAwD;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,eAAe;IACf,iBAAiB;IACjB,gBAAgB;IAChB,mBAAmB;IACnB,iBAAiB;IACjB,oBAAoB;IACpB,iBAAiB;IACjB,qBAAqB;IACrB,yBAAyB;IACzB,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;CACjB,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,oBAAoB,EAAE,sBAAsB,CAAC,CAAC"}
|