obsidian-mcp-server 3.2.2 → 3.2.4

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 (45) hide show
  1. package/AGENTS.md +409 -0
  2. package/CLAUDE.md +17 -7
  3. package/README.md +6 -6
  4. package/changelog/3.2.x/3.2.3.md +29 -0
  5. package/changelog/3.2.x/3.2.4.md +29 -0
  6. package/changelog/template.md +35 -1
  7. package/dist/mcp-server/tools/definitions/index.d.ts +17 -13
  8. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  9. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +1 -1
  10. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -1
  11. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +1 -1
  12. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
  13. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +1 -1
  14. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +1 -1
  16. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +1 -1
  18. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +3 -1
  20. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +12 -0
  22. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +3 -1
  24. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +16 -7
  26. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -1
  27. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +1 -1
  28. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +1 -1
  30. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +1 -1
  32. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +1 -1
  34. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +1 -1
  36. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +8 -2
  38. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +26 -7
  40. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
  41. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +1 -1
  42. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  43. package/manifest.json +13 -3
  44. package/package.json +9 -8
  45. package/server.json +4 -4
package/AGENTS.md ADDED
@@ -0,0 +1,409 @@
1
+ # Agent Protocol
2
+
3
+ **Server:** obsidian-mcp-server
4
+ **Version:** 3.2.4
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.21`
6
+ **Engines:** Bun ≥1.3.11, Node ≥24.0.0
7
+ **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
+ **Zod:** ^4.4.3
9
+
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
+
12
+ ---
13
+
14
+ ## What's Next?
15
+
16
+ When the user asks what to do next, what's left, or needs direction, suggest relevant options based on the current project state:
17
+
18
+ 1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
19
+ 2. **Run the `design-mcp-server` skill** — if the tool/resource surface hasn't been mapped yet, work through domain design
20
+ 3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
21
+ 4. **Add services** — scaffold domain service integrations using the `add-service` skill
22
+ 5. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
23
+ 6. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
24
+ 7. **Run `devcheck`** — lint, format, typecheck, and security audit
25
+ 8. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
26
+ 9. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
27
+ 10. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
28
+
29
+ Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
30
+
31
+ ---
32
+
33
+ ## Core Rules
34
+
35
+ - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
36
+ - **Use `ctx.log`** for request-scoped logging. No `console` calls.
37
+ - **Check `ctx.elicit`** for presence before calling — used by `obsidian_delete_note` to confirm destructive ops.
38
+ - **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.
39
+ - **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
40
+ - **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.
41
+ - **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.
42
+ - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
43
+
44
+ ---
45
+
46
+ ## Patterns
47
+
48
+ ### Tool — `obsidian_list_tags`
49
+
50
+ A small read-only tool that wraps a single upstream endpoint, normalizes the response into the output schema, and renders a markdown twin in `format()`.
51
+
52
+ ```ts
53
+ import { tool, z } from '@cyanheads/mcp-ts-core';
54
+ import { getObsidianService } from '@/services/obsidian/obsidian-service.js';
55
+
56
+ export const obsidianListTags = tool('obsidian_list_tags', {
57
+ description:
58
+ 'List every tag found across the vault, with usage counts. Includes hierarchical parents — `work/tasks` contributes to both `work` and `work/tasks`.',
59
+ annotations: { readOnlyHint: true, idempotentHint: true },
60
+ input: z.object({}),
61
+ output: z.object({
62
+ tags: z
63
+ .array(
64
+ z.object({
65
+ name: z.string().describe('Tag name without the leading `#`.'),
66
+ count: z.number().describe('Usage count across the vault.'),
67
+ }).describe('A tag with its usage count.'),
68
+ )
69
+ .describe('All tags in the vault, in upstream-provided order.'),
70
+ }),
71
+ auth: ['tool:obsidian_list_tags:read'],
72
+
73
+ async handler(_input, ctx) {
74
+ const svc = getObsidianService();
75
+ const tags = await svc.listTags(ctx);
76
+ return { tags: tags.map((t) => ({ name: t.name, count: t.count })) };
77
+ },
78
+
79
+ // format() populates content[] — the markdown twin of structuredContent.
80
+ // Different clients read different surfaces (Claude Code → structuredContent,
81
+ // Claude Desktop → content[]); both must carry the same data.
82
+ // Enforced at lint time: every field in `output` must appear in the rendered text.
83
+ format: (result) => {
84
+ if (result.tags.length === 0) {
85
+ return [{ type: 'text', text: '_No tags found in the vault._' }];
86
+ }
87
+ const lines = [`**${result.tags.length} tags**`, ''];
88
+ for (const t of result.tags) lines.push(`- \`#${t.name}\` (${t.count})`);
89
+ return [{ type: 'text', text: lines.join('\n') }];
90
+ },
91
+ });
92
+ ```
93
+
94
+ For a destructive tool with optional human-in-the-loop confirmation, see `obsidian-delete-note.tool.ts` — it uses `ctx.elicit` when present and falls back to the `destructiveHint` annotation otherwise.
95
+
96
+ ### Resource — `obsidian://status`
97
+
98
+ ```ts
99
+ import { resource, z } from '@cyanheads/mcp-ts-core';
100
+ import { getObsidianService } from '@/services/obsidian/obsidian-service.js';
101
+
102
+ export const obsidianStatus = resource('obsidian://status', {
103
+ name: 'obsidian-status',
104
+ description:
105
+ 'Server reachability, plugin version, and auth status of the Obsidian Local REST API.',
106
+ mimeType: 'application/json',
107
+ params: z.object({}),
108
+ output: z.object({
109
+ status: z.string().describe('Upstream reported status string.'),
110
+ service: z.string().describe('Service identifier returned by the plugin.'),
111
+ authenticated: z.boolean().describe('Whether the configured OBSIDIAN_API_KEY is recognized.'),
112
+ }),
113
+ auth: ['resource:obsidian-status:read'],
114
+ async handler(_params, ctx) {
115
+ const svc = getObsidianService();
116
+ return await svc.getStatus(ctx);
117
+ },
118
+ });
119
+ ```
120
+
121
+ For a parameterized resource, see `obsidian-vault-note.resource.ts` (`obsidian://vault/{+path}`) — the `{+path}` segment captures everything after `/vault/` including slashes.
122
+
123
+ ### Prompt
124
+
125
+ This server exposes a CRUD/search surface; no recurring multi-turn pattern benefits from a structured prompt template, so `allPromptDefinitions` is intentionally empty. Add one with `prompt('name', { ... })` if a workflow emerges.
126
+
127
+ ### Server config — `OBSIDIAN_*` env vars
128
+
129
+ ```ts
130
+ // src/config/server-config.ts — lazy-parsed, separate from framework config
131
+ import { z } from '@cyanheads/mcp-ts-core';
132
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
133
+
134
+ const envBoolean = z.preprocess((val) => {
135
+ if (val === undefined || val === null || val === '') return;
136
+ if (typeof val === 'boolean') return val;
137
+ return String(val).toLowerCase().trim() === 'true' || val === '1';
138
+ }, z.boolean());
139
+
140
+ const ServerConfigSchema = z.object({
141
+ apiKey: z.string().min(1).describe('Bearer token for the Obsidian Local REST API plugin.'),
142
+ baseUrl: z.string().url().default('http://127.0.0.1:27123'),
143
+ verifySsl: envBoolean.default(false),
144
+ requestTimeoutMs: z.coerce.number().int().positive().default(30_000),
145
+ enableCommands: envBoolean.default(false),
146
+ /** Path-policy allowlists — comma-separated, prefix-based, case-insensitive. Unset = full vault. */
147
+ readPaths: envPathList,
148
+ writePaths: envPathList,
149
+ readOnly: envBoolean.default(false),
150
+ });
151
+
152
+ let _config: z.infer<typeof ServerConfigSchema> | undefined;
153
+ export function getServerConfig() {
154
+ _config ??= parseEnvConfig(ServerConfigSchema, {
155
+ apiKey: 'OBSIDIAN_API_KEY',
156
+ baseUrl: 'OBSIDIAN_BASE_URL',
157
+ verifySsl: 'OBSIDIAN_VERIFY_SSL',
158
+ requestTimeoutMs: 'OBSIDIAN_REQUEST_TIMEOUT_MS',
159
+ enableCommands: 'OBSIDIAN_ENABLE_COMMANDS',
160
+ readPaths: 'OBSIDIAN_READ_PATHS',
161
+ writePaths: 'OBSIDIAN_WRITE_PATHS',
162
+ readOnly: 'OBSIDIAN_READ_ONLY',
163
+ });
164
+ return _config;
165
+ }
166
+ ```
167
+
168
+ `parseEnvConfig` maps Zod schema paths → env var names so validation errors name the actual variable (`OBSIDIAN_API_KEY`) rather than the internal path (`apiKey`). It throws a `ConfigurationError` the framework catches and prints as a clean startup banner.
169
+
170
+ ---
171
+
172
+ ## Context
173
+
174
+ Handlers receive a unified `ctx` object. Properties this server actually uses:
175
+
176
+ | Property | Description |
177
+ |:---------|:------------|
178
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
179
+ | `ctx.elicit` | Optional human-in-the-loop confirmation. **Check for presence first** — used by `obsidian_delete_note` to confirm destructive operations when the client supports elicitation. |
180
+ | `ctx.signal` | `AbortSignal` propagated to the Local REST API client so per-request timeouts and client cancellations cut off in-flight HTTP. |
181
+ | `ctx.requestId` | Unique request ID — surfaces in log lines for correlation. |
182
+ | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
183
+
184
+ The framework also provides `ctx.state`, `ctx.sample`, and `ctx.progress`. They aren't used by this server — Obsidian is single-vault and stateless from the server's perspective, so per-tenant KV and progress streams aren't needed. See the framework `CLAUDE.md` for the full surface.
185
+
186
+ ---
187
+
188
+ ## Errors
189
+
190
+ Handlers throw — the framework catches, classifies, and formats.
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.
193
+
194
+ ```ts
195
+ errors: [
196
+ { reason: 'note_missing', code: JsonRpcErrorCode.NotFound,
197
+ when: 'No note matched the path',
198
+ recovery: 'Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note.' },
199
+ { reason: 'plugin_unreachable', code: JsonRpcErrorCode.ServiceUnavailable,
200
+ when: 'Local REST API plugin is offline', retryable: true,
201
+ recovery: 'Confirm Obsidian is running with the Local REST API plugin enabled.' },
202
+ ],
203
+ async handler(input, ctx) {
204
+ const note = await svc.getNote(input.path, ctx);
205
+ // Static recovery — pulled from the contract via ctx.recoveryFor.
206
+ if (!note) throw ctx.fail('note_missing', `Note ${input.path} not found`, {
207
+ ...ctx.recoveryFor('note_missing'),
208
+ });
209
+ return note;
210
+ }
211
+ ```
212
+
213
+ **Declare contracts inline on each tool, even when they look similar across tools.** The contract is part of the tool's documented public surface — reading one tool definition file should give the full picture (input, output, errors, handler, format). Don't extract a shared `errors[]` constant or contract module to deduplicate; per-tool repetition is the intended cost of locality, and dynamic `recovery` hints often need tool-specific context anyway.
214
+
215
+ 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:
216
+
217
+ ```ts
218
+ // inside obsidian-service.ts
219
+ throw notFound(`Not found: ${display}`, data('note_missing'));
220
+ // where data(reason) does: { path, reason, ...ctx.recoveryFor(reason), upstream? }
221
+ ```
222
+
223
+ **Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code without a contract): use error factories.
224
+
225
+ ```ts
226
+ import { notFound, validationError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
227
+ throw notFound('Note not found', { path });
228
+ throw serviceUnavailable('Local REST API unavailable', { url }, { cause: err });
229
+ ```
230
+
231
+ For HTTP responses from the Local REST API, use `httpErrorFromResponse(response, { service: 'obsidian-rest' })` from `/utils` — maps the full status table (401/403/408/422/429/5xx) and captures body + `Retry-After`.
232
+
233
+ Available factories: `notFound`, `validationError`, `forbidden`, `unauthorized`, `invalidParams`, `invalidRequest`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`. Plain `Error` is also auto-classified from message patterns (`'not found'` → `NotFound`, etc.). See framework CLAUDE.md and the `api-errors` skill for the full pattern table.
234
+
235
+ ---
236
+
237
+ ## Structure
238
+
239
+ ```text
240
+ src/
241
+ index.ts # createApp() entry point — registers tools/resources, inits Obsidian service
242
+ config/
243
+ server-config.ts # OBSIDIAN_* env vars (Zod schema)
244
+ services/
245
+ obsidian/
246
+ obsidian-service.ts # Local REST API client (init/accessor pattern)
247
+ frontmatter-ops.ts # YAML frontmatter parse/serialize/edit helpers
248
+ section-extractor.ts # Heading/block/frontmatter section extraction
249
+ types.ts # Domain types (NoteJson, NoteTarget, etc.)
250
+ mcp-server/
251
+ tools/definitions/
252
+ _shared/schemas.ts # Shared TargetSchema + SectionSchema reused across tools
253
+ index.ts # read/write/command tool sets + buildSearchNotesTool factory (Omnisearch-aware)
254
+ obsidian-*.tool.ts # 14 tool definitions (12 base + 2 opt-in command-palette pair)
255
+ resources/definitions/
256
+ index.ts # allResourceDefinitions[]
257
+ obsidian-vault-note.resource.ts # obsidian://vault/{+path}
258
+ obsidian-tags.resource.ts # obsidian://tags
259
+ obsidian-status.resource.ts # obsidian://status
260
+ prompts/definitions/
261
+ index.ts # allPromptDefinitions = [] (intentionally empty)
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Naming
267
+
268
+ | What | Convention | Example |
269
+ |:-----|:-----------|:--------|
270
+ | Files | kebab-case with suffix | `search-docs.tool.ts` |
271
+ | Tool/resource/prompt names | snake_case | `search_docs` |
272
+ | Directories | kebab-case | `src/services/doc-search/` |
273
+ | Descriptions | Single string or template literal, no `+` concatenation | `'Search items by query and filter.'` |
274
+
275
+ ---
276
+
277
+ ## Skills
278
+
279
+ Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
280
+
281
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
282
+
283
+ Available skills:
284
+
285
+ | Skill | Purpose |
286
+ |:------|:--------|
287
+ | `setup` | Post-init project orientation |
288
+ | `design-mcp-server` | Design tool surface, resources, and services for a new server |
289
+ | `add-tool` | Scaffold a new tool definition |
290
+ | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
291
+ | `add-resource` | Scaffold a new resource definition |
292
+ | `add-prompt` | Scaffold a new prompt definition |
293
+ | `add-service` | Scaffold a new service integration |
294
+ | `add-test` | Scaffold test file for a tool, resource, or service |
295
+ | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
296
+ | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
297
+ | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
298
+ | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
299
+ | `devcheck` | Lint, format, typecheck, audit |
300
+ | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
301
+ | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
302
+ | `release-and-publish` | Ship a release end-to-end across npm, MCP Registry, GitHub Releases (`.mcpb`), and GHCR |
303
+ | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
304
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
305
+ | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
306
+ | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
307
+ | `api-auth` | Auth modes, scopes, JWT/OAuth |
308
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
309
+ | `api-config` | AppConfig, parseConfig, env vars |
310
+ | `api-context` | Context interface, logger, state, progress |
311
+ | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
312
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
313
+ | `api-services` | LLM, Speech, Graph services |
314
+ | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
315
+ | `api-testing` | createMockContext, test patterns |
316
+ | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
317
+ | `api-workers` | Cloudflare Workers runtime |
318
+
319
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
320
+
321
+ When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
322
+
323
+ ---
324
+
325
+ ## Commands
326
+
327
+ **Runtime:** Scripts use `tsx` — both `bun run <cmd>` and `npm run <cmd>` work. `bun` is preferred (faster startup, native TS).
328
+
329
+ | Command | Purpose |
330
+ |:--------|:--------|
331
+ | `bun run build` | Compile TypeScript |
332
+ | `bun run rebuild` | Clean + build |
333
+ | `bun run clean` | Remove build artifacts |
334
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
335
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
336
+ | `bun run tree` | Generate `docs/tree.md` |
337
+ | `bun run list-skills` | Print project skill index (name, version, description) |
338
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
339
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
340
+ | `bun run lint:mcp` | Validate MCP definitions against the linter rules |
341
+ | `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` |
342
+ | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
343
+ | `bun run test` | Run Vitest tests |
344
+ | `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
345
+ | `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
346
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
347
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
348
+
349
+ ---
350
+
351
+ ## Bundling
352
+
353
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP deployments are unaffected. Delete `manifest.json` and `.mcpbignore` if not shipping MCPB bundles; `lint:packaging` skips cleanly.
354
+
355
+ **Adding an env var requires both files:** `server.json` (`environmentVariables[]`) and `manifest.json` (`mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
356
+
357
+ ---
358
+
359
+ ## Changelog
360
+
361
+ Directory-based, grouped by minor series using the `.x` semver-wildcard convention. Source of truth is `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.1.x/0.1.0.md`) — one file per released version, shipped in the npm package. At release time, author the per-version file with a concrete version and date, then run `npm run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited, never renamed, never moved. Read it to remember the frontmatter + section layout when scaffolding a new per-version file. `CHANGELOG.md` is a **navigation index** (header + link + one-line summary per version), regenerated by `npm run changelog:build`. Devcheck hard-fails on drift. Never hand-edit `CHANGELOG.md`.
362
+
363
+ Each per-version file opens with YAML frontmatter:
364
+
365
+ ```markdown
366
+ ---
367
+ summary: One-line headline, ≤350 chars # required — powers the rollup index
368
+ breaking: false # optional — true flags breaking changes
369
+ security: false # optional — true flags security fixes
370
+ ---
371
+
372
+ # 0.1.0 — YYYY-MM-DD
373
+ ...
374
+ ```
375
+
376
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
377
+
378
+ ---
379
+
380
+ ## Imports
381
+
382
+ ```ts
383
+ // Framework — z is re-exported, no separate zod import needed
384
+ import { tool, z } from '@cyanheads/mcp-ts-core';
385
+ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
386
+
387
+ // Server's own code — via path alias
388
+ import { getMyService } from '@/services/my-domain/my-service.js';
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Checklist
394
+
395
+ - [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`)
396
+ - [ ] Optional nested objects: handler guards for empty inner values from form-based clients (`if (input.obj?.field && ...)`, not just `if (input.obj)`). When regex/length constraints matter, use `z.union([z.literal(''), z.string().regex(...).describe(...)])` — literal variants are exempt from `describe-on-fields`.
397
+ - [ ] JSDoc `@fileoverview` + `@module` on every file
398
+ - [ ] `ctx.log` for logging, `ctx.state` for storage
399
+ - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
400
+ - [ ] `format()` renders all data the LLM needs — different clients forward different surfaces (Claude Code → `structuredContent`, Claude Desktop → `content[]`); both must carry the same data
401
+ - [ ] If wrapping external API: raw/domain/output schemas reviewed against real upstream sparsity/nullability before finalizing required vs optional fields
402
+ - [ ] If wrapping external API: normalization and `format()` preserve uncertainty; do not fabricate facts from missing upstream data
403
+ - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
404
+ - [ ] 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
405
+ - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
406
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
407
+ - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
408
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
409
+ - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.2.2
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.6`
4
+ **Version:** 3.2.4
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.21`
6
6
  **Engines:** Bun ≥1.3.11, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -39,6 +39,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
39
39
  - **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
40
40
  - **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.
41
41
  - **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.
42
+ - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
42
43
 
43
44
  ---
44
45
 
@@ -293,11 +294,14 @@ Available skills:
293
294
  | `add-test` | Scaffold test file for a tool, resource, or service |
294
295
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
295
296
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
296
- | `tool-defs-analysis` | Audit MCP definition language across tools/resources/prompts — voice, leaks, defaults, recovery hints, sparsity, structure |
297
+ | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
298
+ | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
299
+ | `devcheck` | Lint, format, typecheck, audit |
297
300
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
301
+ | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
298
302
  | `release-and-publish` | Ship a release end-to-end across npm, MCP Registry, GitHub Releases (`.mcpb`), and GHCR |
299
303
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
300
- | `migrate-mcp-ts-template` | Migrate a `mcp-ts-template` fork to depend on `@cyanheads/mcp-ts-core` as a package |
304
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline build-out, QA-fix, update-ship when you can spawn sub-agents |
301
305
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
302
306
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
303
307
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
@@ -305,13 +309,15 @@ Available skills:
305
309
  | `api-config` | AppConfig, parseConfig, env vars |
306
310
  | `api-context` | Context interface, logger, state, progress |
307
311
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
308
- | `api-linter` | MCP definition linter rule reference (`bun run lint:mcp` failures) |
312
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
309
313
  | `api-services` | LLM, Speech, Graph services |
310
314
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
311
315
  | `api-testing` | createMockContext, test patterns |
312
316
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
313
317
  | `api-workers` | Cloudflare Workers runtime |
314
318
 
319
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
320
+
315
321
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
316
322
 
317
323
  ---
@@ -326,10 +332,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
326
332
  | `bun run rebuild` | Clean + build |
327
333
  | `bun run clean` | Remove build artifacts |
328
334
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
329
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
335
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
330
336
  | `bun run tree` | Generate `docs/tree.md` |
331
337
  | `bun run list-skills` | Print project skill index (name, version, description) |
332
- | `bun run format` | Auto-fix formatting (Biome) |
338
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
339
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
333
340
  | `bun run lint:mcp` | Validate MCP definitions against the linter rules |
334
341
  | `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` |
335
342
  | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
@@ -396,4 +403,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
396
403
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
397
404
  - [ ] 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
398
405
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
406
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
407
+ - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
408
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
399
409
  - [ ] `bun run devcheck` passes
package/README.md CHANGED
@@ -7,13 +7,13 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-3.2.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-^1.29.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-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-3.2.4-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-^1.29.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-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
14
14
  <div align="center">
15
15
 
16
- [![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/obsidian-mcp-server/releases/latest/download/obsidian-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=obsidian-mcp-server&config=eyJjb21tYW5kIjoibnB4IC15IG9ic2lkaWFuLW1jcC1zZXJ2ZXIifQ==) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22obsidian-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22obsidian-mcp-server%22%5D%7D)
16
+ [![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/obsidian-mcp-server/releases/latest/download/obsidian-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=obsidian-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvb2JzaWRpYW4tbWNwLXNlcnZlciJdLCJlbnYiOnsiT0JTSURJQU5fQVBJX0tFWSI6InlvdXItYXBpLWtleSJ9fQ==) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22obsidian-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads/obsidian-mcp-server%22%5D%2C%22env%22%3A%7B%22OBSIDIAN_API_KEY%22%3A%22your-api-key%22%7D%7D)
17
17
 
18
18
  [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
19
19
 
@@ -63,7 +63,7 @@ Up to three search modes selected by `mode`:
63
63
  - `jsonlogic` — JSONLogic tree evaluated against `path`, `content`, `frontmatter.<key>`, `tags`, and `stat.{ctime,mtime,size}`; custom `glob` and `regexp` operators
64
64
  - `omnisearch` — BM25-ranked search via the community [Omnisearch](https://github.com/scambier/obsidian-omnisearch) plugin. Supports quoted phrases, `-exclusion`, `path:` / `ext:` filters, typo tolerance, and PDF + OCR coverage (via [Text Extractor](https://github.com/scambier/obsidian-text-extractor)). Only present in the mode enum when the plugin's HTTP server is reachable at startup; the upstream hard-caps results at 50 — narrow the query to surface more (the response carries `truncated: true` when the cap was likely hit).
65
65
 
66
- Results paginate via opaque cursors per the [MCP 2025-06-18 spec](https://modelcontextprotocol.io/specification/2025-06-18/utils/pagination): omit `cursor` for the first page, then pass `nextCursor` from the prior response. Every result carries `totalCount` (post-path-policy, pre-pagination); `nextCursor` is omitted on the last page. Text-mode hits are additionally clipped per file at `maxMatchesPerHit` (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry `truncated: true` and `totalMatches`.
66
+ Results paginate via opaque cursors per the [MCP 2025-11-25 spec](https://modelcontextprotocol.io/specification/2025-11-25/utils/pagination): omit `cursor` for the first page, then pass `nextCursor` from the prior response. Every result carries `totalCount` (post-path-policy, pre-pagination); `nextCursor` is omitted on the last page. Text-mode hits are additionally clipped per file at `maxMatchesPerHit` (default 10) so a single match-heavy note can't blow the response budget — clipped hits carry `truncated: true` and `totalMatches`.
67
67
 
68
68
  ---
69
69
 
@@ -195,7 +195,7 @@ Obsidian-specific:
195
195
  - Wraps the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin — typed client, deterministic error mapping
196
196
  - Section-aware editing across headings, block references, and frontmatter fields via `PATCH`-with-target operations
197
197
  - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping fenced code blocks)
198
- - Search across up to three modes: text, JSONLogic, and (when the plugin is reachable) BM25-ranked Omnisearch — cursor-paginated per the MCP 2025-06-18 spec, with per-file match clipping in text mode
198
+ - 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
199
199
  - Optional human-in-the-loop confirmation for destructive deletes via `ctx.elicit`
200
200
  - 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
201
201
  - Opt-in command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`) — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`
@@ -208,7 +208,7 @@ Add the following to your MCP client configuration file. The Obsidian Local REST
208
208
  ```json
209
209
  {
210
210
  "mcpServers": {
211
- "obsidian": {
211
+ "obsidian-mcp-server": {
212
212
  "type": "stdio",
213
213
  "command": "bunx",
214
214
  "args": ["obsidian-mcp-server@latest"],
@@ -227,7 +227,7 @@ Or with npx (no Bun required):
227
227
  ```json
228
228
  {
229
229
  "mcpServers": {
230
- "obsidian": {
230
+ "obsidian-mcp-server": {
231
231
  "type": "stdio",
232
232
  "command": "npx",
233
233
  "args": ["-y", "obsidian-mcp-server@latest"],
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "Enrichment block on search/list tools, mcp-ts-core ^0.9.6 → ^0.9.16, skill sync, format script safe-by-default"
3
+ ---
4
+
5
+ # 3.2.3 — 2026-05-29
6
+
7
+ ## Added
8
+
9
+ - **`obsidian_search_notes`** emits an `enrichment` block with `effectiveQuery` (query echo) and a `notice` when the search returns no results — agent-facing context that surfaces in both `structuredContent` and `content[]`.
10
+ - **`obsidian_list_notes`** and **`obsidian_list_tags`** emit an `enrichment` block with a `notice` when results are empty or capped.
11
+ - **`AGENTS.md`** added at repo root, byte-identical to `CLAUDE.md` (agents that discover `AGENTS.md` now get the same protocol as Claude Code).
12
+ - **`MCP_HTTP_MAX_BODY_BYTES`** documented in `.env.example` (HTTP transport hardening from framework 0.9.13).
13
+ - **`skills/code-simplifier`** and **`skills/git-wrapup`** added (synced from framework 0.9.15).
14
+
15
+ ## Changed
16
+
17
+ - Redundant hardcoded empty-state guidance removed from `format()` on `obsidian_search_notes`, `obsidian_list_notes`, and `obsidian_list_tags` — the `enrichment.notice` now owns that signal.
18
+ - **`format` script** made safe-by-default (`biome check --write .`); unsafe autofixes moved to new `format:unsafe` script.
19
+ - **`server.json` description** re-aligned to match the other surfaces.
20
+ - **`.mcpbignore`** now excludes `skills/` and `.agents/` from MCPB bundles.
21
+ - **`skills/migrate-mcp-ts-template`** removed (pruned upstream in framework 0.9.15).
22
+ - Remaining project skills resynced from framework 0.9.15 (`add-tool`, `add-service`, `add-app-tool`, `api-canvas`, `api-config`, `api-context`, `api-linter`, `design-mcp-server`, `maintenance`, `polish-docs-meta`, `release-and-publish`, `report-issue-framework`, `setup`, `skills/*/references`).
23
+ - Framework scripts resynced (`build-changelog.ts`, `check-docs-sync.ts`, `check-framework-antipatterns.ts`, `check-skills-sync.ts`).
24
+ - **README.md** MCP spec version updated to `2025-11-25`.
25
+ - **`manifest.json`** gained `repository`, `homepage`, and `license` fields.
26
+ - **`package.json`** keywords trimmed (`llm`, `llm-agent` removed); `format` / `format:unsafe` scripts updated.
27
+ - Install badge configs normalized (two prior commits on `main` — `d56e003`, `fd256e9`).
28
+ - **`@cyanheads/mcp-ts-core`** `^0.9.6 → ^0.9.16` (enrichment block 0.9.14–0.9.16, HTTP body-size limit 0.9.13, plus earlier fixes).
29
+ - **`@biomejs/biome`** `^2.4.15 → ^2.4.16`.
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.16 → ^0.9.21 (per-request log context, retryable auto-population, query-string redaction, devcheck gates); plugin marketplace support (.claude-plugin, .codex-plugin); MCP config key renamed to obsidian-mcp-server"
3
+ breaking: false
4
+ security: false
5
+ agent-notes: |
6
+ New untracked directories now ship: `.claude-plugin/` (Claude Code plugin manifest) and `.codex-plugin/` (Codex plugin manifest + mcp.json). The MCP client config key in install snippets changed from `obsidian` to `obsidian-mcp-server` to match the server's declared name. New scripts: `scripts/release-github.ts` (invoked via `release:github`) and `scripts/check-skill-versions.ts`. Skills resynced from mcp-ts-core 0.9.21 — new: `api-mirror`, `orchestrations`. If running `maintenance` on this server after 3.2.4, copy skills from `node_modules/@cyanheads/mcp-ts-core/skills/` to `skills/` and `.claude/skills/` as usual.
7
+ ---
8
+
9
+ # 3.2.4 — 2026-06-02
10
+
11
+ ## Added
12
+
13
+ - **Plugin marketplace manifests.** `.claude-plugin/plugin.json` (Claude Code marketplace) and `.codex-plugin/plugin.json` + `.codex-plugin/mcp.json` (Codex marketplace) — both declare the server's name, version, description, and env var config for one-click installs.
14
+ - **`release:github` script** (`scripts/release-github.ts`) — constructs the GitHub Release title from the tag subject, attaches the `.mcpb` bundle, and handles the "release already exists" fallback. Inherited from mcp-ts-core 0.9.19.
15
+ - **`api-mirror` skill** — documents the `@cyanheads/mcp-ts-core/mirror` API surface (MirrorService, SQLite + FTS5 backend). Added in mcp-ts-core 0.9.17.
16
+ - **`orchestrations` skill** — chains task skills into gated multi-phase pipelines. Added in mcp-ts-core 0.9.19 (promoted to `audience: external` in 0.9.21).
17
+
18
+ ## Changed
19
+
20
+ - **MCP client config key renamed** from `obsidian` to `obsidian-mcp-server` in README install snippets — aligns with the server's declared name in manifests and `server.json`.
21
+ - **Skills resynced** from mcp-ts-core 0.9.21: `add-tool`, `add-service`, `api-canvas`, `api-context`, `api-linter`, `api-utils`, `design-mcp-server`, `release-and-publish` — picks up list-filtering guidance, updated retry semantics, skill-version enforcement gate details, and `release:github` adoption.
22
+ - **`scripts/devcheck.ts` updated** — adds the skill-version enforcement gate and open-indexed-named interface gate from mcp-ts-core 0.9.18.
23
+ - **CLAUDE.md / AGENTS.md** — `orchestrations` skill added to the skills table; pipeline-chaining guidance block added; plugin checklist items added for `.claude-plugin/plugin.json` and `.codex-plugin/` files.
24
+
25
+ ## Dependency bumps
26
+
27
+ - `@cyanheads/mcp-ts-core` ^0.9.16 → ^0.9.21 — HTTP per-request log context fix ([#194](https://github.com/cyanheads/mcp-ts-core/issues/194)), `ctx.fail` auto-populates `data.retryable` ([#174](https://github.com/cyanheads/mcp-ts-core/issues/174)), `fetchWithTimeout` query-string redaction ([#190](https://github.com/cyanheads/mcp-ts-core/issues/190)), `error-contract` lint scoped to construction sites ([#191](https://github.com/cyanheads/mcp-ts-core/issues/191)), list-changed notifications routed under HTTP ([#135](https://github.com/cyanheads/mcp-ts-core/issues/135)), MirrorService (`/mirror` subpath), two devcheck gates (skill-version, open-indexed-named interfaces)
28
+ - `js-yaml` ^4.1.1 → ^4.2.0
29
+ - `vitest` ^4.1.7 → ^4.1.8 (dev)