obsidian-mcp-server 3.2.1 → 3.2.3

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 (47) hide show
  1. package/AGENTS.md +403 -0
  2. package/CLAUDE.md +26 -8
  3. package/README.md +10 -4
  4. package/changelog/3.2.x/3.2.2.md +19 -0
  5. package/changelog/3.2.x/3.2.3.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-get-note.tool.js +5 -14
  18. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +1 -1
  20. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +3 -1
  22. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +12 -0
  24. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
  25. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +3 -1
  26. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +16 -7
  28. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +1 -1
  30. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +1 -1
  32. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +1 -1
  34. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +1 -1
  36. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +1 -1
  38. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +8 -2
  40. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +27 -7
  42. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
  43. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +1 -1
  44. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -1
  45. package/manifest.json +100 -0
  46. package/package.json +13 -7
  47. package/server.json +4 -4
package/AGENTS.md ADDED
@@ -0,0 +1,403 @@
1
+ # Agent Protocol
2
+
3
+ **Server:** obsidian-mcp-server
4
+ **Version:** 3.2.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.16`
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
+ | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
305
+ | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
306
+ | `api-auth` | Auth modes, scopes, JWT/OAuth |
307
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
308
+ | `api-config` | AppConfig, parseConfig, env vars |
309
+ | `api-context` | Context interface, logger, state, progress |
310
+ | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
311
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
312
+ | `api-services` | LLM, Speech, Graph services |
313
+ | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
314
+ | `api-testing` | createMockContext, test patterns |
315
+ | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
316
+ | `api-workers` | Cloudflare Workers runtime |
317
+
318
+ When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
319
+
320
+ ---
321
+
322
+ ## Commands
323
+
324
+ **Runtime:** Scripts use `tsx` — both `bun run <cmd>` and `npm run <cmd>` work. `bun` is preferred (faster startup, native TS).
325
+
326
+ | Command | Purpose |
327
+ |:--------|:--------|
328
+ | `bun run build` | Compile TypeScript |
329
+ | `bun run rebuild` | Clean + build |
330
+ | `bun run clean` | Remove build artifacts |
331
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
332
+ | `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. |
333
+ | `bun run tree` | Generate `docs/tree.md` |
334
+ | `bun run list-skills` | Print project skill index (name, version, description) |
335
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
336
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
337
+ | `bun run lint:mcp` | Validate MCP definitions against the linter rules |
338
+ | `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` |
339
+ | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
340
+ | `bun run test` | Run Vitest tests |
341
+ | `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
342
+ | `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
343
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` rollup from `changelog/<minor>.x/*.md` |
344
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
345
+
346
+ ---
347
+
348
+ ## Bundling
349
+
350
+ `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.
351
+
352
+ **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.
353
+
354
+ ---
355
+
356
+ ## Changelog
357
+
358
+ 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`.
359
+
360
+ Each per-version file opens with YAML frontmatter:
361
+
362
+ ```markdown
363
+ ---
364
+ summary: One-line headline, ≤350 chars # required — powers the rollup index
365
+ breaking: false # optional — true flags breaking changes
366
+ security: false # optional — true flags security fixes
367
+ ---
368
+
369
+ # 0.1.0 — YYYY-MM-DD
370
+ ...
371
+ ```
372
+
373
+ `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`.
374
+
375
+ ---
376
+
377
+ ## Imports
378
+
379
+ ```ts
380
+ // Framework — z is re-exported, no separate zod import needed
381
+ import { tool, z } from '@cyanheads/mcp-ts-core';
382
+ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
383
+
384
+ // Server's own code — via path alias
385
+ import { getMyService } from '@/services/my-domain/my-service.js';
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Checklist
391
+
392
+ - [ ] 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()`)
393
+ - [ ] 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`.
394
+ - [ ] JSDoc `@fileoverview` + `@module` on every file
395
+ - [ ] `ctx.log` for logging, `ctx.state` for storage
396
+ - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
397
+ - [ ] `format()` renders all data the LLM needs — different clients forward different surfaces (Claude Code → `structuredContent`, Claude Desktop → `content[]`); both must carry the same data
398
+ - [ ] If wrapping external API: raw/domain/output schemas reviewed against real upstream sparsity/nullability before finalizing required vs optional fields
399
+ - [ ] If wrapping external API: normalization and `format()` preserve uncertainty; do not fabricate facts from missing upstream data
400
+ - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
401
+ - [ ] 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
402
+ - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
403
+ - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** obsidian-mcp-server
4
- **Version:** 3.2.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.1`
4
+ **Version:** 3.2.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.9.16`
6
6
  **Engines:** Bun ≥1.3.11, Node ≥24.0.0
7
+ **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
+ **Zod:** ^4.4.3
7
9
 
8
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.
9
11
 
@@ -37,6 +39,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
37
39
  - **Secrets in env vars only.** `OBSIDIAN_API_KEY` is required; never hardcoded.
38
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.
39
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.
40
43
 
41
44
  ---
42
45
 
@@ -291,19 +294,21 @@ Available skills:
291
294
  | `add-test` | Scaffold test file for a tool, resource, or service |
292
295
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
293
296
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
294
- | `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 |
295
300
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
296
- | `release-and-publish` | Ship a release end-to-end across npm, MCP Registry, and GHCR |
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 |
297
303
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
298
- | `migrate-mcp-ts-template` | Migrate a `mcp-ts-template` fork to depend on `@cyanheads/mcp-ts-core` as a package |
299
304
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
300
305
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
301
306
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
302
- | `api-canvas` | DataCanvas SQL workspace (Tier 3, DuckDB) not used by this server |
307
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets Tier 3 opt-in |
303
308
  | `api-config` | AppConfig, parseConfig, env vars |
304
309
  | `api-context` | Context interface, logger, state, progress |
305
310
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
306
- | `api-linter` | MCP definition linter rule reference (`bun run lint:mcp` failures) |
311
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
307
312
  | `api-services` | LLM, Speech, Graph services |
308
313
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
309
314
  | `api-testing` | createMockContext, test patterns |
@@ -324,9 +329,14 @@ When you complete a skill's checklist, check the boxes and add a completion time
324
329
  | `bun run rebuild` | Clean + build |
325
330
  | `bun run clean` | Remove build artifacts |
326
331
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
332
+ | `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. |
327
333
  | `bun run tree` | Generate `docs/tree.md` |
328
- | `bun run format` | Auto-fix formatting (Biome) |
334
+ | `bun run list-skills` | Print project skill index (name, version, description) |
335
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
336
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
329
337
  | `bun run lint:mcp` | Validate MCP definitions against the linter rules |
338
+ | `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` |
339
+ | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
330
340
  | `bun run test` | Run Vitest tests |
331
341
  | `bun run start:stdio` | Production mode (stdio) — requires `bun run build` first |
332
342
  | `bun run start:http` | Production mode (HTTP) — requires `bun run build` first |
@@ -335,6 +345,14 @@ When you complete a skill's checklist, check the boxes and add a completion time
335
345
 
336
346
  ---
337
347
 
348
+ ## Bundling
349
+
350
+ `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.
351
+
352
+ **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.
353
+
354
+ ---
355
+
338
356
  ## Changelog
339
357
 
340
358
  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`.
package/README.md CHANGED
@@ -7,9 +7,15 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![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) [![Version](https://img.shields.io/badge/Version-3.2.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-259?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/)
10
+ [![Version](https://img.shields.io/badge/Version-3.2.3-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
- [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![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/)
12
+ </div>
13
+
14
+ <div align="center">
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=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
+
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)
13
19
 
14
20
  </div>
15
21
 
@@ -57,7 +63,7 @@ Up to three search modes selected by `mode`:
57
63
  - `jsonlogic` — JSONLogic tree evaluated against `path`, `content`, `frontmatter.<key>`, `tags`, and `stat.{ctime,mtime,size}`; custom `glob` and `regexp` operators
58
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).
59
65
 
60
- 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`.
61
67
 
62
68
  ---
63
69
 
@@ -189,7 +195,7 @@ Obsidian-specific:
189
195
  - Wraps the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin — typed client, deterministic error mapping
190
196
  - Section-aware editing across headings, block references, and frontmatter fields via `PATCH`-with-target operations
191
197
  - Tag reconciliation across both representations: frontmatter `tags:` array and inline `#tag` syntax (skipping fenced code blocks)
192
- - 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
193
199
  - Optional human-in-the-loop confirmation for destructive deletes via `ctx.elicit`
194
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
195
201
  - Opt-in command-palette pair (`obsidian_list_commands` + `obsidian_execute_command`) — registered only when `OBSIDIAN_ENABLE_COMMANDS=true`
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.1 → ^0.9.6; format-parity fixes on search_notes and get_note; manifest.json + .mcpbignore scaffolded for MCPB bundle support; install badges added to README."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 3.2.2 — 2026-05-23
8
+
9
+ ## Changed
10
+
11
+ - **`@cyanheads/mcp-ts-core`** `^0.9.1 → ^0.9.6` — picks up upstream fixes and API additions across five patch releases.
12
+ - **`obsidian_search_notes` `format()` output** now includes the `logic` and `query` field values so text-mode callers see the query echoed in `content[]`; previously those fields appeared only in `structuredContent`.
13
+ - **`obsidian_get_note` `format()` output** now includes the `path` and `format` field values; previously the structured result was richer than the markdown twin.
14
+
15
+ ## Added
16
+
17
+ - **`manifest.json`** scaffolded for `.mcpb` (MCPB Claude Desktop bundle) support — enables `bun run bundle` to produce a one-click install artifact.
18
+ - **`.mcpbignore`** scaffolded alongside `manifest.json` to exclude dev files from the bundle.
19
+ - **Install badges** (MCPB, VS Code) added to the README header badge row.
@@ -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`.
@@ -19,6 +19,14 @@ breaking: false
19
19
  # `## Security` section below. Flagged as `Security` in the rollup so
20
20
  # users can triage upgrade urgency at a glance.
21
21
  security: false
22
+
23
+ # Optional free-form notes for maintenance agents processing this release.
24
+ # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
25
+ # downstream servers. Use for adoption instructions that don't fit the
26
+ # human-facing sections: new files to create, fields to populate, one-time
27
+ # migration steps. Omit the field entirely when there's nothing to say.
28
+ # agent-notes: |
29
+ # <instructions for downstream maintenance agents>
22
30
  ---
23
31
 
24
32
  # <version> — YYYY-MM-DD
@@ -35,7 +43,7 @@ security: false
35
43
  name the symbol, state what changed, stop. Use a second sentence only when
36
44
  it carries weight. If a bullet feels long, it is.
37
45
 
38
- Cut: mechanism walkthroughs (those belong in JSDoc, AGENTS.md, or the
46
+ Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
39
47
  relevant skill), ceremonial framings ("This release introduces…",
40
48
  backwards-compat paragraphs), file-by-file test enumerations, internal
41
49
  implementation notes. Prefer code/symbol names over English re-explanations.
@@ -66,6 +74,32 @@ security: false
66
74
  Never speculate on a future number — `#42` for an upcoming PR silently
67
75
  resolves to whatever real item already owns 42, and timeline previews pull
68
76
  in that unrelated item's metadata.
77
+
78
+ TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
79
+ via `gh release create --notes-from-tag`. The tag is a derivative of this
80
+ changelog entry — a condensed, scannable version, not a copy. Format:
81
+
82
+ <theme — omit version number, GitHub prepends it>
83
+ ← blank line
84
+ <1-2 sentence context: what this release does>
85
+ ← blank line
86
+ Dependency bumps: ← section header
87
+ ← blank line
88
+ - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
89
+ ← blank line
90
+ Changed: ← only sections with entries
91
+ ← blank line
92
+ - `format()` output includes `query` in text mode
93
+ ← blank line
94
+ Added:
95
+ ← blank line
96
+ - `manifest.json` scaffolded for MCPB bundle support
97
+ - Install badges (Claude Desktop, Cursor, VS Code)
98
+ ← blank line
99
+ <N> tests pass; `bun run devcheck` clean. ← footer
100
+
101
+ Never a flat comma-separated string. Always structured markdown with
102
+ sections. The tag must scan well as a rendered GitHub Release page.
69
103
  -->
70
104
 
71
105
  ## Added