@contentful/mcp-server 1.0.4 → 1.1.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/README.md CHANGED
@@ -48,6 +48,10 @@ This MCP server provides a comprehensive set of tools for content management, al
48
48
 
49
49
  _Note: This requires [Cursor](https://cursor.com/) to be installed. If the link doesn't work, try the manual installation below._
50
50
 
51
+ **Claude Desktop:**
52
+
53
+ Download the .dxt configuration file [here](https://github.com/contentful/contentful-mcp-server/releases) from the latest release and import it into Claude Desktop to automatically configure the MCP server with your environment variables.
54
+
51
55
  #### Install from source
52
56
 
53
57
  ```bash
@@ -4,7 +4,7 @@ dotenv.config();
4
4
  const EnvSchema = z.object({
5
5
  CONTENTFUL_MANAGEMENT_ACCESS_TOKEN: z
6
6
  .string()
7
- .describe('Contentul CMA token'),
7
+ .describe('Contentful CMA token'),
8
8
  CONTENTFUL_HOST: z
9
9
  .string()
10
10
  .optional()
@@ -19,7 +19,7 @@ const EnvSchema = z.object({
19
19
  .describe('Contentful environment ID'),
20
20
  });
21
21
  export const env = EnvSchema.safeParse(process.env);
22
- if (!env.success) {
22
+ if (!env.success && process.env.TEST_TYPE !== 'unit') {
23
23
  console.error('Invalid environment variables', env.error.format());
24
24
  process.exit(1);
25
25
  }
@@ -0,0 +1,228 @@
1
+ import { vi } from 'vitest';
2
+ import { createToolClient } from '../../utils/tools.js';
3
+ import { VariableType, OutputFormat } from '../../utils/ai-actions.js';
4
+ /**
5
+ * Shared mock objects for AI action tests
6
+ * Provides standardized mock client and AI action objects used across all AI action tests
7
+ */
8
+ // Create mock functions for the client
9
+ export const mockAiActionGet = vi.fn();
10
+ export const mockAiActionCreate = vi.fn();
11
+ export const mockAiActionUpdate = vi.fn();
12
+ export const mockAiActionDelete = vi.fn();
13
+ export const mockAiActionPublish = vi.fn();
14
+ export const mockAiActionUnpublish = vi.fn();
15
+ export const mockAiActionGetMany = vi.fn();
16
+ export const mockAiActionInvoke = vi.fn();
17
+ export const mockAiActionInvocationGet = vi.fn();
18
+ /**
19
+ * Standard mock Contentful client with all AI action operations
20
+ */
21
+ export const mockClient = {
22
+ aiAction: {
23
+ get: mockAiActionGet,
24
+ create: mockAiActionCreate,
25
+ update: mockAiActionUpdate,
26
+ delete: mockAiActionDelete,
27
+ publish: mockAiActionPublish,
28
+ unpublish: mockAiActionUnpublish,
29
+ getMany: mockAiActionGetMany,
30
+ invoke: mockAiActionInvoke,
31
+ },
32
+ aiActionInvocation: {
33
+ get: mockAiActionInvocationGet,
34
+ },
35
+ };
36
+ /**
37
+ * Sets up the mock client for tests
38
+ * Call this in beforeEach to ensure the mock is properly configured
39
+ */
40
+ export function setupMockClient() {
41
+ vi.mocked(createToolClient).mockReturnValue(mockClient);
42
+ }
43
+ /**
44
+ * Standard mock AI action object used across tests
45
+ */
46
+ export const mockAiAction = {
47
+ sys: {
48
+ id: 'test-ai-action-id',
49
+ type: 'AiAction',
50
+ version: 1,
51
+ space: {
52
+ sys: {
53
+ type: 'Link',
54
+ linkType: 'Space',
55
+ id: 'test-space-id',
56
+ },
57
+ },
58
+ environment: {
59
+ sys: {
60
+ type: 'Link',
61
+ linkType: 'Environment',
62
+ id: 'test-environment',
63
+ },
64
+ },
65
+ createdAt: '2023-01-01T00:00:00Z',
66
+ updatedAt: '2023-01-01T00:00:00Z',
67
+ publishedVersion: undefined,
68
+ },
69
+ name: 'Test AI Action',
70
+ description: 'A test AI action for unit tests',
71
+ instruction: {
72
+ template: 'Translate the following text from {{var.sourceLocale}} to {{var.targetLocale}}: {{var.sourceContent}}',
73
+ variables: [
74
+ {
75
+ id: 'sourceContent',
76
+ name: 'Source Content',
77
+ type: VariableType.TEXT,
78
+ description: 'The content to be translated',
79
+ },
80
+ {
81
+ id: 'sourceLocale',
82
+ name: 'Source Locale',
83
+ type: VariableType.LOCALE,
84
+ description: 'The source language locale',
85
+ },
86
+ {
87
+ id: 'targetLocale',
88
+ name: 'Target Locale',
89
+ type: VariableType.LOCALE,
90
+ description: 'The target language locale',
91
+ },
92
+ ],
93
+ },
94
+ configuration: {
95
+ modelType: 'gpt-4',
96
+ modelTemperature: 0.3,
97
+ },
98
+ testCases: [],
99
+ };
100
+ /**
101
+ * Mock AI action with published version
102
+ */
103
+ export const mockPublishedAiAction = {
104
+ ...mockAiAction,
105
+ sys: {
106
+ ...mockAiAction.sys,
107
+ publishedVersion: 1,
108
+ },
109
+ };
110
+ /**
111
+ * Standard test arguments for AI action operations
112
+ */
113
+ export const mockArgs = {
114
+ spaceId: 'test-space-id',
115
+ environmentId: 'test-environment',
116
+ aiActionId: 'test-ai-action-id',
117
+ };
118
+ /**
119
+ * Mock AI action invocation result
120
+ */
121
+ export const mockAiActionInvocation = {
122
+ sys: {
123
+ id: 'test-invocation-id',
124
+ type: 'AiActionInvocation',
125
+ status: 'COMPLETED',
126
+ createdAt: '2023-01-01T00:00:00Z',
127
+ updatedAt: '2023-01-01T00:00:00Z',
128
+ },
129
+ result: {
130
+ content: 'Translated content result',
131
+ },
132
+ };
133
+ /**
134
+ * Mock AI action invocation status response
135
+ */
136
+ export const mockInvocationStatusResponse = {
137
+ sys: {
138
+ id: 'test-invocation-id',
139
+ status: 'COMPLETED',
140
+ },
141
+ result: {
142
+ content: 'AI action completed successfully',
143
+ },
144
+ };
145
+ /**
146
+ * Mock AI actions list response
147
+ */
148
+ export const mockAiActionsResponse = {
149
+ total: 2,
150
+ skip: 0,
151
+ limit: 3,
152
+ items: [
153
+ mockAiAction,
154
+ {
155
+ ...mockAiAction,
156
+ sys: { ...mockAiAction.sys, id: 'another-ai-action' },
157
+ name: 'Another AI Action',
158
+ description: 'Another test AI action',
159
+ },
160
+ ],
161
+ };
162
+ /**
163
+ * Mock variable values for AI action invocation
164
+ */
165
+ export const mockVariables = [
166
+ {
167
+ id: 'sourceContent',
168
+ value: 'Hello, world!',
169
+ },
170
+ {
171
+ id: 'sourceLocale',
172
+ value: 'en-US',
173
+ },
174
+ {
175
+ id: 'targetLocale',
176
+ value: 'es-ES',
177
+ },
178
+ ];
179
+ /**
180
+ * Mock fields for AI action invocation
181
+ */
182
+ export const mockInvocationFields = [
183
+ {
184
+ outputFormat: OutputFormat.PLAIN_TEXT,
185
+ variables: mockVariables,
186
+ },
187
+ ];
188
+ /**
189
+ * Mock complex AI action with all field types
190
+ */
191
+ export const mockComplexAiAction = {
192
+ ...mockAiAction,
193
+ name: 'Complex AI Action',
194
+ description: 'An AI action with complex variable types',
195
+ instruction: {
196
+ template: 'Process content from {{var.entryRef}} with context {{var.smartContext}} for {{var.mediaRef}}',
197
+ variables: [
198
+ {
199
+ id: 'entryRef',
200
+ name: 'Entry Reference',
201
+ type: VariableType.REFERENCE,
202
+ description: 'Reference to a content entry',
203
+ },
204
+ {
205
+ id: 'smartContext',
206
+ name: 'Smart Context',
207
+ type: VariableType.SMART_CONTEXT,
208
+ description: 'AI-powered context understanding',
209
+ },
210
+ {
211
+ id: 'mediaRef',
212
+ name: 'Media Reference',
213
+ type: VariableType.MEDIA_REFERENCE,
214
+ description: 'Reference to media assets',
215
+ },
216
+ ],
217
+ },
218
+ configuration: {
219
+ modelType: 'gpt-3.5-turbo',
220
+ modelTemperature: 0.7,
221
+ },
222
+ testCases: [
223
+ {
224
+ name: 'Test case 1',
225
+ variables: {},
226
+ },
227
+ ],
228
+ };
@@ -0,0 +1,134 @@
1
+ import { vi } from 'vitest';
2
+ import { createToolClient } from '../../utils/tools.js';
3
+ /**
4
+ * Shared mock objects for content type tests
5
+ * Provides standardized mock client and content type objects used across all content type tests
6
+ */
7
+ // Create mock functions for the client
8
+ export const mockContentTypeGet = vi.fn();
9
+ export const mockContentTypeCreate = vi.fn();
10
+ export const mockContentTypeUpdate = vi.fn();
11
+ export const mockContentTypeDelete = vi.fn();
12
+ export const mockContentTypePublish = vi.fn();
13
+ export const mockContentTypeUnpublish = vi.fn();
14
+ export const mockContentTypeGetMany = vi.fn();
15
+ /**
16
+ * Standard mock Contentful client with all content type operations
17
+ */
18
+ export const mockClient = {
19
+ contentType: {
20
+ get: mockContentTypeGet,
21
+ create: mockContentTypeCreate,
22
+ update: mockContentTypeUpdate,
23
+ delete: mockContentTypeDelete,
24
+ publish: mockContentTypePublish,
25
+ unpublish: mockContentTypeUnpublish,
26
+ getMany: mockContentTypeGetMany,
27
+ },
28
+ };
29
+ /**
30
+ * Sets up the mock client for tests
31
+ * Call this in beforeEach to ensure the mock is properly configured
32
+ */
33
+ export function setupMockClient() {
34
+ vi.mocked(createToolClient).mockReturnValue(mockClient);
35
+ }
36
+ /**
37
+ * Standard mock content type object used across tests
38
+ */
39
+ export const mockContentType = {
40
+ sys: {
41
+ id: 'test-content-type-id',
42
+ type: 'ContentType',
43
+ version: 1,
44
+ space: {
45
+ sys: {
46
+ type: 'Link',
47
+ linkType: 'Space',
48
+ id: 'test-space-id',
49
+ },
50
+ },
51
+ environment: {
52
+ sys: {
53
+ type: 'Link',
54
+ linkType: 'Environment',
55
+ id: 'test-environment',
56
+ },
57
+ },
58
+ createdAt: '2023-01-01T00:00:00Z',
59
+ updatedAt: '2023-01-01T00:00:00Z',
60
+ },
61
+ name: 'Test Content Type',
62
+ description: 'A test content type for unit tests',
63
+ displayField: 'title',
64
+ fields: [
65
+ {
66
+ id: 'title',
67
+ name: 'Title',
68
+ type: 'Symbol',
69
+ required: true,
70
+ localized: false,
71
+ validations: [],
72
+ },
73
+ {
74
+ id: 'description',
75
+ name: 'Description',
76
+ type: 'Text',
77
+ required: false,
78
+ localized: false,
79
+ validations: [],
80
+ },
81
+ ],
82
+ };
83
+ /**
84
+ * Standard test arguments for content type operations
85
+ */
86
+ export const mockArgs = {
87
+ spaceId: 'test-space-id',
88
+ environmentId: 'test-environment',
89
+ contentTypeId: 'test-content-type-id',
90
+ };
91
+ /**
92
+ * Mock field definitions for testing
93
+ */
94
+ export const mockField = {
95
+ id: 'title',
96
+ name: 'Title',
97
+ type: 'Symbol',
98
+ required: true,
99
+ localized: false,
100
+ validations: [],
101
+ };
102
+ export const mockTextField = {
103
+ id: 'description',
104
+ name: 'Description',
105
+ type: 'Text',
106
+ required: false,
107
+ localized: false,
108
+ validations: [],
109
+ };
110
+ export const mockLinkField = {
111
+ id: 'relatedEntry',
112
+ name: 'Related Entry',
113
+ type: 'Link',
114
+ linkType: 'Entry',
115
+ required: false,
116
+ localized: false,
117
+ validations: [],
118
+ };
119
+ /**
120
+ * Mock content types list response
121
+ */
122
+ export const mockContentTypesResponse = {
123
+ total: 2,
124
+ skip: 0,
125
+ limit: 10,
126
+ items: [
127
+ mockContentType,
128
+ {
129
+ ...mockContentType,
130
+ sys: { ...mockContentType.sys, id: 'another-content-type' },
131
+ name: 'Another Content Type',
132
+ },
133
+ ],
134
+ };
@@ -0,0 +1,78 @@
1
+ import { vi } from 'vitest';
2
+ import { createToolClient } from '../../utils/tools.js';
3
+ /**
4
+ * Shared mock objects for entry tests
5
+ * Provides standardized mock client and entry objects used across all entry tests
6
+ */
7
+ // Create mock functions for the client
8
+ export const mockEntryGet = vi.fn();
9
+ export const mockEntryCreate = vi.fn();
10
+ export const mockEntryUpdate = vi.fn();
11
+ export const mockEntryDelete = vi.fn();
12
+ export const mockEntryPublish = vi.fn();
13
+ export const mockEntryUnpublish = vi.fn();
14
+ export const mockEntryGetMany = vi.fn();
15
+ export const mockBulkActionPublish = vi.fn();
16
+ export const mockBulkActionUnpublish = vi.fn();
17
+ /**
18
+ * Standard mock Contentful client with all entry operations
19
+ */
20
+ export const mockClient = {
21
+ entry: {
22
+ get: mockEntryGet,
23
+ create: mockEntryCreate,
24
+ update: mockEntryUpdate,
25
+ delete: mockEntryDelete,
26
+ publish: mockEntryPublish,
27
+ unpublish: mockEntryUnpublish,
28
+ getMany: mockEntryGetMany,
29
+ },
30
+ bulkAction: {
31
+ publish: mockBulkActionPublish,
32
+ unpublish: mockBulkActionUnpublish,
33
+ },
34
+ };
35
+ /**
36
+ * Sets up the mock client for tests
37
+ * Call this in beforeEach to ensure the mock is properly configured
38
+ */
39
+ export function setupMockClient() {
40
+ vi.mocked(createToolClient).mockReturnValue(mockClient);
41
+ }
42
+ /**
43
+ * Standard mock entry object used across tests
44
+ */
45
+ export const mockEntry = {
46
+ sys: {
47
+ id: 'test-entry-id',
48
+ type: 'Entry',
49
+ contentType: {
50
+ sys: {
51
+ id: 'test-content-type',
52
+ type: 'Link',
53
+ linkType: 'ContentType',
54
+ },
55
+ },
56
+ version: 1,
57
+ },
58
+ fields: {
59
+ title: { 'en-US': 'Test Entry Title' },
60
+ description: { 'en-US': 'Test Entry Description' },
61
+ },
62
+ };
63
+ /**
64
+ * Standard test arguments for entry operations
65
+ */
66
+ export const mockArgs = {
67
+ spaceId: 'test-space-id',
68
+ environmentId: 'test-environment',
69
+ entryId: 'test-entry-id',
70
+ };
71
+ /**
72
+ * Standard test arguments for bulk operations
73
+ */
74
+ export const mockBulkArgs = {
75
+ spaceId: 'test-space-id',
76
+ environmentId: 'test-environment',
77
+ entryId: ['entry-1', 'entry-2'],
78
+ };
@@ -17,27 +17,19 @@ async function tool(args) {
17
17
  const entryIds = Array.isArray(args.entryId) ? args.entryId : [args.entryId];
18
18
  // For single entry, use individual publish for simplicity
19
19
  if (entryIds.length === 1) {
20
- try {
21
- const entryId = entryIds[0];
22
- const params = {
23
- ...baseParams,
24
- entryId,
25
- };
26
- // Get the entry first
27
- const entry = await contentfulClient.entry.get(params);
28
- // Publish the entry
29
- const publishedEntry = await contentfulClient.entry.publish(params, entry);
30
- return createSuccessResponse('Entry published successfully', {
31
- status: publishedEntry.sys.status,
32
- entryId,
33
- });
34
- }
35
- catch (error) {
36
- return createSuccessResponse('Entry publish failed', {
37
- status: error,
38
- entryId: entryIds[0],
39
- });
40
- }
20
+ const entryId = entryIds[0];
21
+ const params = {
22
+ ...baseParams,
23
+ entryId,
24
+ };
25
+ // Get the entry first
26
+ const entry = await contentfulClient.entry.get(params);
27
+ // Publish the entry
28
+ const publishedEntry = await contentfulClient.entry.publish(params, entry);
29
+ return createSuccessResponse('Entry published successfully', {
30
+ status: publishedEntry.sys.status,
31
+ entryId,
32
+ });
41
33
  }
42
34
  // For multiple entries, use bulk action API
43
35
  // Get the current version of each entry
@@ -15,29 +15,21 @@ async function tool(args) {
15
15
  const contentfulClient = createToolClient(args);
16
16
  // Normalize input to always be an array
17
17
  const entryIds = Array.isArray(args.entryId) ? args.entryId : [args.entryId];
18
- // For single entry, use individual publish for simplicity
18
+ // For single entry, use individual unpublish for simplicity
19
19
  if (entryIds.length === 1) {
20
- try {
21
- const entryId = entryIds[0];
22
- const params = {
23
- ...baseParams,
24
- entryId,
25
- };
26
- // Get the entry first
27
- const entry = await contentfulClient.entry.get(params);
28
- // Unpublish the entry
29
- const unpublishedEntry = await contentfulClient.entry.unpublish(params, entry);
30
- return createSuccessResponse('Entry unpublished successfully', {
31
- status: unpublishedEntry.sys.status,
32
- entryId,
33
- });
34
- }
35
- catch (error) {
36
- return createSuccessResponse('Entry unpublish failed', {
37
- status: error,
38
- entryId: entryIds[0],
39
- });
40
- }
20
+ const entryId = entryIds[0];
21
+ const params = {
22
+ ...baseParams,
23
+ entryId,
24
+ };
25
+ // Get the entry first
26
+ const entry = await contentfulClient.entry.get(params);
27
+ // Unpublish the entry
28
+ const unpublishedEntry = await contentfulClient.entry.unpublish(params, entry);
29
+ return createSuccessResponse('Entry unpublished successfully', {
30
+ status: unpublishedEntry.sys.status,
31
+ entryId,
32
+ });
41
33
  }
42
34
  // For multiple entries, use bulk action API
43
35
  // Get the unversioned links for each entry (unpublish doesn't need version info)
@@ -6,7 +6,7 @@ export const CreateLocaleToolParams = BaseToolSchema.extend({
6
6
  code: z.string().describe('The locale code (e.g., "en-US")'),
7
7
  fallbackCode: z
8
8
  .string()
9
- .optional()
9
+ .nullable()
10
10
  .describe('The locale code to fallback to when there is no content for the current locale'),
11
11
  contentDeliveryApi: z
12
12
  .boolean()
@@ -1,6 +1,6 @@
1
1
  import { registerContextTools } from './context/register.js';
2
2
  import { registerEntriesTools } from './entries/register.js';
3
- import { registerContentTypesTools } from './types/register.js';
3
+ import { registerContentTypesTools } from './content-types/register.js';
4
4
  import { registerEnvironmentTools } from './environments/register.js';
5
5
  import { registerAssetTools } from './assets/register.js';
6
6
  import { registerSpaceTools } from './spaces/register.js';
@@ -5,8 +5,8 @@ export const FieldSchema = z.object({
5
5
  type: z
6
6
  .string()
7
7
  .describe('The field type (e.g., "Symbol", "Text", "Integer", "Boolean", "Date", "Location", "Link", "Array", "Object")'),
8
- required: z.boolean().optional().describe('Whether the field is required'),
9
- localized: z.boolean().optional().describe('Whether the field is localized'),
8
+ required: z.boolean().describe('Whether the field is required'),
9
+ localized: z.boolean().describe('Whether the field is localized'),
10
10
  disabled: z.boolean().optional().describe('Whether the field is disabled'),
11
11
  omitted: z
12
12
  .boolean()
@@ -14,6 +14,7 @@ export const FieldSchema = z.object({
14
14
  .describe('Whether the field is omitted from the API response'),
15
15
  validations: z.array(z.any()).optional().describe('Field validations'),
16
16
  settings: z.record(z.any()).optional().describe('Field-specific settings'),
17
+ defaultValue: z.any().optional().describe('Default value for the field'),
17
18
  linkType: z
18
19
  .string()
19
20
  .optional()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/mcp-server",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/contentful/contentful-mcp-server"
@@ -18,7 +18,8 @@
18
18
  "npm": ">=10.0.0"
19
19
  },
20
20
  "scripts": {
21
- "build": "tsc",
21
+ "clean": "rimraf build",
22
+ "build": "npm run clean && tsc",
22
23
  "dev": "nodemon --ext ts --ignore build/ --watch src/ --exec 'npm run build && npm start'",
23
24
  "format": "prettier --write .",
24
25
  "lint": "eslint .",
@@ -43,6 +44,7 @@
43
44
  "zod": "^3.25.76"
44
45
  },
45
46
  "devDependencies": {
47
+ "@anthropic-ai/dxt": "^0.2.6",
46
48
  "@commitlint/cli": "^19.8.1",
47
49
  "@commitlint/config-conventional": "^19.8.1",
48
50
  "@modelcontextprotocol/inspector": "0.16.2",
@@ -57,6 +59,7 @@
57
59
  "nx": "^21.3.5",
58
60
  "prettier": "^3.6.2",
59
61
  "pretty-quick": "^4.2.2",
62
+ "rimraf": "^6.0.1",
60
63
  "ts-node": "^10.9.2",
61
64
  "typescript": "^5.8.2",
62
65
  "vitest": "^3.2.4"