Skip to content

Commit 3dba89d

Browse files
mairasduquesnaycoddingtonbearclaude
authored
Add "move" operation via MOVE HTTP verb (#191)
* feat: implement file move operation - Add move operation to PATCH endpoint for files - Use Operation: move, Target-Type: file, Target: path - Automatically creates parent directories if needed - Preserves all internal links using FileManager.renameFile() - Validates paths and prevents overwrites - Add tests for edge cases * refactor: simplify PATCH endpoint validation logic - Add new error codes for clearer validation messages - Use consistent returnCannedResponse pattern throughout - Reorder validation checks for better flow - Separate file operations from applyPatch operations early - Maintain upstream coding style and patterns * feat: add path validation security and consistent error handling to file move - Add path traversal protection to prevent access outside vault - Use returnCannedResponse consistently throughout handleMoveOperation - Add proper error codes to ErrorCode enum and ERROR_CODE_MESSAGES - Validate paths early to prevent directory path destinations - Check parent directory existence before creating - Add comprehensive security tests for path traversal attempts - Normalize paths to handle backslashes and multiple slashes * docs: Add OpenAPI documentation for file move operation - Add move operation to PATCH endpoint enum - Update Target parameter description for move operations - Include example for file move functionality - Documents Operation: move, Target-Type: file usage * Implement a custom MOVE HTTP method * Place MOVE under additionalOperations in OpenAPI spec Stoplight Elements (forked build) expects non-standard HTTP methods to be nested under an `additionalOperations` key on the path item rather than as direct siblings of `get`/`post`/etc. Without this, the MOVE operation is ignored by the doc renderer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Refine MOVE endpoint headers and destination handling - Rename Overwrite: T/F to Allow-Overwrite: true/false to match the boolean header convention used elsewhere in this API (e.g. Create-Target-If-Missing, Reject-If-Content-Preexists). Defaults to false, which preserves the safe no-overwrite behaviour. - Treat a trailing slash in the Destination header as a directory target: the source filename is appended automatically, so "Destination: archive/" moves "notes/todo.md" to "archive/todo.md". This mirrors the behaviour of Unix mv and removes an otherwise confusing 400 error. - Move path normalisation (backslash conversion, slash collapsing) before the traversal check so that a Windows-style path cannot bypass the leading-slash guard. - Fix a bug in the .all() route registration where this.handle(...) returned a middleware function that was never called; requests were silently dropped, causing all MOVE tests to time out. - Remove the now-unreachable InvalidDestinationPath error code and fix the numeric ordering of the remaining new error codes in types.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Refactor MOVE into VaultOperations and add vault_move MCP tool Moves the core file-rename logic out of RequestHandler._vaultMove and into a new VaultOperations.moveVaultFile method, following the same pattern used for all other vault operations (read, write, append, delete, patch). The HTTP handler now owns only the HTTP-specific concerns: header parsing, path normalisation, and mapping typed errors to status codes. Adds a vault_move MCP tool that exposes the same operation to MCP clients, including path-traversal validation and trailing-slash destination handling (preserves source filename when destination ends with '/'). Introduces DestinationAlreadyExistsError alongside the existing FileNotFoundError so callers can distinguish the two failure modes without inspecting error messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Change MOVE endpoint response from 201 to 204 The 201 + JSON body (message, oldPath, newPath) was borrowed from WebDAV semantics, but those fields carry no new information — the caller already knows both paths. 204 No Content matches the pattern used by every other mutating endpoint in this API (PUT, DELETE, PATCH on non-patch operations). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add Content-Location header to MOVE 204 response Sets Content-Location to the vault-relative path of the file at its new location (e.g. "archive/file.md"), matching the convention already used by the active and periodic routes. Also corrects two test assertions whose expected error codes didn't match the current ErrorCode enum. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Document and test Content-Location on active file routes The Content-Location header was already being set on all /active/ responses (via redirectToVaultPath and direct res.set calls), but had no unit test coverage, no OpenAPI documentation, and a loose integration test assertion. Adds unit tests for all five verbs (GET, PUT, POST, PATCH, DELETE), adds Content-Location header documentation to the OpenAPI spec for each corresponding success response, and tightens the integration test assertion to check the exact vault-relative path rather than just truthiness. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Document and test Content-Location on periodic note routes Mirrors the active-file commit: Content-Location was already being set on all /periodic/ responses but had no unit test coverage, no OpenAPI documentation, and a loose integration test assertion. Adds unit tests for all five verbs on the current-period route, adds Content-Location header documentation to the OpenAPI spec for both the current-period and date-specific variants of each operation, and tightens the integration test to verify the header value matches a Markdown file path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add integration tests for MOVE endpoint and vault_move MCP tool Covers the happy path (file moves, source gone, dest has original content), trailing-slash destination resolution, missing/invalid input errors (404, 400, 409), and Allow-Overwrite / allowOverwrite overwrite semantics for both the REST and MCP surfaces. Running the tests revealed that Allow-Overwrite: true was silently producing a 500 because renameFile cannot overwrite an existing file. Fixed moveVaultFile to delete the destination via adapter.remove before renaming when allowOverwrite is true, consistent with the pattern already used in deleteVaultFile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Document vault_move in MCP endpoint description The vault_move tool was missing from the Available Tools table in the /mcp/ endpoint documentation. Added the entry and regenerated docs/openapi.yaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Document vault_move in README MCP tools table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Return 400 on malformed percent-encoding in Destination header decodeURIComponent throws a URIError on invalid sequences like '%E0%'. Without a try/catch this bubbled into the generic error handler and produced a 500. Wrap the decode step and return a new InvalidDestinationHeader (40022) error code instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Treat empty Destination header as vault-root target in MOVE A Destination header consisting entirely of whitespace was previously rejected with MissingDestinationHeader (400) because Node.js HTTP strips OWS from field values before Express sees them, leaving an empty string that the original !rawDestination guard caught. Change the guard to rawDestination === undefined so that an explicitly sent (but empty) Destination header is allowed through. After normalization the empty string maps to the vault root, and the existing !normalized branch in the newPath calculation appends the source filename, moving the file to the root of the vault with its name preserved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Return immediately from the MOVE branch in the vault .all() handler Without the return, control falls through after handle() completes, making the control flow harder to reason about and creating a risk that a future edit adds code after the if/else that would execute even for handled MOVE requests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Guard against data loss when source and destination paths are equal in moveVaultFile When allowOverwrite was true and destination equaled source, the overwrite branch deleted the destination (which is the same file as the source) before calling renameFile on the now-deleted handle, silently destroying the file. Add an early return when sourcePath === destinationPath, treating a same-path move as a no-op. An integration test covers the allowOverwrite variant that previously caused the deletion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Declare OpenAPI document version as 3.2.0 The additionalOperations field used to document the MOVE verb is a standard OpenAPI 3.2 Path Item field. The document was previously declared as 3.0.2, making that field a spec violation. No schema content changes are needed: none of the 3.0→3.1 breaking constructs (nullable, boolean exclusiveMaximum/Minimum, format:binary/base64) appear in this document. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add missing ERROR_CODE_MESSAGES entry for InvalidDestinationHeader ErrorCode.InvalidDestinationHeader (40022) was defined in types.ts but had no corresponding entry in the ERROR_CODE_MESSAGES Record, causing TypeScript to emit a compile error and MOVE error responses for malformed Destination headers to include an undefined message string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Trim destination in vault_move before normalization The HTTP MOVE handler trims the raw Destination header value before normalization; the MCP vault_move tool was missing the equivalent step. A whitespace-only destination would pass the traversal check and reach moveVaultFile as a blank path, causing a runtime failure. Adding trim() aligns MCP behavior with the HTTP handler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace substring-based traversal check with resolve-and-contain The previous check (normalized.includes("..") || normalized.startsWith("/")) was too broad: it rejected legitimate filenames containing ".." as a substring (e.g. "notes..md") and was redundant for the leading-slash case once normalization was in place. The new check uses posix.resolve against a synthetic vault root and asserts the resolved path starts with that root. This correctly handles all traversal attempts (including multi-segment ".." sequences and absolute paths) while allowing filenames that merely happen to contain ".." as a substring. Applied consistently in both the HTTP MOVE handler and the MCP vault_move tool. Tests added for both the rejection and the newly-allowed case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Reject Destination paths starting with '/' before traversal check posix.resolve treats its second argument as absolute when it starts with '/', ignoring the synthetic root entirely. A destination like /vault/notes/file.md resolves to itself, passes the startsWith check, and reaches Obsidian's API with an absolute-looking path it does not understand. Adding an explicit leading-slash guard before the posix.resolve step ensures any absolute path — including those that happen to share the synthetic-root prefix — is rejected with PathTraversalNotAllowed (HTTP) or the equivalent MCP error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Document percent-encoding requirement for Destination header The Destination header is decoded server-side with decodeURIComponent, matching how the Target header works on PATCH endpoints. HTTP/1.1 headers are Latin-1 by default, so raw UTF-8 bytes in header values will be misinterpreted; clients must percent-encode non-ASCII characters (e.g. r%C3%A9sum%C3%A9.md for résumé.md). Adds the same guidance already present in the Target header docs to the Destination header description, and notes that Content-Location values for non-ASCII paths will likewise be percent-encoded. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix missing trailing slash in periodic note integration test URLs The /periodic/:period/ routes require a trailing slash. All test calls used /periodic/daily without one, producing a 404 from Express before the handler was ever reached. This was a pre-existing bug on main. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Return actual post-rename path from moveVaultFile; document Content-Location encoding moveVaultFile now returns the path Obsidian reports after renameFile completes (sourceFile.path), matching the pattern every other endpoint uses — reading the canonical path from the TFile object rather than echoing back the client-provided string. Both the REST Content-Location header and the MCP newPath response field now reflect this value. Also adds a percent-encoding note to the shared ContentLocationHeader description in the OpenAPI spec, so all endpoints consistently document that non-ASCII names appear percent-encoded in that header. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Guard moveVaultFile against empty destination path Adds an early check that rejects empty-string destinations before any vault adapter calls are made. This prevents adapter operations on an invalid path when a caller bypasses the normalization logic that would normally resolve an empty destination to a vault-root move. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Align MCP vault_move empty-destination handling with REST behavior When the caller passes an empty or whitespace-only destination, the REST MOVE handler resolves it to the vault root by appending the source filename (matching the trailing-slash convention). The MCP handler was missing the !normalized guard, so it forwarded an empty string to moveVaultFile instead. This commit adds the same guard and covers the case with two new tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Correct MOVE destination docs: escape check, not dot-dot check The documentation in move.jsonnet and the vault_move MCP tool description both said the destination "must not contain '..'" — but the actual validation resolves the path against a synthetic vault root and rejects only paths that escape it. That means a/../b.md is accepted (resolves within the vault) while ../../etc/passwd is rejected. Update both the Destination header description and the 400 response description in move.jsonnet to reflect the real rule, and regenerate docs/openapi.yaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use realistic mock return value in vault_move dot-dot substring test The test was mocking moveVaultFile to resolve undefined, which is not a valid return type and masked any assertion on the returned newPath. Mock it with the real destination string and assert on newPath so the test exercises the full round-trip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Guillaume Duquesnay <guillaume.duquesnay@gmail.com> Co-authored-by: Adam Coddington <me@adamcoddington.net> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 17a1ca8 commit 3dba89d

16 files changed

Lines changed: 1186 additions & 40 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ The exact config syntax varies by client; see the [Quick start](#mcp-clients) ex
264264
| `vault_append` | Append content to the end of a vault file |
265265
| `vault_patch` | Patch a specific heading, block reference, or frontmatter field |
266266
| `vault_delete` | Delete a vault file |
267+
| `vault_move` | Move (rename) a vault file to a new path |
267268
| `vault_get_document_map` | List the headings, block references, and frontmatter fields in a file |
268269
| `active_file_get_path` | Return the vault path of the file currently open in Obsidian |
269270
| `periodic_note_get_path` | Return the vault path of the current periodic note (`daily`, `weekly`, `monthly`, `quarterly`, `yearly`) |

docs/openapi.yaml

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ info:
7979
**Certificate warning** — the plugin generates a self-signed TLS certificate on first run. Most browsers will block requests to an untrusted certificate, so you may need to add it as a trusted certificate in your OS or browser settings before requests will go through. The steps vary by environment — search for "trust self-signed certificate" plus your OS or browser name if you're unsure. If that proves too cumbersome, you can enable the insecure HTTP server in your plugin settings instead and select "HTTP (insecure mode)" from the **Try It** section.
8080
title: "Local REST API for Obsidian"
8181
version: "1.0"
82-
openapi: "3.0.2"
82+
openapi: "3.2.0"
8383
paths:
8484
/:
8585
get:
@@ -123,6 +123,12 @@ paths:
123123
responses:
124124
"204":
125125
description: "Success"
126+
headers:
127+
Content-Location:
128+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
129+
schema:
130+
example: "notes/file.md"
131+
type: "string"
126132
"404":
127133
content:
128134
application/json:
@@ -256,6 +262,12 @@ paths:
256262
something else here
257263
type: "string"
258264
description: "Success"
265+
headers:
266+
Content-Location:
267+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
268+
schema:
269+
example: "notes/file.md"
270+
type: "string"
259271
"404":
260272
description: "File or target section does not exist"
261273
summary: |
@@ -546,6 +558,12 @@ paths:
546558
responses:
547559
"200":
548560
description: "Success"
561+
headers:
562+
Content-Location:
563+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
564+
schema:
565+
example: "notes/file.md"
566+
type: "string"
549567
"400":
550568
content:
551569
application/json:
@@ -683,8 +701,20 @@ paths:
683701
schema:
684702
type: "string"
685703
description: "Success; targeted section updated. The full updated file content is returned."
704+
headers:
705+
Content-Location:
706+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
707+
schema:
708+
example: "notes/file.md"
709+
type: "string"
686710
"204":
687711
description: "Success; content appended to end of file."
712+
headers:
713+
Content-Location:
714+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
715+
schema:
716+
example: "notes/file.md"
717+
type: "string"
688718
"400":
689719
content:
690720
application/json:
@@ -805,8 +835,20 @@ paths:
805835
schema:
806836
type: "string"
807837
description: "Success; targeted section replaced. The full updated file content is returned."
838+
headers:
839+
Content-Location:
840+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
841+
schema:
842+
example: "notes/file.md"
843+
type: "string"
808844
"204":
809845
description: "Success; entire file replaced."
846+
headers:
847+
Content-Location:
848+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
849+
schema:
850+
example: "notes/file.md"
851+
type: "string"
810852
"400":
811853
content:
812854
application/json:
@@ -940,6 +982,7 @@ paths:
940982
| `vault_append` | Append content to the end of a vault file |
941983
| `vault_patch` | Patch a specific heading, block reference, or frontmatter field |
942984
| `vault_delete` | Delete a vault file |
985+
| `vault_move` | Move (rename) a vault file to a new path |
943986
| `vault_get_document_map` | List the headings, block references, and frontmatter fields in a file |
944987
| `active_file_get_path` | Return the vault path of the file currently open in Obsidian |
945988
| `periodic_note_get_path` | Return the vault path of the current periodic note (daily, weekly, monthly, quarterly, yearly) |
@@ -1139,6 +1182,12 @@ paths:
11391182
responses:
11401183
"204":
11411184
description: "Success"
1185+
headers:
1186+
Content-Location:
1187+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1188+
schema:
1189+
example: "notes/file.md"
1190+
type: "string"
11421191
"404":
11431192
content:
11441193
application/json:
@@ -1285,6 +1334,12 @@ paths:
12851334
something else here
12861335
type: "string"
12871336
description: "Success"
1337+
headers:
1338+
Content-Location:
1339+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1340+
schema:
1341+
example: "notes/file.md"
1342+
type: "string"
12881343
"404":
12891344
description: "File or target section does not exist"
12901345
summary: |
@@ -1588,6 +1643,12 @@ paths:
15881643
responses:
15891644
"200":
15901645
description: "Success"
1646+
headers:
1647+
Content-Location:
1648+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1649+
schema:
1650+
example: "notes/file.md"
1651+
type: "string"
15911652
"400":
15921653
content:
15931654
application/json:
@@ -1738,8 +1799,20 @@ paths:
17381799
schema:
17391800
type: "string"
17401801
description: "Success; targeted section updated. The full updated file content is returned."
1802+
headers:
1803+
Content-Location:
1804+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1805+
schema:
1806+
example: "notes/file.md"
1807+
type: "string"
17411808
"204":
17421809
description: "Success; content appended to end of file."
1810+
headers:
1811+
Content-Location:
1812+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1813+
schema:
1814+
example: "notes/file.md"
1815+
type: "string"
17431816
"400":
17441817
content:
17451818
application/json:
@@ -1873,8 +1946,20 @@ paths:
18731946
schema:
18741947
type: "string"
18751948
description: "Success; targeted section replaced. The full updated file content is returned."
1949+
headers:
1950+
Content-Location:
1951+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1952+
schema:
1953+
example: "notes/file.md"
1954+
type: "string"
18761955
"204":
18771956
description: "Success; entire file replaced."
1957+
headers:
1958+
Content-Location:
1959+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
1960+
schema:
1961+
example: "notes/file.md"
1962+
type: "string"
18781963
"400":
18791964
content:
18801965
application/json:
@@ -1932,6 +2017,12 @@ paths:
19322017
responses:
19332018
"204":
19342019
description: "Success"
2020+
headers:
2021+
Content-Location:
2022+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2023+
schema:
2024+
example: "notes/file.md"
2025+
type: "string"
19352026
"404":
19362027
content:
19372028
application/json:
@@ -2096,6 +2187,12 @@ paths:
20962187
something else here
20972188
type: "string"
20982189
description: "Success"
2190+
headers:
2191+
Content-Location:
2192+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2193+
schema:
2194+
example: "notes/file.md"
2195+
type: "string"
20992196
"404":
21002197
description: "File or target section does not exist"
21012198
summary: |
@@ -2417,6 +2514,12 @@ paths:
24172514
responses:
24182515
"200":
24192516
description: "Success"
2517+
headers:
2518+
Content-Location:
2519+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2520+
schema:
2521+
example: "notes/file.md"
2522+
type: "string"
24202523
"400":
24212524
content:
24222525
application/json:
@@ -2585,8 +2688,20 @@ paths:
25852688
schema:
25862689
type: "string"
25872690
description: "Success; targeted section updated. The full updated file content is returned."
2691+
headers:
2692+
Content-Location:
2693+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2694+
schema:
2695+
example: "notes/file.md"
2696+
type: "string"
25882697
"204":
25892698
description: "Success; content appended to end of file."
2699+
headers:
2700+
Content-Location:
2701+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2702+
schema:
2703+
example: "notes/file.md"
2704+
type: "string"
25902705
"400":
25912706
content:
25922707
application/json:
@@ -2738,8 +2853,20 @@ paths:
27382853
schema:
27392854
type: "string"
27402855
description: "Success; targeted section replaced. The full updated file content is returned."
2856+
headers:
2857+
Content-Location:
2858+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2859+
schema:
2860+
example: "notes/file.md"
2861+
type: "string"
27412862
"204":
27422863
description: "Success; entire file replaced."
2864+
headers:
2865+
Content-Location:
2866+
description: "Vault-relative path of the file that was acted on, e.g. `notes/file.md`. Non-ASCII characters are percent-encoded."
2867+
schema:
2868+
example: "notes/file.md"
2869+
type: "string"
27432870
"400":
27442871
content:
27452872
application/json:
@@ -2986,6 +3113,75 @@ paths:
29863113
tags:
29873114
- "Vault Directories"
29883115
"/vault/{filename}":
3116+
additionalOperations:
3117+
move:
3118+
description: |
3119+
Moves a file from its current location to a new location specified in the Destination header. This operation preserves file history and updates internal links.
3120+
parameters:
3121+
- description: |
3122+
The new path for the file, relative to your vault root. The path must not escape the vault root (relative segments like `..` are permitted as long as the resolved path remains inside the vault). Absolute paths (starting with `/`) are rejected. If the path ends with a trailing slash, the source filename is preserved and the file is placed in that directory (e.g. "archive/" moves "notes/todo.md" to "archive/todo.md"). If the path contains non-ASCII characters (e.g. accented letters), percent-encode the value (e.g. `r%C3%A9sum%C3%A9.md` for `résumé.md`).
3123+
in: "header"
3124+
name: "Destination"
3125+
required: true
3126+
schema:
3127+
format: "path"
3128+
type: "string"
3129+
- description: |
3130+
If "true", the move proceeds even when a file already exists at the destination. Defaults to "false", which returns a 409 if the destination exists.
3131+
in: "header"
3132+
name: "Allow-Overwrite"
3133+
required: false
3134+
schema:
3135+
default: "false"
3136+
enum:
3137+
- "true"
3138+
- "false"
3139+
type: "string"
3140+
- description: "Path to the relevant file (relative to your vault root).\n Optionally, you may include a reference to target a sub-part of the document; see \"Targeting a Sub-part of your Document\" for details."
3141+
in: "path"
3142+
name: "filename"
3143+
required: true
3144+
schema:
3145+
format: "path"
3146+
type: "string"
3147+
responses:
3148+
"204":
3149+
description: "File successfully moved."
3150+
headers:
3151+
Content-Location:
3152+
description: "The vault-relative path of the file at its new location (e.g. `archive/file.md`). Non-ASCII characters are percent-encoded."
3153+
schema:
3154+
type: "string"
3155+
"400":
3156+
content:
3157+
application/json:
3158+
schema:
3159+
"$ref": "#/components/schemas/Error"
3160+
description: |
3161+
Bad request - Missing Destination header, malformed percent-encoding, or path escapes the vault root (e.g. starts with "/").
3162+
"404":
3163+
content:
3164+
application/json:
3165+
schema:
3166+
"$ref": "#/components/schemas/Error"
3167+
description: "Source file does not exist."
3168+
"405":
3169+
content:
3170+
application/json:
3171+
schema:
3172+
"$ref": "#/components/schemas/Error"
3173+
description: |
3174+
Your path references a directory instead of a file; this request method is valid only for files.
3175+
"409":
3176+
content:
3177+
application/json:
3178+
schema:
3179+
"$ref": "#/components/schemas/Error"
3180+
description: "Destination file already exists."
3181+
summary: |
3182+
Move a file to a new location in your vault.
3183+
tags:
3184+
- "Vault Files"
29893185
delete:
29903186
parameters:
29913187
- description: "Path to the relevant file (relative to your vault root).\n Optionally, you may include a reference to target a sub-part of the document; see \"Targeting a Sub-part of your Document\" for details."

docs/src/lib/descriptions/mcp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Include the `MCP-Protocol-Version` header on all requests after initialization,
1414
| `vault_append` | Append content to the end of a vault file |
1515
| `vault_patch` | Patch a specific heading, block reference, or frontmatter field |
1616
| `vault_delete` | Delete a vault file |
17+
| `vault_move` | Move (rename) a vault file to a new path |
1718
| `vault_get_document_map` | List the headings, block references, and frontmatter fields in a file |
1819
| `active_file_get_path` | Return the vault path of the file currently open in Obsidian |
1920
| `periodic_note_get_path` | Return the vault path of the current periodic note (daily, weekly, monthly, quarterly, yearly) |

0 commit comments

Comments
 (0)