diff --git a/LICENSE b/LICENSE index 60ac9c6..ef3cb91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Matthew-Wise +Copyright (c) 2025-present Umbraco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9ed1175..c683cfc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Umbraco MCP ![GitHub License](https://img.shields.io/github/license/matthew-wise/umbraco-mcp?style=plastic&link=https%3A%2F%2Fgithub.com%2FMatthew-Wise%2Fumbraco-mcp%3Ftab%3DMIT-1-ov-file%23readme) +# Umbraco MCP ![GitHub License](https://img.shields.io/github/license/umbraco/Umbraco-CMS-MCP-Dev?style=plastic&link=https%3A%2F%2Fgithub.com%2Fumbraco%2FUmbraco-CMS-MCP-Dev%2Fblob%2Fmain%2FLICENSE) An MCP (Model Context Protocol) server for [Umbraco CMS](https://umbraco.com/) it provides access to key parts of the Management API enabling you to do back office tasks with your agent. @@ -34,7 +34,7 @@ Once you have this information head back into Claude desktop app and head to Set "mcpServers": { "umbraco-mcp": { "command": "npx", - "args": ["@umbraco-mcp/umbraco-mcp-cms@alpha"], + "args": ["@umbraco-cms/mcp-dev@beta"], "env": { "NODE_TLS_REJECT_UNAUTHORIZED": "0", "UMBRACO_CLIENT_ID": "umbraco-back-office-mcp", @@ -62,7 +62,7 @@ Restart Claude and try it out with a simple prompt such as `Tell me the GUID of Use the Claude Code CLI to add the Umbraco MCP server: ```bash -claude mcp add umbraco-mcp npx @umbraco-mcp/umbraco-mcp-cms@alpha +claude mcp add umbraco-mcp npx @umbraco-cms/mcp-dev@beta ``` Or configure environment variables and scope: @@ -71,7 +71,7 @@ Or configure environment variables and scope: npm install -g @anthropic-ai/claude-code # Add with environment variables -claude mcp add umbraco-mcp --env UMBRACO_CLIENT_ID="your-id" --env UMBRACO_CLIENT_SECRET="your-secret" --env UMBRACO_BASE_URL="https://your-domain.com" --env NODE_TLS_REJECT_UNAUTHORIZED="0" --env UMBRACO_INCLUDE_TOOL_COLLECTIONS="culture,document,media" -- npx @umbraco-mcp/umbraco-mcp-cms@alpha +claude mcp add umbraco-mcp --env UMBRACO_CLIENT_ID="your-id" --env UMBRACO_CLIENT_SECRET="your-secret" --env UMBRACO_BASE_URL="https://your-domain.com" --env NODE_TLS_REJECT_UNAUTHORIZED="0" --env UMBRACO_INCLUDE_TOOL_COLLECTIONS="culture,document,media" -- npx @umbraco-cms/mcp-dev@beta # Verify installation claude mcp list @@ -99,7 +99,7 @@ Follow the MCP [install guide](https://code.visualstudio.com/docs/copilot/custom "umbraco-mcp": { "type": "stdio", "command": "npx", - "args": ["@umbraco-mcp/umbraco-mcp-cms@alpha"], + "args": ["@umbraco-cms/mcp-dev@beta"], "env": { "UMBRACO_CLIENT_ID": "", "UMBRACO_CLIENT_SECRET": "", @@ -127,7 +127,7 @@ Add the following to the config file and update the env variables. "mcpServers": { "umbraco-mcp": { "command": "npx", - "args": ["@umbraco-mcp/umbraco-mcp-cms@alpha"], + "args": ["@umbraco-cms/mcp-dev@beta"], "env": { "UMBRACO_CLIENT_ID": "", "UMBRACO_CLIENT_SECRET": "", diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 9cf1262..10b2a51 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -71,8 +71,25 @@ stages: # Setup temp npm project to load in defaults from the local .npmrc npm init -y - # Find the first .tgz file in the current directory and publish it + # Find the first .tgz file in the current directory files=( ./*.tgz ) - npm publish "${files[0]}" - displayName: Push to npm + + # Extract version from package.json in the tarball + tar -tf "${files[0]}" | grep package/package.json | head -1 | xargs tar -xf "${files[0]}" --strip-components=1 + version=$(node -p "require('./package.json').version") + + # Determine tag based on version + if [[ "${version}" == *"alpha"* ]]; then + tag="alpha" + elif [[ "${version}" == *"beta"* ]]; then + tag="beta" + elif [[ "${version}" == *"rc"* ]]; then + tag="rc" + else + tag="latest" + fi + + echo "Publishing version ${version} with tag ${tag}" + npm publish "${files[0]}" --tag $tag --access public + displayName: Push to npm with dynamic tagging workingDirectory: $(Pipeline.Workspace)/npm-package \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d09f442..4fb46d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@umbraco-mcp/umbraco-mcp-cms", - "version": "0.1.0-alpha.7", + "name": "@umbraco-cms/mcp-dev", + "version": "16.0.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@umbraco-mcp/umbraco-mcp-cms", - "version": "0.1.0-alpha.7", + "name": "@umbraco-cms/mcp-dev", + "version": "16.0.0-beta.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.9.0", diff --git a/package.json b/package.json index 5279b9d..a4f5268 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@umbraco-mcp/umbraco-mcp-cms", - "version": "0.1.0-alpha.7", + "name": "@umbraco-cms/mcp-dev", + "version": "16.0.0-beta.1", "type": "module", "description": "A model context protocol (MCP) server for Umbraco CMS", "main": "index.js", @@ -30,7 +30,7 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/Matthew-Wise/umbraco-mcp.git" + "url": "git+https://github.com/umbraco/Umbraco-CMS-MCP-Dev" }, "keywords": [ "Umbraco", @@ -43,9 +43,9 @@ "author": "", "license": "MIT", "bugs": { - "url": "https://github.com/Matthew-Wise/umbraco-mcp/issues" + "url": "https://github.com/umbraco/Umbraco-CMS-MCP-Dev/issues" }, - "homepage": "https://github.com/Matthew-Wise/umbraco-mcp#readme", + "homepage": "https://github.com/umbraco/Umbraco-CMS-MCP-Dev#readme", "dependencies": { "@modelcontextprotocol/sdk": "^1.9.0", "@types/uuid": "^10.0.0", diff --git a/src/umb-management-api/tools/document/__tests__/create-document.test.ts b/src/umb-management-api/tools/document/__tests__/create-document.test.ts index 21a7d1b..06d69c7 100644 --- a/src/umb-management-api/tools/document/__tests__/create-document.test.ts +++ b/src/umb-management-api/tools/document/__tests__/create-document.test.ts @@ -74,4 +74,49 @@ describe("create-document", () => { }; expect(norm).toMatchSnapshot(); }); + + it("should create a document with specific cultures", async () => { + // Create document with specific cultures + const docModel = { + documentTypeId: ROOT_DOCUMENT_TYPE_ID, + name: TEST_DOCUMENT_NAME, + cultures: ["en-US", "da-DK"], + values: [], + }; + + const result = await CreateDocumentTool().handler(docModel, { + signal: new AbortController().signal, + }); + + expect(result).toMatchSnapshot(); + + const item = await DocumentTestHelper.findDocument(TEST_DOCUMENT_NAME); + expect(item).toBeDefined(); + // Should have variants for both cultures + expect(item!.variants).toHaveLength(2); + const cultures = item!.variants.map(v => v.culture).sort(); + expect(cultures).toEqual(["da-DK", "en-US"]); + }); + + it("should create a document with empty cultures array (null culture)", async () => { + // Create document with empty cultures array - should behave like original (null culture) + const docModel = { + documentTypeId: ROOT_DOCUMENT_TYPE_ID, + name: TEST_DOCUMENT_NAME, + cultures: [], + values: [], + }; + + const result = await CreateDocumentTool().handler(docModel, { + signal: new AbortController().signal, + }); + + expect(result).toMatchSnapshot(); + + const item = await DocumentTestHelper.findDocument(TEST_DOCUMENT_NAME); + expect(item).toBeDefined(); + // Should have single variant with null culture (original behavior) + expect(item!.variants).toHaveLength(1); + expect(item!.variants[0].culture).toBeNull(); + }); }); diff --git a/src/umb-management-api/tools/document/post/create-document.ts b/src/umb-management-api/tools/document/post/create-document.ts index 59b89d2..a81a3d5 100644 --- a/src/umb-management-api/tools/document/post/create-document.ts +++ b/src/umb-management-api/tools/document/post/create-document.ts @@ -10,6 +10,7 @@ const createDocumentSchema = z.object({ documentTypeId: z.string().uuid("Must be a valid document type type UUID"), parentId: z.string().uuid("Must be a valid document UUID").optional(), name: z.string(), + cultures: z.array(z.string()).optional().describe("Array of culture codes. If not provided or empty array, will create single variant with null culture."), values: z .array( z.object({ @@ -25,7 +26,10 @@ const createDocumentSchema = z.object({ const CreateDocumentTool = CreateUmbracoTool( "create-document", - `Creates a document, + `Creates a document with support for multiple cultures. + + If cultures parameter is provided, a variant will be created for each culture code. + If cultures parameter is not provided or is an empty array, will create a single variant with null culture (original behavior). Always follow these requirements when creating documents exactly, do not deviate in any way. @@ -633,6 +637,24 @@ const CreateDocumentTool = CreateUmbracoTool( const documentId = uuidv4(); + // Determine cultures to use + let culturesToUse: (string | null)[] = []; + + if (model.cultures === undefined || model.cultures.length === 0) { + // If cultures not provided or empty array, use original behavior (null culture) + culturesToUse = [null]; + } else { + // Use provided cultures + culturesToUse = model.cultures; + } + + // Create variants for each culture + const variants = culturesToUse.map(culture => ({ + culture, + name: model.name, + segment: null, + })); + const payload: CreateDocumentRequestModel = { id: documentId, documentType: { @@ -645,13 +667,7 @@ const CreateDocumentTool = CreateUmbracoTool( : undefined, template: null, values: model.values, - variants: [ - { - culture: null, - name: model.name, - segment: null, - }, - ], + variants, }; const response = await client.postDocument(payload);