Skip to content

Commit bf7e116

Browse files
hifi-philPhil Whittakerclaude
authored
Improve copy-document tool with flattened schema and clearer workflow (#30)
- Flatten tool parameter schema for better LLM usability - Replace nested `id` + `data` structure with top-level parameters - Use `idToCopy` instead of `id` for clarity - Move `relateToOriginal` and `includeDescendants` to top level - Make `parentId` optional (omit for root, provide for specific parent) - Add comprehensive tool description with workflow examples - Document the empty string return value behavior - Provide clear copy-only vs copy-and-update workflow patterns - Explain search-document requirement for post-copy operations - Update e2e test to use update-document instead of search - Simplify workflow: copy → update → publish → delete - Remove unnecessary search-document and document-type lookups - Update allowed tools list to match actual workflow - Pin mcp-server-tester to version 1.4.0 for consistency - Update copy-document unit tests to match new schema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Phil Whittaker <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 4c3ddc7 commit bf7e116

File tree

5 files changed

+41
-34
lines changed

5 files changed

+41
-34
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
1717
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
1818
"patch-publish-alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha --access public",
19-
"eval-mcp:basic": "npx mcp-server-tester evals tests/e2e/basic/basic-tests.yaml --server-config tests/e2e/basic/basic-tests-config.json",
20-
"eval-mcp:create-data-type": "npx mcp-server-tester evals tests/e2e/create-data-type/create-data-type.yaml --server-config tests/e2e/create-data-type/create-data-type-config.json",
21-
"eval-mcp:create-document-type": "npx mcp-server-tester evals tests/e2e/create-document-type/create-document-type.yaml --server-config tests/e2e/create-document-type/create-document-type-config.json",
22-
"eval-mcp:create-blog-post": "npx mcp-server-tester evals tests/e2e/create-blog-post/create-blog-post.yaml --server-config tests/e2e/create-blog-post/create-blog-post-config.json",
19+
"eval-mcp:basic": "npx mcp-server-tester@1.4.0 evals tests/e2e/basic/basic-tests.yaml --server-config tests/e2e/basic/basic-tests-config.json",
20+
"eval-mcp:create-data-type": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-data-type/create-data-type.yaml --server-config tests/e2e/create-data-type/create-data-type-config.json",
21+
"eval-mcp:create-document-type": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-document-type/create-document-type.yaml --server-config tests/e2e/create-document-type/create-document-type-config.json",
22+
"eval-mcp:create-blog-post": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-blog-post/create-blog-post.yaml --server-config tests/e2e/create-blog-post/create-blog-post-config.json",
2323
"eval-mcp:all": "npm run eval-mcp:basic && npm run eval-mcp:create-data-type && npm run eval-mcp:create-document-type && npm run eval-mcp:create-blog-post"
2424
},
2525
"engines": {

src/umb-management-api/tools/document/__tests__/copy-document.test.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,12 @@ describe("copy-document", () => {
3434
.withRootDocumentType()
3535
.create();
3636

37-
// Copy the document to root (no target)
37+
// Copy the document to root (no parentId means root)
3838
const result = await CopyDocumentTool().handler(
3939
{
40-
id: docBuilder.getId(),
41-
data: {
42-
target: null,
43-
relateToOriginal: false,
44-
includeDescendants: false,
45-
},
40+
idToCopy: docBuilder.getId(),
41+
relateToOriginal: false,
42+
includeDescendants: false,
4643
},
4744
{ signal: new AbortController().signal }
4845
);
@@ -73,12 +70,9 @@ describe("copy-document", () => {
7370
it("should handle non-existent document", async () => {
7471
const result = await CopyDocumentTool().handler(
7572
{
76-
id: BLANK_UUID,
77-
data: {
78-
target: null,
79-
relateToOriginal: false,
80-
includeDescendants: false,
81-
},
73+
idToCopy: BLANK_UUID,
74+
relateToOriginal: false,
75+
includeDescendants: false,
8276
},
8377
{ signal: new AbortController().signal }
8478
);

src/umb-management-api/tools/document/post/copy-document.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { UmbracoManagementClient } from "@umb-management-client";
2-
import { postDocumentByIdCopyBody } from "@/umb-management-api/umbracoManagementAPI.zod.js";
32
import { CreateUmbracoTool } from "@/helpers/mcp/create-umbraco-tool.js";
43
import { z } from "zod";
5-
import { CurrentUserResponseModel } from "@/umb-management-api/schemas/index.js";
4+
import { CopyDocumentRequestModel, CurrentUserResponseModel } from "@/umb-management-api/schemas/index.js";
65
import { UmbracoDocumentPermissions } from "../constants.js";
76

7+
const copyDocumentSchema = z.object({
8+
parentId: z.string().uuid("Must be a valid document UUID of the parent node").optional(),
9+
idToCopy: z.string().uuid("Must be a valid document UUID that belongs to the parent document's children"),
10+
relateToOriginal: z.boolean().describe("Relate the copy to the original document. This is usually set to false unless specified."),
11+
includeDescendants: z.boolean().describe("If true, all descendant documents (children, grandchildren, etc.) will also be copied. This is usually set to false unless specified."),
12+
});
13+
814
const CopyDocumentTool = CreateUmbracoTool(
915
"copy-document",
1016
`Copy a document to a new location. This is also the recommended way to create new documents.
@@ -25,13 +31,19 @@ const CopyDocumentTool = CreateUmbracoTool(
2531
Example workflows:
2632
1. Copy only: copy-document (creates draft copy)
2733
2. Copy and update: copy-document → search-document → update-document → publish-document`,
28-
{
29-
id: z.string().uuid(),
30-
data: z.object(postDocumentByIdCopyBody.shape),
31-
},
32-
async (model: { id: string; data: any }) => {
34+
copyDocumentSchema.shape,
35+
async (model) => {
3336
const client = UmbracoManagementClient.getClient();
34-
const response = await client.postDocumentByIdCopy(model.id, model.data);
37+
38+
const payload: CopyDocumentRequestModel = {
39+
target: model.parentId ? {
40+
id: model.parentId,
41+
} : undefined,
42+
relateToOriginal: model.relateToOriginal,
43+
includeDescendants: model.includeDescendants,
44+
};
45+
46+
const response = await client.postDocumentByIdCopy(model.idToCopy, payload);
3547
return {
3648
content: [
3749
{

tests/e2e/create-blog-post/create-blog-post-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"UMBRACO_CLIENT_ID": "umbraco-back-office-mcp",
88
"UMBRACO_CLIENT_SECRET": "1234567890",
99
"UMBRACO_BASE_URL": "http://localhost:56472",
10-
"UMBRACO_INCLUDE_TOOLS": "search-document,get-document-by-id,copy-document,publish-document,delete-document,get-document-root,get-document-children,get-document-publish,get-document-type-by-id,get-all-document-types"
10+
"UMBRACO_INCLUDE_TOOLS": "get-document-root,get-document-children,get-document-publish,get-document-by-id,copy-document,publish-document,delete-document,update-document"
1111
}
1212
}
1313
}

tests/e2e/create-blog-post/create-blog-post.yaml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,31 @@ evals:
99
- name: "Create and manage blog post workflow"
1010
prompt: |
1111
Complete these tasks in order:
12-
1. Copy an existing blog post document and create a new blog post document with the following details:
12+
1. Get the root document of umbraco
13+
2. Find the Blogs document under the root node
14+
3. Copy an existing blog post document
15+
4. Update the copied blog post document with the following details:
1316
- Title: "_Test Blog Post - Creating Amazing Content"
1417
- Content/Body: A rich text content about creating amazing content for blogs
1518
- Author: "Paul Seal"
16-
2. Publish the blog post
17-
3. Delete the blog post
18-
4. When successfully completed all tasks, say 'The blog post workflow has completed successfully', nothing else
19+
5. Publish the blog post
20+
6. Delete the blog post
21+
7. When successfully completed all tasks, say 'The blog post workflow has completed successfully', nothing else
1922
expected_tool_calls:
2023
required:
21-
- "search-document"
2224
- "copy-document"
2325
- "get-document-by-id"
26+
- "update-document"
2427
- "publish-document"
2528
- "delete-document"
2629
allowed:
27-
- "search-document"
2830
- "get-document-root"
2931
- "get-document-children"
3032
- "get-document-by-id"
3133
- "copy-document"
3234
- "publish-document"
33-
- "get-document-publish"
3435
- "delete-document"
35-
- "get-document-type-by-id"
36+
- "update-document"
3637
response_scorers:
3738
- type: 'llm-judge'
3839
criteria: 'Did the last assistant step say "The blog post workflow has completed successfully"'

0 commit comments

Comments
 (0)