obsidian-mcp-server 3.1.0 → 3.1.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.
- package/CLAUDE.md +24 -9
- package/README.md +28 -1
- package/changelog/3.1.x/3.1.1.md +21 -0
- package/changelog/3.1.x/3.1.2.md +23 -0
- package/dist/config/server-config.d.ts +5 -2
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +73 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +39 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +793 -10
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/index.js +15 -9
- package/dist/mcp-server/tools/definitions/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +7 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +9 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +14 -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 +20 -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 +14 -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 +17 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +7 -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 +18 -3
- 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 +14 -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 +17 -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 +14 -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 +17 -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 +6 -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 +14 -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 +16 -2
- 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 +16 -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 +14 -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 +17 -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 +21 -4
- 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 +6 -0
- 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 +13 -1
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -1
- package/dist/services/obsidian/obsidian-service.d.ts +7 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
- package/dist/services/obsidian/obsidian-service.js +132 -28
- package/dist/services/obsidian/obsidian-service.js.map +1 -1
- package/dist/services/obsidian/path-policy.d.ts +56 -0
- package/dist/services/obsidian/path-policy.d.ts.map +1 -0
- package/dist/services/obsidian/path-policy.js +154 -0
- package/dist/services/obsidian/path-policy.js.map +1 -0
- package/package.json +7 -8
- package/server.json +41 -3
package/CLAUDE.md
CHANGED
|
@@ -34,7 +34,8 @@ 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
|
-
- **Command-palette tools are opt-in.** `obsidian_list_commands` and `obsidian_execute_command` are
|
|
37
|
+
- **Command-palette tools are opt-in.** `obsidian_list_commands` and `obsidian_execute_command` are callable only when `OBSIDIAN_ENABLE_COMMANDS=true` — Obsidian commands are opaque and can be destructive. When the flag is unset, the entry point wraps both with `disabledTool()` so they're absent from `tools/list` (LLM can't invoke) but visible in the operator-facing manifest with a hint to enable them.
|
|
38
|
+
- **Path-policy gating goes through `PathPolicy`.** Every path-taking method on `ObsidianService` calls `policy.assertReadable` / `assertWritable` before the upstream HTTP call; `obsidian_search_notes` post-filters hits via `svc.policy.filterReadable`. Don't bypass this — `OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are the single chokepoint, and `path_forbidden` is declared on every path-taking tool's `errors[]` contract.
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
@@ -138,6 +139,10 @@ const ServerConfigSchema = z.object({
|
|
|
138
139
|
verifySsl: envBoolean.default(false),
|
|
139
140
|
requestTimeoutMs: z.coerce.number().int().positive().default(30_000),
|
|
140
141
|
enableCommands: envBoolean.default(false),
|
|
142
|
+
/** Path-policy allowlists — comma-separated, prefix-based, case-insensitive. Unset = full vault. */
|
|
143
|
+
readPaths: envPathList,
|
|
144
|
+
writePaths: envPathList,
|
|
145
|
+
readOnly: envBoolean.default(false),
|
|
141
146
|
});
|
|
142
147
|
|
|
143
148
|
let _config: z.infer<typeof ServerConfigSchema> | undefined;
|
|
@@ -148,6 +153,9 @@ export function getServerConfig() {
|
|
|
148
153
|
verifySsl: 'OBSIDIAN_VERIFY_SSL',
|
|
149
154
|
requestTimeoutMs: 'OBSIDIAN_REQUEST_TIMEOUT_MS',
|
|
150
155
|
enableCommands: 'OBSIDIAN_ENABLE_COMMANDS',
|
|
156
|
+
readPaths: 'OBSIDIAN_READ_PATHS',
|
|
157
|
+
writePaths: 'OBSIDIAN_WRITE_PATHS',
|
|
158
|
+
readOnly: 'OBSIDIAN_READ_ONLY',
|
|
151
159
|
});
|
|
152
160
|
return _config;
|
|
153
161
|
}
|
|
@@ -177,25 +185,33 @@ The framework also provides `ctx.state`, `ctx.sample`, and `ctx.progress`. They
|
|
|
177
185
|
|
|
178
186
|
Handlers throw — the framework catches, classifies, and formats.
|
|
179
187
|
|
|
180
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
188
|
+
**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
189
|
|
|
182
190
|
```ts
|
|
183
191
|
errors: [
|
|
184
|
-
{ reason: 'note_missing', code: JsonRpcErrorCode.NotFound,
|
|
185
|
-
|
|
192
|
+
{ reason: 'note_missing', code: JsonRpcErrorCode.NotFound,
|
|
193
|
+
when: 'No note matched the path',
|
|
194
|
+
recovery: 'Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.' },
|
|
195
|
+
{ reason: 'plugin_unreachable', code: JsonRpcErrorCode.ServiceUnavailable,
|
|
196
|
+
when: 'Local REST API plugin is offline', retryable: true,
|
|
197
|
+
recovery: 'Confirm Obsidian is running with the Local REST API plugin enabled.' },
|
|
186
198
|
],
|
|
187
199
|
async handler(input, ctx) {
|
|
188
200
|
const note = await svc.getNote(input.path, ctx);
|
|
189
|
-
|
|
201
|
+
// Static recovery — pulled from the contract via ctx.recoveryFor.
|
|
202
|
+
if (!note) throw ctx.fail('note_missing', `Note ${input.path} not found`, {
|
|
203
|
+
...ctx.recoveryFor('note_missing'),
|
|
204
|
+
});
|
|
190
205
|
return note;
|
|
191
206
|
}
|
|
192
207
|
```
|
|
193
208
|
|
|
194
|
-
Services
|
|
209
|
+
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
210
|
|
|
196
211
|
```ts
|
|
197
212
|
// inside obsidian-service.ts
|
|
198
|
-
throw notFound(
|
|
213
|
+
throw notFound(`Not found: ${display}`, data('note_missing'));
|
|
214
|
+
// where data(reason) does: { path, reason, ...ctx.recoveryFor(reason), upstream? }
|
|
199
215
|
```
|
|
200
216
|
|
|
201
217
|
**Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code without a contract): use error factories.
|
|
@@ -279,6 +295,7 @@ Available skills:
|
|
|
279
295
|
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
280
296
|
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
281
297
|
| `api-auth` | Auth modes, scopes, JWT/OAuth |
|
|
298
|
+
| `api-canvas` | DataCanvas SQL workspace (Tier 3, DuckDB) — not used by this server |
|
|
282
299
|
| `api-config` | AppConfig, parseConfig, env vars |
|
|
283
300
|
| `api-context` | Context interface, logger, state, progress |
|
|
284
301
|
| `api-errors` | McpError, JsonRpcErrorCode, error patterns |
|
|
@@ -306,8 +323,6 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
306
323
|
| `bun run format` | Auto-fix formatting (Biome) |
|
|
307
324
|
| `bun run lint:mcp` | Validate MCP definitions against the linter rules |
|
|
308
325
|
| `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
326
|
| `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
|
|
312
327
|
| `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
|
|
313
328
|
| `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
|
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
|
|
|
@@ -126,6 +126,29 @@ Dispatch an Obsidian command-palette command by ID (discoverable via `obsidian_l
|
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
129
|
+
## Path policy (folder-scoped permissions)
|
|
130
|
+
|
|
131
|
+
Three optional env vars gate which vault paths each tool can target. **Default unset = full vault** for both reads and writes — backwards compatible.
|
|
132
|
+
|
|
133
|
+
| Goal | Config |
|
|
134
|
+
|:---|:---|
|
|
135
|
+
| Default (current behavior) | all unset |
|
|
136
|
+
| Read everywhere, write only in `projects/` and `scratch/` | `OBSIDIAN_WRITE_PATHS=projects/,scratch/` |
|
|
137
|
+
| Read only `public/`, write only `public/inbox/` | `OBSIDIAN_READ_PATHS=public/`, `OBSIDIAN_WRITE_PATHS=public/inbox/` |
|
|
138
|
+
| Read-only deployment — no writes anywhere | `OBSIDIAN_READ_ONLY=true` |
|
|
139
|
+
|
|
140
|
+
**Matching is prefix-based with implicit recursion**, case-insensitive, with trailing slashes normalized. `projects/` matches `projects/a.md`, `projects/sub/b.md`, etc.
|
|
141
|
+
|
|
142
|
+
**Write paths are implicitly readable** — you can't sanely edit what you can't see. So a read passes when the target matches `READ_PATHS` *or* `WRITE_PATHS`.
|
|
143
|
+
|
|
144
|
+
**`OBSIDIAN_READ_ONLY=true` short-circuits before the path checks** — every write is denied regardless of `WRITE_PATHS`, and the command-palette pair is unregistered regardless of `OBSIDIAN_ENABLE_COMMANDS` (commands can mutate).
|
|
145
|
+
|
|
146
|
+
Denies are typed `path_forbidden` (JSON-RPC code `Forbidden`) with the active scope echoed back in `data.recovery.hint` and `data.activeScope`, so the LLM can self-correct without inspecting server logs. Search results from `obsidian_search_notes` are filtered against `READ_PATHS` silently — surfacing a "we hid N hits" indicator would defeat the gate.
|
|
147
|
+
|
|
148
|
+
The startup banner logs the active scope so operators can verify their config at boot.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
129
152
|
## Resources
|
|
130
153
|
|
|
131
154
|
| Type | URI | Description |
|
|
@@ -155,6 +178,7 @@ Obsidian-specific:
|
|
|
155
178
|
- Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping fenced code blocks)
|
|
156
179
|
- Search across three modes: text, Dataview DQL, JSONLogic — with overflow indicator when results exceed the 100-hit cap
|
|
157
180
|
- Optional human-in-the-loop confirmation for destructive deletes via `ctx.elicit`
|
|
181
|
+
- 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
|
|
158
182
|
- Opt-in command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`) — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`
|
|
159
183
|
- 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
184
|
|
|
@@ -247,6 +271,9 @@ MCP_TRANSPORT_TYPE=http OBSIDIAN_API_KEY=... bun run start:http
|
|
|
247
271
|
| `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
272
|
| `OBSIDIAN_REQUEST_TIMEOUT_MS` | Per-request timeout in milliseconds. | `30000` |
|
|
249
273
|
| `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` |
|
|
274
|
+
| `OBSIDIAN_READ_PATHS` | Comma-separated vault-relative folder allowlist for read operations. Prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault. Write paths are implicitly readable. | unset |
|
|
275
|
+
| `OBSIDIAN_WRITE_PATHS` | Comma-separated vault-relative folder allowlist for write operations. Same syntax as `OBSIDIAN_READ_PATHS`. Unset = full vault. | unset |
|
|
276
|
+
| `OBSIDIAN_READ_ONLY` | Global kill switch. When `true`, denies every write regardless of `OBSIDIAN_WRITE_PATHS`, and suppresses the `OBSIDIAN_ENABLE_COMMANDS` pair (commands can mutate). | `false` |
|
|
250
277
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
251
278
|
| `MCP_HTTP_HOST` | Host for the HTTP server. | `127.0.0.1` |
|
|
252
279
|
| `MCP_HTTP_PORT` | Port for the HTTP server. | `3010` |
|
|
@@ -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`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: Folder-scoped read/write permissions and a global read-only kill switch — three opt-in env vars (OBSIDIAN_READ_PATHS, OBSIDIAN_WRITE_PATHS, OBSIDIAN_READ_ONLY) gate every path-taking tool and resource, with a new path_forbidden error reason.
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 3.1.2 — 2026-05-03
|
|
7
|
+
|
|
8
|
+
Three opt-in env vars now scope which vault paths read and write tools may target. Closes [#40](https://github.com/cyanheads/obsidian-mcp-server/issues/40); resolves the original ask in [discussion #15](https://github.com/cyanheads/obsidian-mcp-server/discussions/15#discussioncomment-13918526). Backwards compatible — all three default unset / false, preserving current full-vault behavior.
|
|
9
|
+
|
|
10
|
+
## Added
|
|
11
|
+
|
|
12
|
+
- **`OBSIDIAN_READ_PATHS` / `OBSIDIAN_WRITE_PATHS`** — comma-separated vault-relative folder allowlists. Prefix-based with implicit recursion (`projects/` matches `projects/a.md`, `projects/sub/b.md`, …); case-insensitive; trailing slashes normalized; deduplicated. Empty / whitespace-only input falls back to unset; separator-only input (`,`, `,,,`) and absolute paths or `..` traversal throw a `ConfigurationError` at startup naming the offending env var.
|
|
13
|
+
- **`OBSIDIAN_READ_ONLY`** — global kill switch. When `true`, denies every write regardless of `OBSIDIAN_WRITE_PATHS`, and suppresses the `OBSIDIAN_ENABLE_COMMANDS` pair (commands can mutate). Useful for shared or public-facing deployments.
|
|
14
|
+
- **`path_forbidden` error reason** declared on every path-taking tool's `errors[]` contract — mapped to `JsonRpcErrorCode.Forbidden`. The wire payload carries `{ path, op, subreason, activeScope, recovery }` where `subreason` is one of `outside_read_paths`, `outside_write_paths`, or `read_only_mode`, and the recovery hint echoes the active scope so the LLM can self-correct without inspecting server logs.
|
|
15
|
+
- **Startup banner** — `core.logger.info('Path policy', …)` echoes the active scope and command-palette enable state at boot, so operators can verify their config. Emits a warning when `OBSIDIAN_READ_ONLY=true` AND `OBSIDIAN_WRITE_PATHS` is non-empty (the boolean wins; the paths are ignored).
|
|
16
|
+
- **`PathPolicy`** module (`src/services/obsidian/path-policy.ts`) — single chokepoint that decides per path: `isReadable`, `isWritable`, `assertReadable`, `assertWritable`, plus a silent `filterReadable` for search post-filtering.
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
|
|
20
|
+
- **`ObsidianService` gates every path-taking method** before the upstream HTTP call: `getNoteContent`, `getNoteJson`, `getDocumentMap`, `listFiles`, `openInUi` (read), and `writeNote`, `appendToNote`, `patchNote`, `deleteNote` (write). For non-`path` targets (`active`, `periodic`) under restrictions, the service routes through a JSON resolution fetch first to learn the path, then gates on the resolved path — costs one extra fetch only for users who configured a path scope.
|
|
21
|
+
- **`obsidian_search_notes` post-filters hits against `OBSIDIAN_READ_PATHS`** silently — surfacing a "we hid N hits" indicator would defeat the gate. The `excluded` overflow indicator continues to count hits trimmed by the 100-hit cap, not policy-dropped hits.
|
|
22
|
+
- **Tool registration in `src/index.ts`** splits read-only tools from write tools at the barrel and wraps the write set with `disabledTool()` when `OBSIDIAN_READ_ONLY=true`. The command-palette pair stays wrapped with `disabledTool()` whenever it's not callable (either `OBSIDIAN_ENABLE_COMMANDS=false` or `OBSIDIAN_READ_ONLY=true`), with a `hint` explaining how to enable it. The LLM never sees disabled tools in `tools/list`; they remain visible in the operator-facing manifest.
|
|
23
|
+
- **`obsidian_list_notes` listing semantics** — the vault root always lists, even with `OBSIDIAN_READ_PATHS` set. Specific subdirectories outside the read scope throw `path_forbidden`. Children aren't filtered at the service level; the per-file read gate on subsequent reads (`obsidian_get_note`, etc.) still catches access to out-of-scope notes.
|
|
@@ -7,9 +7,12 @@ import { z } from '@cyanheads/mcp-ts-core';
|
|
|
7
7
|
declare const ServerConfigSchema: z.ZodObject<{
|
|
8
8
|
apiKey: z.ZodString;
|
|
9
9
|
baseUrl: z.ZodDefault<z.ZodString>;
|
|
10
|
-
verifySsl: z.ZodDefault<z.
|
|
10
|
+
verifySsl: z.ZodDefault<z.ZodPreprocess<z.ZodBoolean>>;
|
|
11
11
|
requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
12
|
-
enableCommands: z.ZodDefault<z.
|
|
12
|
+
enableCommands: z.ZodDefault<z.ZodPreprocess<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>>>;
|
|
15
|
+
readOnly: z.ZodDefault<z.ZodPreprocess<z.ZodBoolean>>;
|
|
13
16
|
}, z.core.$strip>;
|
|
14
17
|
export type ServerConfig = z.infer<typeof ServerConfigSchema>;
|
|
15
18
|
export declare function getServerConfig(): ServerConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AA4E3C,QAAA,MAAM,kBAAkB;;;;;;;;;iBAyCtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAY9C;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -14,6 +14,71 @@ const envBoolean = z.preprocess((val) => {
|
|
|
14
14
|
}
|
|
15
15
|
return val;
|
|
16
16
|
}, z.boolean());
|
|
17
|
+
/**
|
|
18
|
+
* Comma-separated path-list preprocessor. Semantics (see issue #40):
|
|
19
|
+
* - undefined / `''` / whitespace-only → undefined (treat as unset; default = full vault)
|
|
20
|
+
* - `,` / `,,,` (separators only, no path content) → throw ZodError → ConfigurationError
|
|
21
|
+
* - mixed empties (`a,,b`) → drop empties → `['a', 'b']`
|
|
22
|
+
* - absolute path / `..` traversal → throw
|
|
23
|
+
* - valid: lower-case + trim trailing slash + dedupe (preserves first occurrence order)
|
|
24
|
+
*/
|
|
25
|
+
const envPathList = z
|
|
26
|
+
.preprocess((val) => {
|
|
27
|
+
if (val === undefined || val === null)
|
|
28
|
+
return;
|
|
29
|
+
if (Array.isArray(val))
|
|
30
|
+
return val;
|
|
31
|
+
if (typeof val !== 'string')
|
|
32
|
+
return val;
|
|
33
|
+
if (val.trim() === '')
|
|
34
|
+
return;
|
|
35
|
+
const parts = val
|
|
36
|
+
.split(',')
|
|
37
|
+
.map((s) => s.trim())
|
|
38
|
+
.filter((s) => s.length > 0);
|
|
39
|
+
return parts;
|
|
40
|
+
}, z
|
|
41
|
+
.array(z.string())
|
|
42
|
+
.optional()
|
|
43
|
+
.transform((parts, ctx) => {
|
|
44
|
+
if (parts === undefined)
|
|
45
|
+
return;
|
|
46
|
+
if (parts.length === 0) {
|
|
47
|
+
ctx.addIssue({
|
|
48
|
+
code: 'custom',
|
|
49
|
+
message: 'contained separators but no valid paths after trimming',
|
|
50
|
+
});
|
|
51
|
+
return z.NEVER;
|
|
52
|
+
}
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const raw of parts) {
|
|
56
|
+
if (raw.startsWith('/') || raw.startsWith('\\')) {
|
|
57
|
+
ctx.addIssue({
|
|
58
|
+
code: 'custom',
|
|
59
|
+
message: `must be vault-relative; got absolute path '${raw}'`,
|
|
60
|
+
});
|
|
61
|
+
return z.NEVER;
|
|
62
|
+
}
|
|
63
|
+
const segments = raw.split(/[\\/]/);
|
|
64
|
+
if (segments.includes('..')) {
|
|
65
|
+
ctx.addIssue({
|
|
66
|
+
code: 'custom',
|
|
67
|
+
message: `must not contain '..' traversal; got '${raw}'`,
|
|
68
|
+
});
|
|
69
|
+
return z.NEVER;
|
|
70
|
+
}
|
|
71
|
+
const normalized = raw.toLowerCase().replace(/[\\/]+$/, '');
|
|
72
|
+
if (normalized.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
if (seen.has(normalized))
|
|
75
|
+
continue;
|
|
76
|
+
seen.add(normalized);
|
|
77
|
+
out.push(normalized);
|
|
78
|
+
}
|
|
79
|
+
return out.length > 0 ? out : undefined;
|
|
80
|
+
}))
|
|
81
|
+
.describe('Comma-separated list of vault-relative folder prefixes. Empty/whitespace falls back to unset.');
|
|
17
82
|
const ServerConfigSchema = z.object({
|
|
18
83
|
apiKey: z
|
|
19
84
|
.string()
|
|
@@ -36,6 +101,11 @@ const ServerConfigSchema = z.object({
|
|
|
36
101
|
enableCommands: envBoolean
|
|
37
102
|
.default(false)
|
|
38
103
|
.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.'),
|
|
104
|
+
readPaths: envPathList.describe('Optional vault-relative folder allowlist for read operations. Comma-separated; prefix-based with implicit recursion; case-insensitive; trailing slashes normalized. Unset = full vault.'),
|
|
105
|
+
writePaths: envPathList.describe('Optional vault-relative folder allowlist for write operations. Same syntax as OBSIDIAN_READ_PATHS. Write paths are implicitly readable. Unset = full vault.'),
|
|
106
|
+
readOnly: envBoolean
|
|
107
|
+
.default(false)
|
|
108
|
+
.describe('Global kill switch. When true, denies every write regardless of OBSIDIAN_WRITE_PATHS, and suppresses the OBSIDIAN_ENABLE_COMMANDS pair (commands can mutate). Defaults to false.'),
|
|
39
109
|
});
|
|
40
110
|
let _config;
|
|
41
111
|
export function getServerConfig() {
|
|
@@ -45,6 +115,9 @@ export function getServerConfig() {
|
|
|
45
115
|
verifySsl: 'OBSIDIAN_VERIFY_SSL',
|
|
46
116
|
requestTimeoutMs: 'OBSIDIAN_REQUEST_TIMEOUT_MS',
|
|
47
117
|
enableCommands: 'OBSIDIAN_ENABLE_COMMANDS',
|
|
118
|
+
readPaths: 'OBSIDIAN_READ_PATHS',
|
|
119
|
+
writePaths: 'OBSIDIAN_WRITE_PATHS',
|
|
120
|
+
readOnly: 'OBSIDIAN_READ_ONLY',
|
|
48
121
|
});
|
|
49
122
|
return _config;
|
|
50
123
|
}
|
|
@@ -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,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;
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,16 +6,34 @@
|
|
|
6
6
|
* @module index
|
|
7
7
|
*/
|
|
8
8
|
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
9
|
+
import { disabledTool } from '@cyanheads/mcp-ts-core/tools';
|
|
10
|
+
import { requestContextService } from '@cyanheads/mcp-ts-core/utils';
|
|
9
11
|
import { getServerConfig } from './config/server-config.js';
|
|
10
12
|
import { allPromptDefinitions } from './mcp-server/prompts/definitions/index.js';
|
|
11
13
|
import { allResourceDefinitions } from './mcp-server/resources/definitions/index.js';
|
|
12
|
-
import {
|
|
14
|
+
import { commandToolDefinitions, readToolDefinitions, writeToolDefinitions, } from './mcp-server/tools/definitions/index.js';
|
|
13
15
|
import { initObsidianService } from './services/obsidian/obsidian-service.js';
|
|
16
|
+
import { PathPolicy } from './services/obsidian/path-policy.js';
|
|
14
17
|
const config = getServerConfig();
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const policy = new PathPolicy(config);
|
|
19
|
+
const writeTools = config.readOnly
|
|
20
|
+
? writeToolDefinitions.map((def) => disabledTool(def, {
|
|
21
|
+
reason: 'Disabled by OBSIDIAN_READ_ONLY=true.',
|
|
22
|
+
hint: 'Unset OBSIDIAN_READ_ONLY (or set it to false) to enable write tools.',
|
|
23
|
+
}))
|
|
24
|
+
: writeToolDefinitions;
|
|
25
|
+
const commandTools = config.enableCommands && !config.readOnly
|
|
26
|
+
? commandToolDefinitions
|
|
27
|
+
: commandToolDefinitions.map((def) => disabledTool(def, {
|
|
28
|
+
reason: config.readOnly
|
|
29
|
+
? 'Disabled by OBSIDIAN_READ_ONLY=true (commands can mutate).'
|
|
30
|
+
: 'Disabled by default — Obsidian commands are opaque and can be destructive.',
|
|
31
|
+
hint: config.readOnly
|
|
32
|
+
? 'Unset OBSIDIAN_READ_ONLY to allow commands; OBSIDIAN_ENABLE_COMMANDS=true is also required.'
|
|
33
|
+
: 'Set OBSIDIAN_ENABLE_COMMANDS=true to enable obsidian_list_commands and obsidian_execute_command.',
|
|
34
|
+
}));
|
|
35
|
+
const tools = [...readToolDefinitions, ...writeTools, ...commandTools];
|
|
36
|
+
const { services } = await createApp({
|
|
19
37
|
tools,
|
|
20
38
|
resources: allResourceDefinitions,
|
|
21
39
|
prompts: allPromptDefinitions,
|
|
@@ -23,4 +41,20 @@ await createApp({
|
|
|
23
41
|
initObsidianService(config);
|
|
24
42
|
},
|
|
25
43
|
});
|
|
44
|
+
/**
|
|
45
|
+
* Startup banner — emitted after createApp() returns so the framework's
|
|
46
|
+
* `logger.initialize()` has run; calls inside `setup()` happen pre-init and
|
|
47
|
+
* are dropped. Operators check this against their config to verify the active
|
|
48
|
+
* path policy. The active scope on `path_forbidden` errors echoes the same
|
|
49
|
+
* data so the LLM (or operator) can self-correct without scrolling the log.
|
|
50
|
+
*/
|
|
51
|
+
const bannerCtx = requestContextService.createRequestContext({
|
|
52
|
+
operation: 'startup',
|
|
53
|
+
...policy.describe(),
|
|
54
|
+
enableCommands: config.enableCommands && !config.readOnly,
|
|
55
|
+
});
|
|
56
|
+
services.logger.info('Path policy', bannerCtx);
|
|
57
|
+
if (policy.readOnlyShadowsWritePaths) {
|
|
58
|
+
services.logger.warning('OBSIDIAN_WRITE_PATHS is set but ignored because OBSIDIAN_READ_ONLY=true. Unset one of the two to remove the conflict.', bannerCtx);
|
|
59
|
+
}
|
|
26
60
|
//# sourceMappingURL=index.js.map
|
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,
|
|
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,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,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,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;AACjC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAEtC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ;IAChC,CAAC,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC/B,YAAY,CAAC,GAAG,EAAE;QAChB,MAAM,EAAE,sCAAsC;QAC9C,IAAI,EAAE,sEAAsE;KAC7E,CAAC,CACH;IACH,CAAC,CAAC,oBAAoB,CAAC;AAEzB,MAAM,YAAY,GAChB,MAAM,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,QAAQ;IACvC,CAAC,CAAC,sBAAsB;IACxB,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACjC,YAAY,CAAC,GAAG,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC,QAAQ;YACrB,CAAC,CAAC,4DAA4D;YAC9D,CAAC,CAAC,4EAA4E;QAChF,IAAI,EAAE,MAAM,CAAC,QAAQ;YACnB,CAAC,CAAC,6FAA6F;YAC/F,CAAC,CAAC,kGAAkG;KACvG,CAAC,CACH,CAAC;AAER,MAAM,KAAK,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,UAAU,EAAE,GAAG,YAAY,CAAC,CAAC;AAEvE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,SAAS,CAAC;IACnC,KAAK;IACL,SAAS,EAAE,sBAAsB;IACjC,OAAO,EAAE,oBAAoB;IAC7B,KAAK;QACH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,SAAS,GAAG,qBAAqB,CAAC,oBAAoB,CAAC;IAC3D,SAAS,EAAE,SAAS;IACpB,GAAG,MAAM,CAAC,QAAQ,EAAE;IACpB,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,QAAQ;CAC1D,CAAC,CAAC;AACH,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;AAC/C,IAAI,MAAM,CAAC,yBAAyB,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,OAAO,CACrB,uHAAuH,EACvH,SAAS,CACV,CAAC;AACJ,CAAC"}
|