obsidian-mcp-server 3.5.0 → 3.5.2

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.
Files changed (68) hide show
  1. package/AGENTS.md +8 -7
  2. package/CLAUDE.md +8 -7
  3. package/Dockerfile +3 -1
  4. package/README.md +8 -5
  5. package/changelog/3.5.x/3.5.1.md +27 -0
  6. package/changelog/3.5.x/3.5.2.md +27 -0
  7. package/dist/config/server-config.d.ts +4 -4
  8. package/dist/mcp-server/resources/definitions/index.d.ts +5 -0
  9. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -1
  10. package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts +11 -4
  11. package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts.map +1 -1
  12. package/dist/mcp-server/resources/definitions/obsidian-status.resource.js +21 -12
  13. package/dist/mcp-server/resources/definitions/obsidian-status.resource.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/index.d.ts +88 -39
  15. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +6 -1
  17. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +7 -1
  19. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +6 -1
  21. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +7 -1
  23. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +11 -2
  25. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +41 -15
  27. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
  28. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +8 -3
  29. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +12 -5
  31. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +6 -1
  33. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +7 -1
  35. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +8 -3
  37. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +10 -4
  39. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +6 -1
  41. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +7 -1
  43. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -1
  44. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +6 -1
  45. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +7 -1
  47. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -1
  48. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +6 -1
  49. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +7 -1
  51. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
  52. package/dist/services/obsidian/frontmatter-ops.d.ts +9 -5
  53. package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -1
  54. package/dist/services/obsidian/frontmatter-ops.js +70 -16
  55. package/dist/services/obsidian/frontmatter-ops.js.map +1 -1
  56. package/dist/services/obsidian/obsidian-service.d.ts +10 -7
  57. package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
  58. package/dist/services/obsidian/obsidian-service.js +193 -38
  59. package/dist/services/obsidian/obsidian-service.js.map +1 -1
  60. package/dist/services/obsidian/section-extractor.d.ts +17 -1
  61. package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
  62. package/dist/services/obsidian/section-extractor.js +109 -40
  63. package/dist/services/obsidian/section-extractor.js.map +1 -1
  64. package/dist/services/obsidian/types.d.ts +17 -0
  65. package/dist/services/obsidian/types.d.ts.map +1 -1
  66. package/manifest.json +1 -1
  67. package/package.json +10 -10
  68. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.5.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
4
+ **Version:** 3.5.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.8`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.5.4
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -189,7 +189,7 @@ The framework also provides `ctx.state`. It isn't used by this server — Obsidi
189
189
 
190
190
  Handlers throw — the framework catches, classifies, and formats.
191
191
 
192
- **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.
192
+ **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`, `RequestCancelled`) bubble freely and don't need declaring.
193
193
 
194
194
  ```ts
195
195
  errors: [
@@ -299,8 +299,9 @@ Available skills:
299
299
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
300
300
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
301
301
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
302
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
303
- | `release-and-publish` | Ship a release end-to-end across npm, MCP Registry, GitHub Releases (`.mcpb`), and GHCR |
302
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
303
+ | `release-pr-review` | Review pass on an open release PR simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
304
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
304
305
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
305
306
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
306
307
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -380,7 +381,7 @@ security: false # optional — true ONLY for a source
380
381
 
381
382
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
382
383
 
383
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
384
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
384
385
 
385
386
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
386
387
 
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.5.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
4
+ **Version:** 3.5.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.8`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.5.4
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -189,7 +189,7 @@ The framework also provides `ctx.state`. It isn't used by this server — Obsidi
189
189
 
190
190
  Handlers throw — the framework catches, classifies, and formats.
191
191
 
192
- **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.
192
+ **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`, `RequestCancelled`) bubble freely and don't need declaring.
193
193
 
194
194
  ```ts
195
195
  errors: [
@@ -299,8 +299,9 @@ Available skills:
299
299
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
300
300
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
301
301
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
302
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
303
- | `release-and-publish` | Ship a release end-to-end across npm, MCP Registry, GitHub Releases (`.mcpb`), and GHCR |
302
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
303
+ | `release-pr-review` | Review pass on an open release PR simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
304
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
304
305
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
305
306
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
306
307
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -380,7 +381,7 @@ security: false # optional — true ONLY for a source
380
381
 
381
382
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
382
383
 
383
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
384
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
384
385
 
385
386
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
386
387
 
package/Dockerfile CHANGED
@@ -110,7 +110,9 @@ ENV MCP_HTTP_PORT=${PORT:-3010}
110
110
  # bind exposes the upstream OBSIDIAN_API_KEY to any caller that can reach the port.
111
111
  ENV MCP_HTTP_HOST="0.0.0.0"
112
112
  ENV MCP_TRANSPORT_TYPE="http"
113
- ENV MCP_SESSION_MODE="stateless"
113
+ # Stateful because obsidian_delete_note confirms via ctx.requestInput; the
114
+ # elicitation round backing that gate is disabled in stateless mode.
115
+ ENV MCP_SESSION_MODE="stateful"
114
116
  ENV MCP_LOG_LEVEL="info"
115
117
  ENV LOGS_DIR="/var/log/obsidian-mcp-server"
116
118
  ENV MCP_FORCE_CONSOLE_LOGGING="true"
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-3.5.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-3.5.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/obsidian-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/obsidian-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/obsidian-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -49,7 +49,7 @@ Read a note in one of four projections, addressed by vault path, the active file
49
49
  - `format: "content"` — raw markdown body
50
50
  - `format: "full"` — content, frontmatter, tags, and file metadata; pass `includeLinks: true` to also parse outgoing wiki and markdown link references from the body (vault-internal only — external URLs are filtered)
51
51
  - `format: "document-map"` — catalog of headings, block references, and frontmatter fields
52
- - `format: "section"` — single heading/block/frontmatter section value (requires `section`); heading sections include the full subtree under that heading
52
+ - `format: "section"` — single heading/block/frontmatter section value (requires `section`); heading sections include the full subtree under that heading. The response echoes the locator the read resolved to in `sectionTarget` as a full `Parent::Child` path, and when a bare leaf name matches several headings it lists every colliding path in `candidates` — the read still returns the first match
53
53
 
54
54
  Pair the document-map projection with `obsidian_patch_note` to discover edit targets before patching.
55
55
 
@@ -136,7 +136,9 @@ Add, remove, or list tags on a note. Operates on one of two representations, def
136
136
  - `location: 'inline'` — only inline `#tag` syntax in the body; `add` appends `#tag` at end-of-file
137
137
  - `location: 'both'` — opt-in reconciliation across both representations
138
138
 
139
- `add` ensures the tag is present in the requested location(s); `remove` strips it; `list` ignores the input `tags` array. Inline `#tag` occurrences inside fenced code blocks are intentionally left alone.
139
+ `add` ensures the tag is present in the requested location(s); `remove` strips it; `list` ignores the input `tags` array.
140
+
141
+ Inline `#tag` detection skips code spans (fenced and inline), link spans (`[[...]]`, `[text](...)`, `[text][ref]`) — so a heading anchor, block anchor, or wikilink alias is never read as a tag or rewritten by a removal — and a hash escaped as `\#`. A `#` inside an HTML comment or a math span is still counted. `list` and `remove` run the same detection, so what `list` reports is what `remove` can reach.
140
142
 
141
143
  Inline mode reads and writes the note body only — a `#` inside a YAML scalar is frontmatter, so it is neither listed as an inline tag nor rewritten by a removal. Removing an inline tag takes exactly one adjacent horizontal space with it — the one before the tag, or the one after when no space precedes it; every other byte survives, including nested list indentation, four-space indented code blocks, trailing two-space hard line breaks, and table cell padding.
142
144
 
@@ -210,7 +212,7 @@ Obsidian-specific:
210
212
 
211
213
  - Wraps the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin — typed client, deterministic error mapping
212
214
  - Section-aware editing across headings, block references, and frontmatter fields via `PATCH`-with-target operations
213
- - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping fenced code blocks)
215
+ - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping code spans, link spans, and hashes escaped as `\#`)
214
216
  - Search across up to three modes: text, JSONLogic, and (when the plugin is reachable) BM25-ranked Omnisearch — cursor-paginated per the MCP 2025-11-25 spec, with per-file match clipping in text mode
215
217
  - Required human-in-the-loop confirmation for destructive deletes — a multi-round-trip `input_required` round served on both protocol revisions, with no unconfirmed path through the tool
216
218
  - Folder-scoped read/write permissions via `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` and a global `OBSIDIAN_READ_ONLY` kill switch — denies are typed `path_forbidden` with the active scope echoed back in the error data
@@ -268,7 +270,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
268
270
 
269
271
  - [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).
270
272
  - The [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, **v4.0.0 through v5.x**, installed and enabled in your vault. Generate an API key in **Settings → Community Plugins → Local REST API** and copy it into `OBSIDIAN_API_KEY`. Plugin v6.0 removes the markdown-patch 1.x wire format this server pins for section-targeted writes and the document map.
271
- - Periodic-note targets (`target: { "type": "periodic" }`) additionally need plugin **v5.0.1 or earlier** v5.0.2 removed the built-in `/periodic/` routes. Every other target type is unaffected.
273
+ - Periodic-note targets (`target: { "type": "periodic" }`) work across that whole range: natively on plugin **v5.0.1 and earlier**, and on **v5.0.2 and later** — which moved the `/periodic/` routes out of the plugin — once the companion [periodic-notes API extension](https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes) is installed. Without that extension on v5.0.2+, periodic targets fail with a `periodic_unsupported` error naming it; `obsidian://status` lists the registered extensions if you want to check first. Every other target type is unaffected.
272
274
  - An MCP client that can answer an input request (elicitation). `obsidian_delete_note` always asks for confirmation before deleting, so a client without that support can read and write notes but cannot delete one.
273
275
  - This server defaults to `http://127.0.0.1:27123` for simplicity. Enable **"Non-encrypted (HTTP) Server"** in the plugin settings to use it. To use the always-on HTTPS port instead, set `OBSIDIAN_BASE_URL=https://127.0.0.1:27124`; the plugin's self-signed cert is handled by `OBSIDIAN_VERIFY_SSL=false` (the default).
274
276
 
@@ -316,6 +318,7 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
316
318
  | `MCP_HTTP_HOST` | Host for the HTTP server. | `127.0.0.1` |
317
319
  | `MCP_HTTP_PORT` | Port for the HTTP server. | `3010` |
318
320
  | `MCP_HTTP_ENDPOINT_PATH` | Endpoint path for the JSON-RPC handler. | `/mcp` |
321
+ | `MCP_SESSION_MODE` | Session handling for the HTTP transport: `stateless`, `stateful`, or `auto`. Pinned to `stateful` — `obsidian_delete_note` confirms via an elicitation round, which `stateless` disables. | `stateful` |
319
322
  | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata). | unset |
320
323
  | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
321
324
  | `MCP_AUTH_SECRET_KEY` | **Required when `MCP_AUTH_MODE=jwt`.** ≥32-char shared secret used to verify incoming JWTs. | — |
@@ -0,0 +1,27 @@
1
+ ---
2
+ summary: "Upstream 500/501 now classify as ServiceUnavailable instead of InternalError, so a 500 on a retry-safe call is retried automatically; MCP_SESSION_MODE is pinned to stateful so obsidian_delete_note's confirmation round-trip actually works under Docker."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 3.5.1 — 2026-09-04
8
+
9
+ ## Changed
10
+
11
+ - **Upstream HTTP 500 and 501 now classify as `ServiceUnavailable` instead of `InternalError`** — `ObsidianService#throwForStatus`'s default branch adopts `mcp-ts-core`'s widened `httpStatusToErrorCode` mapping (502/503/other 5xx unchanged, 504 stays `Timeout`). `ServiceUnavailable` is in `withRetry`'s transient set, so a 500 on a retry-safe call (e.g. `obsidian_get_note`) is now retried automatically instead of failing on the first attempt. 501 keeps the shared `ServiceUnavailable` code but the service attaches `data.retryable: false` by hand, since a method the plugin declares unimplemented can't change on retry.
12
+ - Framework-managed files under `skills/` resynced from the `mcp-ts-core` package — `api-errors` documents the same 5xx reclassification; `release-and-publish`, `maintenance`, `setup`, `add-tool`, and `api-config` pick up unrelated upstream fixes.
13
+
14
+ ## Fixed
15
+
16
+ - **`MCP_SESSION_MODE` is pinned to `stateful`** across `.env.example`, the `Dockerfile`, and the README env table ([#120](https://github.com/cyanheads/obsidian-mcp-server/issues/120)) — the Docker image shipped `stateless` while `.env.example` documented the schema's `auto` default (which itself resolves to `stateful`), so the two surfaces disagreed. `obsidian_delete_note` gates on a `ctx.requestInput` confirmation round that a 2025-era HTTP client can't complete under `stateless` (no live session to carry the `elicitation/create` call), so the Docker default silently made the delete tool unusable for that client generation. All three surfaces now agree on `stateful`.
17
+
18
+ ## Dependencies
19
+
20
+ - `@cyanheads/mcp-ts-core` ^0.12.3 → ^0.12.5
21
+ - `js-yaml` ^5.3.0 → ^5.4.1
22
+ - `undici` ^8.10.0 → ^8.10.1
23
+ - `zod` ^4.4.3 → ^4.5.4
24
+ - `@biomejs/biome` ^2.5.9 → ^2.5.11 (dev)
25
+ - `@types/node` ^26.2.0 → ^26.4.0 (dev)
26
+ - `ignore` ^7.0.6 → ^7.0.8 (dev)
27
+ - `tsc-alias` ^1.9.2 → ^1.9.3 (dev)
@@ -0,0 +1,27 @@
1
+ ---
2
+ summary: "Frontmatter boundary, inline tag detection, and section reads now share one consistent boundary and resolution logic; list_notes reports directory_missing/path_is_file instead of a generic note_missing; periodic-note targets get a periodic_unsupported error naming the missing plugin extension instead of a bare 404."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 3.5.2 — 2026-09-09
8
+
9
+ ## Changed
10
+
11
+ - **`obsidian_get_note` section reads echo the resolved heading path** — a bare leaf target reports back the full `Parent::Child` path it matched in `sectionTarget`, and every colliding heading in `candidates` when more than one shares that leaf; the read still returns the first match ([#107](https://github.com/cyanheads/obsidian-mcp-server/issues/107)).
12
+ - **`obsidian_manage_tags` inline `#tag` detection skips link spans and escaped hashes** — `[[wikilink#Heading]]`, `[text](url#frag)`, and `[text][ref]` are no longer read or rewritten as tags, and a `\#` is recognized as an escaped literal hash; `list` and `remove` share the same detection so they agree on what a tag is ([#119](https://github.com/cyanheads/obsidian-mcp-server/issues/119)).
13
+ - **`obsidian_list_notes` reports `directory_missing` and `path_is_file` in place of `note_missing`** — a folder that doesn't exist or holds no files gets `directory_missing`; a `path` that names a file gets `path_is_file` ([#105](https://github.com/cyanheads/obsidian-mcp-server/issues/105)).
14
+
15
+ ## Fixed
16
+
17
+ - **The frontmatter fence regex now agrees with `splice()` on an empty properties block** — `section-extractor`'s line-scan boundary previously used a different rule than `frontmatter-ops`'s splice, so a note with an empty `---\n---` block or a fence carrying trailing whitespace could hide body content from section reads; both now resolve the boundary through the same `splice()` call ([#118](https://github.com/cyanheads/obsidian-mcp-server/issues/118)).
18
+ - **Periodic-note targets get a typed `periodic_unsupported` error on Local REST API v5.0.2+ without the companion extension** — that plugin version removed the built-in `/periodic/` routes, and every periodic-target tool previously surfaced the resulting 404 as `periodic_not_found`, indistinguishable from "no note for that period." A memoized capability probe against `GET /`'s `apiExtensions` now tells the two apart and names `local-rest-api-periodic-notes` in the recovery hint ([#103](https://github.com/cyanheads/obsidian-mcp-server/issues/103)).
19
+
20
+ ## Dependencies
21
+
22
+ - `@cyanheads/mcp-ts-core` ^0.12.5 → ^0.12.8
23
+ - `undici` ^8.10.1 → ^8.10.2
24
+ - `@biomejs/biome` ^2.5.11 → ^2.5.12 (dev)
25
+ - `@types/node` ^26.4.0 → ^26.4.1 (dev)
26
+ - `tsc-alias` ^1.9.3 → ^1.9.4 (dev)
27
+ - `vitest` ^4.1.11 → ^5.0.0 (dev)
@@ -6,14 +6,14 @@
6
6
  import { z } from '@cyanheads/mcp-ts-core';
7
7
  declare const ServerConfigSchema: z.ZodObject<{
8
8
  apiKey: z.ZodString;
9
- baseUrl: z.ZodPreprocess<z.ZodDefault<z.ZodString>>;
9
+ baseUrl: z.ZodPreprocess<z.ZodDefault<z.ZodString>, unknown>;
10
10
  verifySsl: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
11
11
  requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
12
12
  enableCommands: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
13
- readPaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>>;
14
- writePaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>>;
13
+ readPaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>, unknown>;
14
+ writePaths: z.ZodPreprocess<z.ZodPipe<z.ZodOptional<z.ZodArray<z.ZodString>>, z.ZodTransform<string[] | undefined, string[] | undefined>>, unknown>;
15
15
  readOnly: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCodec<z.ZodString, z.ZodBoolean>]>>;
16
- omnisearchUrl: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
16
+ omnisearchUrl: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
17
17
  }, z.core.$strip>;
18
18
  export type ServerConfig = z.infer<typeof ServerConfigSchema>;
19
19
  export declare function getServerConfig(): ServerConfig;
@@ -47,5 +47,10 @@ export declare const allResourceDefinitions: (import("@cyanheads/mcp-ts-core").R
47
47
  name: import("zod").ZodString;
48
48
  version: import("zod").ZodString;
49
49
  }, import("zod/v4/core").$strip>>;
50
+ apiExtensions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
51
+ id: import("zod").ZodString;
52
+ name: import("zod").ZodOptional<import("zod").ZodString>;
53
+ version: import("zod").ZodOptional<import("zod").ZodString>;
54
+ }, import("zod/v4/core").$strip>>>;
50
55
  }, import("zod/v4/core").$strip>, undefined>)[];
51
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CAAoD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CAAoD,CAAC"}
@@ -1,8 +1,10 @@
1
1
  /**
2
- * @fileoverview obsidian://status — reachability + auth check for the Obsidian
3
- * Local REST API plugin. Issues an anonymous probe (so the resource still
4
- * works when the key is wrong) and a separate authenticated probe so the
5
- * `authenticated` field reflects whether the configured key is accepted.
2
+ * @fileoverview obsidian://status — reachability, auth, and capability report
3
+ * for the Obsidian Local REST API plugin. One authenticated `GET /`: the route
4
+ * answers `200` whatever the key is and self-reports whether it was accepted,
5
+ * so a misconfigured key still yields the full reachability payload, and
6
+ * `apiExtensions` — which the plugin omits from the unauthenticated response —
7
+ * comes back on the same request.
6
8
  * @module mcp-server/resources/definitions/obsidian-status.resource
7
9
  */
8
10
  import { z } from '@cyanheads/mcp-ts-core';
@@ -19,5 +21,10 @@ export declare const obsidianStatus: import("@cyanheads/mcp-ts-core").ResourceDe
19
21
  name: z.ZodString;
20
22
  version: z.ZodString;
21
23
  }, z.core.$strip>>;
24
+ apiExtensions: z.ZodOptional<z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ name: z.ZodOptional<z.ZodString>;
27
+ version: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>>>;
22
29
  }, z.core.$strip>, undefined>;
23
30
  //# sourceMappingURL=obsidian-status.resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"obsidian-status.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-status.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAGrD,eAAO,MAAM,cAAc;;;;;;;;;;;;;6BAwCzB,CAAC"}
1
+ {"version":3,"file":"obsidian-status.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-status.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAGrD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;6BAmDzB,CAAC"}
@@ -1,15 +1,17 @@
1
1
  /**
2
- * @fileoverview obsidian://status — reachability + auth check for the Obsidian
3
- * Local REST API plugin. Issues an anonymous probe (so the resource still
4
- * works when the key is wrong) and a separate authenticated probe so the
5
- * `authenticated` field reflects whether the configured key is accepted.
2
+ * @fileoverview obsidian://status — reachability, auth, and capability report
3
+ * for the Obsidian Local REST API plugin. One authenticated `GET /`: the route
4
+ * answers `200` whatever the key is and self-reports whether it was accepted,
5
+ * so a misconfigured key still yields the full reachability payload, and
6
+ * `apiExtensions` — which the plugin omits from the unauthenticated response —
7
+ * comes back on the same request.
6
8
  * @module mcp-server/resources/definitions/obsidian-status.resource
7
9
  */
8
10
  import { resource, z } from '@cyanheads/mcp-ts-core';
9
11
  import { getObsidianService } from '../../../services/obsidian/obsidian-service.js';
10
12
  export const obsidianStatus = resource('obsidian://status', {
11
13
  name: 'obsidian-status',
12
- description: 'Server reachability, plugin version, and auth status of the Obsidian Local REST API. Reports the unauthenticated reachability info even when the API key is misconfigured; `authenticated` reflects whether the configured key is accepted by an authenticated probe.',
14
+ description: 'Server reachability, plugin version, auth status, and registered API extensions of the Obsidian Local REST API. Still reports reachability when the API key is misconfigured; `authenticated` reflects whether the plugin accepted the configured key. Check `apiExtensions` before using a `periodic` note target — on plugin v5.0.2 and later those routes are served only when `local-rest-api-periodic-notes` is registered.',
13
15
  mimeType: 'application/json',
14
16
  params: z.object({}),
15
17
  output: z.object({
@@ -17,7 +19,7 @@ export const obsidianStatus = resource('obsidian://status', {
17
19
  service: z.string().describe('Service identifier returned by the plugin.'),
18
20
  authenticated: z
19
21
  .boolean()
20
- .describe('True when the configured OBSIDIAN_API_KEY is accepted by an authenticated request to the vault listing.'),
22
+ .describe('True when the plugin accepted the configured OBSIDIAN_API_KEY on this request.'),
21
23
  versions: z
22
24
  .object({
23
25
  obsidian: z.string().optional().describe('Obsidian app version, when reported.'),
@@ -33,15 +35,22 @@ export const obsidianStatus = resource('obsidian://status', {
33
35
  })
34
36
  .optional()
35
37
  .describe('Plugin manifest, when reported.'),
38
+ apiExtensions: z
39
+ .array(z
40
+ .object({
41
+ id: z
42
+ .string()
43
+ .describe('Extension manifest ID, e.g. `local-rest-api-periodic-notes` for the extension that serves `/periodic/` routes on plugin v5.0.2 and later.'),
44
+ name: z.string().optional().describe('Extension display name, when reported.'),
45
+ version: z.string().optional().describe('Extension version, when reported.'),
46
+ })
47
+ .describe('One registered API extension.'))
48
+ .optional()
49
+ .describe('API extensions registered against the plugin. An empty array means none are registered; the field is absent when the plugin did not report it at all, which it does only for a request whose API key was accepted.'),
36
50
  }),
37
51
  auth: ['resource:obsidian-status:read'],
38
52
  async handler(_params, ctx) {
39
- const svc = getObsidianService();
40
- const [status, authenticated] = await Promise.all([
41
- svc.getStatus(ctx),
42
- svc.probeAuthenticated(ctx),
43
- ]);
44
- return { ...status, authenticated };
53
+ return await getObsidianService().getStatus(ctx);
45
54
  },
46
55
  });
47
56
  //# sourceMappingURL=obsidian-status.resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"obsidian-status.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-status.resource.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC,mBAAmB,EAAE;IAC1D,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,uQAAuQ;IACzQ,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QAC1E,aAAa,EAAE,CAAC;aACb,OAAO,EAAE;aACT,QAAQ,CACP,yGAAyG,CAC1G;QACH,QAAQ,EAAE,CAAC;aACR,MAAM,CAAC;YACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;YAChF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SACtF,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CAAC,oDAAoD,CAAC;QACjE,QAAQ,EAAE,CAAC;aACR,MAAM,CAAC;YACN,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YAC9C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YACjD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;SAChD,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CAAC,iCAAiC,CAAC;KAC/C,CAAC;IACF,IAAI,EAAE,CAAC,+BAA+B,CAAC;IAEvC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG;QACxB,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QACjC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC;YAClB,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,CAAC;IACtC,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"obsidian-status.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/obsidian-status.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC,mBAAmB,EAAE;IAC1D,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,kaAAka;IACpa,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QAC1E,aAAa,EAAE,CAAC;aACb,OAAO,EAAE;aACT,QAAQ,CAAC,gFAAgF,CAAC;QAC7F,QAAQ,EAAE,CAAC;aACR,MAAM,CAAC;YACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;YAChF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SACtF,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CAAC,oDAAoD,CAAC;QACjE,QAAQ,EAAE,CAAC;aACR,MAAM,CAAC;YACN,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YAC9C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YACjD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;SAChD,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CAAC,iCAAiC,CAAC;QAC9C,aAAa,EAAE,CAAC;aACb,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,EAAE,EAAE,CAAC;iBACF,MAAM,EAAE;iBACR,QAAQ,CACP,2IAA2I,CAC5I;YACH,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;YAC9E,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;SAC7E,CAAC;aACD,QAAQ,CAAC,+BAA+B,CAAC,CAC7C;aACA,QAAQ,EAAE;aACV,QAAQ,CACP,oNAAoN,CACrN;KACJ,CAAC;IACF,IAAI,EAAE,CAAC,+BAA+B,CAAC;IAEvC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG;QACxB,OAAO,MAAM,kBAAkB,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC;CACF,CAAC,CAAC"}