obsidian-mcp-server 3.6.1 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +27 -17
- package/CLAUDE.md +27 -17
- package/README.md +22 -13
- package/changelog/3.7.x/3.7.0.md +34 -0
- package/dist/config/server-config.d.ts +1 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +4 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +28 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +7 -5
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +12 -8
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +30 -9
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +28 -6
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +230 -294
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/index.js +9 -3
- package/dist/mcp-server/tools/definitions/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +107 -10
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +237 -162
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +61 -19
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +6 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +8 -10
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +7 -5
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +13 -7
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +4 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +15 -4
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -1
- package/dist/services/obsidian/obsidian-service.d.ts +7 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -1
- package/dist/services/obsidian/obsidian-service.js +57 -15
- package/dist/services/obsidian/obsidian-service.js.map +1 -1
- package/dist/services/obsidian/patch-instruction.d.ts +20 -1
- package/dist/services/obsidian/patch-instruction.d.ts.map +1 -1
- package/dist/services/obsidian/patch-instruction.js +30 -0
- package/dist/services/obsidian/patch-instruction.js.map +1 -1
- package/dist/services/obsidian/path-policy.d.ts +23 -1
- package/dist/services/obsidian/path-policy.d.ts.map +1 -1
- package/dist/services/obsidian/path-policy.js +45 -3
- package/dist/services/obsidian/path-policy.js.map +1 -1
- package/dist/services/obsidian/section-extractor.d.ts +24 -0
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -1
- package/dist/services/obsidian/section-extractor.js +37 -0
- package/dist/services/obsidian/section-extractor.js.map +1 -1
- package/manifest.json +9 -1
- package/package.json +2 -2
- package/server.json +17 -3
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview obsidian_delete_note — permanently delete a note.
|
|
2
|
+
* @fileoverview obsidian_delete_note — permanently delete a note. Built by a
|
|
3
|
+
* factory because the confirmation is an operator choice
|
|
4
|
+
* (`OBSIDIAN_DELETE_ELICITATION`, read in the entry point): off, the handler
|
|
5
|
+
* checks write scope, reads the note, and deletes. On, it suspends via
|
|
3
6
|
* `ctx.requestInput` to confirm with the user before the DELETE, and is
|
|
4
|
-
* re-entered with the answer on `ctx.inputs
|
|
7
|
+
* re-entered with the answer on `ctx.inputs`; the answer counts only on a
|
|
5
8
|
* round that redeems the consent record this handler stored when it asked.
|
|
6
9
|
* @module mcp-server/tools/definitions/obsidian-delete-note.tool
|
|
7
10
|
*/
|
|
8
11
|
import { createHash, randomUUID } from 'node:crypto';
|
|
9
12
|
import { isDeepStrictEqual } from 'node:util';
|
|
10
13
|
import { inputRequired, tool, z } from '@cyanheads/mcp-ts-core';
|
|
11
|
-
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
14
|
+
import { JsonRpcErrorCode, McpError, notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
12
15
|
import { getObsidianService } from '../../../services/obsidian/obsidian-service.js';
|
|
13
16
|
import { TargetSchema } from './_shared/schemas.js';
|
|
17
|
+
import { findSimilarPaths } from './_shared/suggest-paths.js';
|
|
14
18
|
const OPERATION = 'obsidian_delete_note';
|
|
15
19
|
const CONSENT_TTL_SECONDS = 600;
|
|
16
20
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
@@ -29,164 +33,235 @@ const ConsentRecord = z.object({
|
|
|
29
33
|
target: z.string().describe('Resolved vault path the user confirmed.'),
|
|
30
34
|
contentHash: z.string().describe('SHA-256 of the note content the user confirmed.'),
|
|
31
35
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Read the note at exactly `path`, letter case included, before anything can
|
|
38
|
+
* be deleted. The plugin's raw-markdown read and its v4.x DELETE
|
|
39
|
+
* (`adapter.exists` + `adapter.remove`) case-fold on a case-insensitive
|
|
40
|
+
* filesystem (macOS, Windows), so a wrong-case path would read — and then
|
|
41
|
+
* permanently remove — the differently-cased note. The `note+json` read
|
|
42
|
+
* resolves through Obsidian's case-sensitive vault index on v4.x and v5.x, so
|
|
43
|
+
* a wrong-case path 404s here. A miss names the case-insensitive and
|
|
44
|
+
* extension-stripped near matches in its folder; a delete never substitutes
|
|
45
|
+
* one. A folder path still fails `path_is_directory`: the folder listing has
|
|
46
|
+
* no `Content-Disposition` on this route either.
|
|
47
|
+
*
|
|
48
|
+
* The bytes come from a second, raw-markdown read of the path that read
|
|
49
|
+
* confirmed. `note+json` serves `cachedRead`, which lags a change made outside
|
|
50
|
+
* Obsidian (sync, git, another editor) until its file watcher fires, so a
|
|
51
|
+
* hash of it could match a consent record for text the file no longer holds.
|
|
52
|
+
*/
|
|
53
|
+
async function readExactNote(ctx, svc, path) {
|
|
54
|
+
const target = { type: 'path', path };
|
|
55
|
+
try {
|
|
56
|
+
await svc.getNoteJson(ctx, target);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (!(err instanceof McpError) || err.code !== JsonRpcErrorCode.NotFound)
|
|
60
|
+
throw err;
|
|
61
|
+
const suggestions = await findSimilarPaths(ctx, svc, path);
|
|
62
|
+
if (suggestions.length === 0)
|
|
63
|
+
throw err;
|
|
64
|
+
const list = suggestions.map((s) => `"${s}"`).join(', ');
|
|
65
|
+
throw notFound(`${err.message.replace(/[.!?]?\s*$/, '')}. Did you mean: ${list}?`, { ...(err.data ?? {}), suggestions }, { cause: err });
|
|
66
|
+
}
|
|
67
|
+
return await svc.getNoteContent(ctx, target);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `elicitation` mirrors `OBSIDIAN_DELETE_ELICITATION`. Off, the operator's
|
|
71
|
+
* `OBSIDIAN_WRITE_PATHS` / `OBSIDIAN_READ_ONLY` are what bound a delete — the
|
|
72
|
+
* same bound `obsidian_write_note` with `overwrite: true` already has.
|
|
73
|
+
*/
|
|
74
|
+
export function buildDeleteNoteTool({ elicitation }) {
|
|
75
|
+
return tool('obsidian_delete_note', {
|
|
76
|
+
description: elicitation
|
|
77
|
+
? 'Permanently delete a note from the vault. Asks the user to confirm before deleting — the call is answered with a confirmation request and retried with the answer. Recovery requires the local trash in Obsidian — there is no API-level undo.'
|
|
78
|
+
: 'Permanently delete a note from the vault. Deletes on the first call without asking the user to confirm. Recovery requires the local trash in Obsidian — there is no API-level undo.',
|
|
79
|
+
annotations: { destructiveHint: true },
|
|
80
|
+
input: z.object({
|
|
81
|
+
target: TargetSchema.describe('Which note to delete.'),
|
|
82
|
+
}),
|
|
83
|
+
output: z.object({
|
|
84
|
+
path: z.string().describe('Resolved vault-relative path of the deleted note.'),
|
|
85
|
+
deleted: z.boolean().describe('True when the file was removed.'),
|
|
86
|
+
previousSizeInBytes: z
|
|
87
|
+
.number()
|
|
88
|
+
.describe('Byte size of the note immediately before deletion. Confirms the size of what was removed.'),
|
|
89
|
+
currentSizeInBytes: z
|
|
90
|
+
.number()
|
|
91
|
+
.describe('Always 0 after a successful delete — the file no longer exists.'),
|
|
92
|
+
}),
|
|
93
|
+
auth: ['tool:obsidian_delete_note:write'],
|
|
94
|
+
errors: [
|
|
95
|
+
{
|
|
96
|
+
reason: 'path_forbidden',
|
|
97
|
+
thrownBy: 'service',
|
|
98
|
+
code: JsonRpcErrorCode.Forbidden,
|
|
99
|
+
when: 'The target path is outside OBSIDIAN_WRITE_PATHS, or OBSIDIAN_READ_ONLY=true denies all writes.',
|
|
100
|
+
recovery: 'Use a path inside the configured write scope. The error data echoes the active scope.',
|
|
101
|
+
},
|
|
102
|
+
/** Declared only where it can fire: the off build never asks, so never throws it. */
|
|
103
|
+
...(elicitation
|
|
104
|
+
? [
|
|
105
|
+
{
|
|
106
|
+
reason: 'cancelled',
|
|
107
|
+
code: JsonRpcErrorCode.InvalidRequest,
|
|
108
|
+
when: 'User declined, cancelled, or answered false to the confirmation request.',
|
|
109
|
+
severity: 'notice',
|
|
110
|
+
recovery: 'Re-run the tool when the user is ready to confirm deletion.',
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
: []),
|
|
114
|
+
{
|
|
115
|
+
reason: 'note_missing',
|
|
116
|
+
thrownBy: 'service',
|
|
117
|
+
code: JsonRpcErrorCode.NotFound,
|
|
118
|
+
when: 'No note has exactly this vault path, letter case included. Near matches in the same folder, such as a different-case spelling, are listed in `suggestions` and never deleted in its place.',
|
|
119
|
+
recovery: 'Verify the path with obsidian_list_notes or use obsidian_search_notes to locate the note. Deletes match the path exactly, letter case included; pass a path from `suggestions` as given.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
reason: 'no_active_file',
|
|
123
|
+
thrownBy: 'service',
|
|
124
|
+
code: JsonRpcErrorCode.NotFound,
|
|
125
|
+
when: 'Target was `active` but no file is currently open in Obsidian.',
|
|
126
|
+
recovery: 'Call obsidian_open_in_ui to focus a file, or pass an explicit path target instead.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
reason: 'periodic_unsupported',
|
|
130
|
+
thrownBy: 'service',
|
|
131
|
+
code: JsonRpcErrorCode.NotFound,
|
|
132
|
+
when: 'Target was `periodic` and this vault runs Local REST API v5.0.2 or later without the companion periodic-notes extension, so the `/periodic/` routes are not served at all.',
|
|
133
|
+
recovery: 'Install the periodic-notes extension from https://github.com/coddingtonbear/obsidian-local-rest-api-periodic-notes, or address the note by an explicit vault path.',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
reason: 'periodic_not_found',
|
|
137
|
+
thrownBy: 'service',
|
|
138
|
+
code: JsonRpcErrorCode.NotFound,
|
|
139
|
+
when: 'Target was `periodic`, the `/periodic/` routes are served on this vault, and no note exists for the requested period.',
|
|
140
|
+
recovery: 'Pass an explicit path target — periodic notes must already exist.',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
reason: 'periodic_disabled',
|
|
144
|
+
thrownBy: 'service',
|
|
145
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
146
|
+
when: "Target was `periodic` but the requested period is not enabled in Obsidian's Periodic Notes plugin settings.",
|
|
147
|
+
recovery: "Pass an explicit path target — the requested period is disabled in the operator's Periodic Notes plugin.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
reason: 'path_is_directory',
|
|
151
|
+
thrownBy: 'service',
|
|
152
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
153
|
+
when: 'The supplied path names a folder rather than a note file. Deleting a folder is not offered — the upstream removes it and everything inside it in one unrecoverable step.',
|
|
154
|
+
recovery: 'Call obsidian_list_notes with this path to list the folder, then delete files one at a time by their full paths.',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
reason: 'path_traversal',
|
|
158
|
+
thrownBy: 'service',
|
|
159
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
160
|
+
when: 'The path contains a `.` or `..` segment, which is rejected to prevent vault escape.',
|
|
161
|
+
recovery: 'Supply a vault-relative path with no `.` or `..` segments, e.g. "Projects/Note.md". Use obsidian_list_notes to browse the vault.',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
async handler(input, ctx) {
|
|
165
|
+
/**
|
|
166
|
+
* Redeem first: whatever this round carries, the record it names is spent
|
|
167
|
+
* before anything acts on the answer. An accepted answer on `ctx.inputs` is
|
|
168
|
+
* not proof the user was asked — a client that declared elicitation can
|
|
169
|
+
* pre-answer a call nothing prompted for — so only the record this handler
|
|
170
|
+
* stored when it asked makes the answer count.
|
|
171
|
+
*
|
|
172
|
+
* Single-use holds against a sequential replay, not concurrent retries:
|
|
173
|
+
* `ctx.state` has no atomic read-and-delete, so retries carrying one id at
|
|
174
|
+
* the same moment can each read the record before a delete lands, and the
|
|
175
|
+
* Local REST API DELETE takes no idempotency key to dedupe them upstream.
|
|
176
|
+
* The losing DELETE finds the file gone and fails `note_missing`, so the
|
|
177
|
+
* race removes nothing beyond the confirmed note unless that path is
|
|
178
|
+
* recreated in between. Tracked as cyanheads/mcp-ts-core#593 (an atomic
|
|
179
|
+
* `ctx.state.take`).
|
|
180
|
+
*
|
|
181
|
+
* With the confirmation off, nothing on `ctx.inputs` is read and nothing
|
|
182
|
+
* is written to `ctx.state`.
|
|
183
|
+
*/
|
|
184
|
+
const id = elicitation ? ctx.inputs.state() : undefined;
|
|
185
|
+
const record = id && UUID.test(id) ? await ctx.state.get(`consent/${id}`, ConsentRecord) : null;
|
|
186
|
+
if (record)
|
|
187
|
+
await ctx.state.delete(`consent/${id}`);
|
|
188
|
+
/**
|
|
189
|
+
* Write scope is checked on the resolved path before the note is read, so
|
|
190
|
+
* a note the caller can read but not delete fails `path_forbidden` without
|
|
191
|
+
* a prompt or a stored record.
|
|
192
|
+
*/
|
|
193
|
+
const svc = getObsidianService();
|
|
194
|
+
const path = await svc.resolvePath(ctx, input.target);
|
|
195
|
+
svc.policy.assertWritable(path);
|
|
196
|
+
const pathTarget = { type: 'path', path };
|
|
197
|
+
/**
|
|
198
|
+
* The read runs in both modes: it throws `path_is_directory` for a folder,
|
|
199
|
+
* which the DELETE would remove with everything inside it, and
|
|
200
|
+
* `note_missing` when no note has exactly this path — already gone, or a
|
|
201
|
+
* case variant of one (see `readExactNote`). With the confirmation on, it
|
|
202
|
+
* also serves the prompt and the record: the bytes the user is told
|
|
203
|
+
* they're destroying, and the hash a later round must still match.
|
|
204
|
+
*/
|
|
205
|
+
const content = await readExactNote(ctx, svc, path);
|
|
206
|
+
const previousSizeInBytes = Buffer.byteLength(content);
|
|
207
|
+
if (elicitation) {
|
|
208
|
+
const expected = {
|
|
209
|
+
operation: OPERATION,
|
|
210
|
+
clientId: ctx.auth?.clientId ?? '',
|
|
211
|
+
subject: ctx.auth?.sub ?? '',
|
|
212
|
+
target: path,
|
|
213
|
+
contentHash: createHash('sha256').update(content).digest('hex'),
|
|
214
|
+
};
|
|
215
|
+
const matches = record !== null && isDeepStrictEqual(record, expected);
|
|
216
|
+
/**
|
|
217
|
+
* A declined or cancelled prompt the user was actually shown is a dead
|
|
218
|
+
* end, not a round to retry — re-asking would burn the round budget
|
|
219
|
+
* until the client gives up.
|
|
220
|
+
*/
|
|
221
|
+
const view = ctx.inputs.view('confirm');
|
|
222
|
+
if (matches && view.kind === 'elicit' && view.action !== 'accept') {
|
|
223
|
+
throw ctx.fail('cancelled', `User sent '${view.action}' for the deletion confirmation.`, {
|
|
224
|
+
path,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const answer = matches ? ctx.inputs.accepted('confirm', DeleteConfirmation) : undefined;
|
|
228
|
+
if (!answer) {
|
|
229
|
+
const fresh = randomUUID();
|
|
230
|
+
await ctx.state.set(`consent/${fresh}`, expected, { ttl: CONSENT_TTL_SECONDS });
|
|
231
|
+
return ctx.requestInput({
|
|
232
|
+
inputRequests: {
|
|
233
|
+
confirm: inputRequired.elicit({
|
|
234
|
+
message: `Permanently delete '${path}' (${previousSizeInBytes} bytes)? This cannot be undone via the API; recovery would require Obsidian's local trash.`,
|
|
235
|
+
requestedSchema: DeleteConfirmation,
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
requestState: fresh,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (!answer.confirm) {
|
|
242
|
+
throw ctx.fail('cancelled', 'Deletion cancelled by user.', { path });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
await svc.deleteNote(ctx, pathTarget);
|
|
246
|
+
return { path, deleted: true, previousSizeInBytes, currentSizeInBytes: 0 };
|
|
189
247
|
},
|
|
190
|
-
|
|
191
|
-
|
|
248
|
+
format: (result) => [
|
|
249
|
+
{
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: `**Deleted ${result.path}** (size: ${result.previousSizeInBytes} → ${result.currentSizeInBytes} bytes)`,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Static specimen for the MCP definition linter (which duck-types tool
|
|
258
|
+
* exports out of each `.tool.ts` file) and for tests that import the tool
|
|
259
|
+
* directly. Built with the confirmation off — the default, and what an
|
|
260
|
+
* unconfigured install registers. The entry point (`src/index.ts`) builds the
|
|
261
|
+
* live tool via `buildDeleteNoteTool` from `OBSIDIAN_DELETE_ELICITATION`; this
|
|
262
|
+
* export is not the registered tool. The confirming variant is exercised by
|
|
263
|
+
* tests rather than the linter (two exports under the same tool name would
|
|
264
|
+
* collide on `name-unique`).
|
|
265
|
+
*/
|
|
266
|
+
export const obsidianDeleteNote = buildDeleteNoteTool({ elicitation: false });
|
|
192
267
|
//# sourceMappingURL=obsidian-delete-note.tool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obsidian-delete-note.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-delete-note.tool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"obsidian-delete-note.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-delete-note.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -147,6 +147,7 @@ export declare const obsidianGetNote: import("@cyanheads/mcp-ts-core").ToolDefin
|
|
|
147
147
|
readonly when: "The path contains a `.` or `..` segment, which is rejected to prevent vault escape.";
|
|
148
148
|
readonly recovery: "Supply a vault-relative path with no `.` or `..` segments, e.g. \"Projects/Note.md\". Use obsidian_list_notes to browse the vault.";
|
|
149
149
|
}], {
|
|
150
|
+
readonly requestedPath: z.ZodOptional<z.ZodString>;
|
|
150
151
|
readonly notice: z.ZodOptional<z.ZodString>;
|
|
151
152
|
}>;
|
|
152
153
|
//# sourceMappingURL=obsidian-get-note.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obsidian-get-note.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-get-note.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAgB3E,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"obsidian-get-note.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/obsidian-get-note.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAgB3E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiY1B,CAAC"}
|