move_node_relative
Reposition outline nodes by moving a selected node and its children to a new location relative to another node, enabling intuitive reorganization of document structure.
Instructions
Move a node (and all its children) to a new position relative to a reference node. This is the intuitive way to reorganize your outline - just specify where you want the node to go.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source_url | Yes | URL of the node to move (with deep link #z=nodeId). The entire subtree (node + all descendants) will be moved. | |
| reference_url | Yes | URL of the reference node that determines the target location | |
| position | Yes | Where to place the node relative to the reference: 'after' = immediately after the reference (same parent, same level), 'before' = immediately before the reference (same parent, same level), 'as_first_child' = as the first child inside the reference node, 'as_last_child' = as the last child inside the reference node |
Implementation Reference
- src/tools/index.ts:871-940 (handler)The core handler function implementing the move_node_relative tool: parses URLs, validates same document, computes target parent/index based on position (using findNodeParent helper), performs move via client.editDocument, returns success message with new URL.async ({ source_url, reference_url, position }) => { // Parse URLs const sourceParsed = parseDynalistUrl(source_url); const refParsed = parseDynalistUrl(reference_url); if (!sourceParsed.nodeId) { return { content: [{ type: "text", text: "Error: source_url must include a node deep link (#z=nodeId)" }], isError: true, }; } if (!refParsed.nodeId) { return { content: [{ type: "text", text: "Error: reference_url must include a node deep link (#z=nodeId)" }], isError: true, }; } if (sourceParsed.documentId !== refParsed.documentId) { return { content: [{ type: "text", text: "Error: Both nodes must be in the same document" }], isError: true, }; } const documentId = sourceParsed.documentId; const sourceNodeId = sourceParsed.nodeId; const refNodeId = refParsed.nodeId; // Fetch document to find parent/index const doc = await client.readDocument(documentId); let targetParentId: string; let targetIndex: number; if (position === "as_first_child") { targetParentId = refNodeId; targetIndex = 0; } else if (position === "as_last_child") { targetParentId = refNodeId; targetIndex = -1; } else { // "after" or "before" - find the parent of the reference node const refParentInfo = findNodeParent(doc.nodes, refNodeId); if (!refParentInfo) { return { content: [{ type: "text", text: "Error: Could not find parent of reference node" }], isError: true, }; } targetParentId = refParentInfo.parentId; targetIndex = position === "after" ? refParentInfo.index + 1 : refParentInfo.index; } // Execute move await client.editDocument(documentId, [ { action: "move", node_id: sourceNodeId, parent_id: targetParentId, index: targetIndex } ]); return { content: [ { type: "text", text: `Node moved successfully!\nSource: ${sourceNodeId}\nPosition: ${position} reference node\nNew Parent: ${targetParentId}\nNew URL: ${buildDynalistUrl(documentId, sourceNodeId)}`, }, ], }; }
- src/tools/index.ts:860-870 (schema)Zod input schema defining parameters: source_url (string), reference_url (string), position (enum: after, before, as_first_child, as_last_child) with detailed descriptions.{ source_url: z.string().describe("URL of the node to move (with deep link #z=nodeId). The entire subtree (node + all descendants) will be moved."), reference_url: z.string().describe("URL of the reference node that determines the target location"), position: z.enum(["after", "before", "as_first_child", "as_last_child"]).describe( "Where to place the node relative to the reference: " + "'after' = immediately after the reference (same parent, same level), " + "'before' = immediately before the reference (same parent, same level), " + "'as_first_child' = as the first child inside the reference node, " + "'as_last_child' = as the last child inside the reference node" ), },
- src/tools/index.ts:857-941 (registration)MCP server.tool registration for 'move_node_relative' including name, description, schema, and inline handler function.server.tool( "move_node_relative", "Move a node (and all its children) to a new position relative to a reference node. This is the intuitive way to reorganize your outline - just specify where you want the node to go.", { source_url: z.string().describe("URL of the node to move (with deep link #z=nodeId). The entire subtree (node + all descendants) will be moved."), reference_url: z.string().describe("URL of the reference node that determines the target location"), position: z.enum(["after", "before", "as_first_child", "as_last_child"]).describe( "Where to place the node relative to the reference: " + "'after' = immediately after the reference (same parent, same level), " + "'before' = immediately before the reference (same parent, same level), " + "'as_first_child' = as the first child inside the reference node, " + "'as_last_child' = as the last child inside the reference node" ), }, async ({ source_url, reference_url, position }) => { // Parse URLs const sourceParsed = parseDynalistUrl(source_url); const refParsed = parseDynalistUrl(reference_url); if (!sourceParsed.nodeId) { return { content: [{ type: "text", text: "Error: source_url must include a node deep link (#z=nodeId)" }], isError: true, }; } if (!refParsed.nodeId) { return { content: [{ type: "text", text: "Error: reference_url must include a node deep link (#z=nodeId)" }], isError: true, }; } if (sourceParsed.documentId !== refParsed.documentId) { return { content: [{ type: "text", text: "Error: Both nodes must be in the same document" }], isError: true, }; } const documentId = sourceParsed.documentId; const sourceNodeId = sourceParsed.nodeId; const refNodeId = refParsed.nodeId; // Fetch document to find parent/index const doc = await client.readDocument(documentId); let targetParentId: string; let targetIndex: number; if (position === "as_first_child") { targetParentId = refNodeId; targetIndex = 0; } else if (position === "as_last_child") { targetParentId = refNodeId; targetIndex = -1; } else { // "after" or "before" - find the parent of the reference node const refParentInfo = findNodeParent(doc.nodes, refNodeId); if (!refParentInfo) { return { content: [{ type: "text", text: "Error: Could not find parent of reference node" }], isError: true, }; } targetParentId = refParentInfo.parentId; targetIndex = position === "after" ? refParentInfo.index + 1 : refParentInfo.index; } // Execute move await client.editDocument(documentId, [ { action: "move", node_id: sourceNodeId, parent_id: targetParentId, index: targetIndex } ]); return { content: [ { type: "text", text: `Node moved successfully!\nSource: ${sourceNodeId}\nPosition: ${position} reference node\nNew Parent: ${targetParentId}\nNew URL: ${buildDynalistUrl(documentId, sourceNodeId)}`, }, ], }; } );