@contentful/mcp-server 1.0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/build/config/contentful.js +19 -0
  4. package/build/config/env.js +25 -0
  5. package/build/config/version.js +2 -0
  6. package/build/index.js +33 -0
  7. package/build/prompts/register.js +1 -0
  8. package/build/resources/register.js +1 -0
  9. package/build/tools/ai-actions/createAiAction.js +55 -0
  10. package/build/tools/ai-actions/deleteAiAction.js +20 -0
  11. package/build/tools/ai-actions/getAiAction.js +20 -0
  12. package/build/tools/ai-actions/getAiActionInvocation.js +21 -0
  13. package/build/tools/ai-actions/invokeAiAction.js +74 -0
  14. package/build/tools/ai-actions/listAiActions.js +65 -0
  15. package/build/tools/ai-actions/publishAiAction.js +34 -0
  16. package/build/tools/ai-actions/register.js +20 -0
  17. package/build/tools/ai-actions/unpublishAiAction.js +28 -0
  18. package/build/tools/ai-actions/updateAiAction.js +64 -0
  19. package/build/tools/assets/deleteAsset.js +20 -0
  20. package/build/tools/assets/getAsset.js +18 -0
  21. package/build/tools/assets/listAssets.js +68 -0
  22. package/build/tools/assets/publishAsset.js +58 -0
  23. package/build/tools/assets/register.js +16 -0
  24. package/build/tools/assets/unpublishAsset.js +58 -0
  25. package/build/tools/assets/updateAsset.js +42 -0
  26. package/build/tools/assets/uploadAsset.js +51 -0
  27. package/build/tools/context/getInitialContextTool.js +41 -0
  28. package/build/tools/context/instructions.js +111 -0
  29. package/build/tools/context/middleware.js +8 -0
  30. package/build/tools/context/register.js +4 -0
  31. package/build/tools/context/store.js +15 -0
  32. package/build/tools/entries/createEntry.js +39 -0
  33. package/build/tools/entries/deleteEntry.js +21 -0
  34. package/build/tools/entries/getEntry.js +18 -0
  35. package/build/tools/entries/publishEntry.js +58 -0
  36. package/build/tools/entries/register.js +16 -0
  37. package/build/tools/entries/searchEntries.js +50 -0
  38. package/build/tools/entries/unpublishEntry.js +58 -0
  39. package/build/tools/entries/updateEntry.js +50 -0
  40. package/build/tools/environments/createEnvironment.js +21 -0
  41. package/build/tools/environments/deleteEnvironment.js +19 -0
  42. package/build/tools/environments/listEnvironments.js +62 -0
  43. package/build/tools/environments/register.js +8 -0
  44. package/build/tools/locales/createLocale.js +49 -0
  45. package/build/tools/locales/deleteLocale.js +21 -0
  46. package/build/tools/locales/getLocale.js +18 -0
  47. package/build/tools/locales/listLocales.js +61 -0
  48. package/build/tools/locales/register.js +12 -0
  49. package/build/tools/locales/updateLocale.js +47 -0
  50. package/build/tools/register.js +20 -0
  51. package/build/tools/spaces/getSpace.js +20 -0
  52. package/build/tools/spaces/listSpaces.js +53 -0
  53. package/build/tools/spaces/register.js +6 -0
  54. package/build/tools/tags/createTag.js +24 -0
  55. package/build/tools/tags/listTags.js +50 -0
  56. package/build/tools/tags/register.js +6 -0
  57. package/build/tools/types/createContentType.js +33 -0
  58. package/build/tools/types/deleteContentType.js +20 -0
  59. package/build/tools/types/getContentType.js +24 -0
  60. package/build/tools/types/listContentTypes.js +59 -0
  61. package/build/tools/types/publishContentType.js +22 -0
  62. package/build/tools/types/register.js +16 -0
  63. package/build/tools/types/unpublishContentType.js +20 -0
  64. package/build/tools/types/updateContentType.js +78 -0
  65. package/build/types/fieldSchema.js +29 -0
  66. package/build/utils/ai-actions.js +46 -0
  67. package/build/utils/bulkOperations.js +94 -0
  68. package/build/utils/formatters.js +39 -0
  69. package/build/utils/getVersion.js +11 -0
  70. package/build/utils/response.js +37 -0
  71. package/build/utils/summarizer.js +39 -0
  72. package/build/utils/tools.js +20 -0
  73. package/package.json +64 -0
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ import { VariableType } from '../../utils/ai-actions.js';
5
+ export const UpdateAiActionToolParams = BaseToolSchema.extend({
6
+ aiActionId: z.string().describe('The ID of the AI action to update'),
7
+ name: z.string().optional().describe('The name of the AI action'),
8
+ description: z
9
+ .string()
10
+ .optional()
11
+ .describe('The description of the AI action'),
12
+ instruction: z
13
+ .object({
14
+ template: z.string().describe('The template for the AI action'),
15
+ variables: z
16
+ .array(z.object({
17
+ id: z.string().describe('The id of the variable'),
18
+ name: z.string().optional().describe('The name of the variable'),
19
+ type: z
20
+ .nativeEnum(VariableType)
21
+ .describe('The type of the variable'),
22
+ description: z
23
+ .string()
24
+ .optional()
25
+ .describe('The description of the variable'),
26
+ }))
27
+ .describe('Array of variables for the AI action'),
28
+ })
29
+ .optional()
30
+ .describe('The instruction for the AI action'),
31
+ configuration: z
32
+ .object({
33
+ modelType: z.string().describe('The type of model to use'),
34
+ modelTemperature: z.number().describe('The temperature for the model'),
35
+ })
36
+ .optional()
37
+ .describe('The configuration for the AI action'),
38
+ testCases: z
39
+ .array(z.any())
40
+ .optional()
41
+ .describe('Test cases for the AI action'),
42
+ });
43
+ async function tool(args) {
44
+ const params = {
45
+ spaceId: args.spaceId,
46
+ environmentId: args.environmentId,
47
+ aiActionId: args.aiActionId,
48
+ };
49
+ const contentfulClient = createToolClient(args);
50
+ // Get existing AI action, merge fields, and update
51
+ const existingAiAction = await contentfulClient.aiAction.get(params);
52
+ const updatedAiAction = await contentfulClient.aiAction.update(params, {
53
+ ...existingAiAction,
54
+ ...(args.name && { name: args.name }),
55
+ ...(args.description && { description: args.description }),
56
+ ...(args.instruction && { instruction: args.instruction }),
57
+ ...(args.configuration && { configuration: args.configuration }),
58
+ ...(args.testCases && { testCases: args.testCases }),
59
+ });
60
+ return createSuccessResponse('AI action updated successfully', {
61
+ updatedAiAction,
62
+ });
63
+ }
64
+ export const updateAiActionTool = withErrorHandling(tool, 'Error updating AI action');
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ export const DeleteAssetToolParams = BaseToolSchema.extend({
5
+ assetId: z.string().describe('The ID of the asset to delete'),
6
+ });
7
+ async function tool(args) {
8
+ const params = {
9
+ spaceId: args.spaceId,
10
+ environmentId: args.environmentId,
11
+ assetId: args.assetId,
12
+ };
13
+ const contentfulClient = createToolClient(args);
14
+ // First, get the asset to store info for return
15
+ const asset = await contentfulClient.asset.get(params);
16
+ // Delete the asset
17
+ await contentfulClient.asset.delete(params);
18
+ return createSuccessResponse('Asset deleted successfully', { asset });
19
+ }
20
+ export const deleteAssetTool = withErrorHandling(tool, 'Error deleting asset');
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ export const GetAssetToolParams = BaseToolSchema.extend({
5
+ assetId: z.string().describe('The ID of the asset to retrieve'),
6
+ });
7
+ async function tool(args) {
8
+ const params = {
9
+ spaceId: args.spaceId,
10
+ environmentId: args.environmentId,
11
+ assetId: args.assetId,
12
+ };
13
+ const contentfulClient = createToolClient(args);
14
+ // Get the asset
15
+ const asset = await contentfulClient.asset.get(params);
16
+ return createSuccessResponse('Asset retrieved successfully', { asset });
17
+ }
18
+ export const getAssetTool = withErrorHandling(tool, 'Error retrieving asset');
@@ -0,0 +1,68 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ import { summarizeData } from '../../utils/summarizer.js';
5
+ export const ListAssetsToolParams = BaseToolSchema.extend({
6
+ limit: z
7
+ .number()
8
+ .optional()
9
+ .describe('Maximum number of assets to return (max 3)'),
10
+ skip: z.number().optional().describe('Skip this many assets for pagination'),
11
+ select: z
12
+ .string()
13
+ .optional()
14
+ .describe('Comma-separated list of fields to return'),
15
+ include: z
16
+ .number()
17
+ .optional()
18
+ .describe('Include this many levels of linked entries'),
19
+ order: z.string().optional().describe('Order assets by this field'),
20
+ links_to_entry: z
21
+ .string()
22
+ .optional()
23
+ .describe('Find assets that link to the specified entry ID'),
24
+ });
25
+ async function tool(args) {
26
+ const params = {
27
+ spaceId: args.spaceId,
28
+ environmentId: args.environmentId,
29
+ };
30
+ const contentfulClient = createToolClient(args);
31
+ const assets = await contentfulClient.asset.getMany({
32
+ ...params,
33
+ query: {
34
+ limit: Math.min(args.limit || 3, 3),
35
+ skip: args.skip || 0,
36
+ ...(args.select && { select: args.select }),
37
+ ...(args.include && { include: args.include }),
38
+ ...(args.order && { order: args.order }),
39
+ ...(args.links_to_entry && { links_to_entry: args.links_to_entry }),
40
+ },
41
+ });
42
+ const summarizedAssets = assets.items.map((asset) => ({
43
+ id: asset.sys.id,
44
+ title: asset.fields.title?.['en-US'] || 'Untitled',
45
+ description: asset.fields.description?.['en-US'] || null,
46
+ fileName: asset.fields.file?.['en-US']?.fileName || null,
47
+ contentType: asset.fields.file?.['en-US']?.contentType || null,
48
+ url: asset.fields.file?.['en-US']?.url || null,
49
+ size: asset.fields.file?.['en-US']?.details?.size || null,
50
+ createdAt: asset.sys.createdAt,
51
+ updatedAt: asset.sys.updatedAt,
52
+ publishedVersion: asset.sys.publishedVersion,
53
+ }));
54
+ const summarized = summarizeData({
55
+ ...assets,
56
+ items: summarizedAssets,
57
+ }, {
58
+ maxItems: 3,
59
+ remainingMessage: 'To see more assets, please ask me to retrieve the next page using the skip parameter.',
60
+ });
61
+ return createSuccessResponse('Assets retrieved successfully', {
62
+ assets: summarized,
63
+ total: assets.total,
64
+ limit: assets.limit,
65
+ skip: assets.skip,
66
+ });
67
+ }
68
+ export const listAssetsTool = withErrorHandling(tool, 'Error listing assets');
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ import { createAssetVersionedLinks, createEntitiesCollection, waitForBulkActionCompletion, } from '../../utils/bulkOperations.js';
5
+ export const PublishAssetToolParams = BaseToolSchema.extend({
6
+ assetId: z
7
+ .union([z.string(), z.array(z.string()).max(100)])
8
+ .describe('The ID of the asset to publish (string) or an array of asset IDs (up to 100 assets)'),
9
+ });
10
+ async function tool(args) {
11
+ const baseParams = {
12
+ spaceId: args.spaceId,
13
+ environmentId: args.environmentId,
14
+ };
15
+ const contentfulClient = createToolClient(args);
16
+ // Normalize input to always be an array
17
+ const assetIds = Array.isArray(args.assetId) ? args.assetId : [args.assetId];
18
+ // For single asset, use individual publish
19
+ if (assetIds.length === 1) {
20
+ try {
21
+ const assetId = assetIds[0];
22
+ const params = {
23
+ ...baseParams,
24
+ assetId,
25
+ };
26
+ // Get the asset first
27
+ const asset = await contentfulClient.asset.get(params);
28
+ // Publish the asset
29
+ const publishedAsset = await contentfulClient.asset.publish(params, asset);
30
+ return createSuccessResponse('Asset published successfully', {
31
+ status: publishedAsset.sys.status,
32
+ assetId,
33
+ });
34
+ }
35
+ catch (error) {
36
+ return createSuccessResponse('Asset publish failed', {
37
+ status: error,
38
+ assetId: assetIds[0],
39
+ });
40
+ }
41
+ }
42
+ // For multiple assets, use bulk action API
43
+ // Get the current version of each asset
44
+ const entityVersions = await createAssetVersionedLinks(contentfulClient, baseParams, assetIds);
45
+ // Create the collection object
46
+ const entitiesCollection = createEntitiesCollection(entityVersions);
47
+ // Create the bulk action
48
+ const bulkAction = await contentfulClient.bulkAction.publish(baseParams, {
49
+ entities: entitiesCollection,
50
+ });
51
+ // Wait for the bulk action to complete
52
+ const action = await waitForBulkActionCompletion(contentfulClient, baseParams, bulkAction.sys.id);
53
+ return createSuccessResponse('Asset(s) published successfully', {
54
+ status: action.sys.status,
55
+ assetIds,
56
+ });
57
+ }
58
+ export const publishAssetTool = withErrorHandling(tool, 'Error publishing asset');
@@ -0,0 +1,16 @@
1
+ import { uploadAssetTool, UploadAssetToolParams } from './uploadAsset.js';
2
+ import { listAssetsTool, ListAssetsToolParams } from './listAssets.js';
3
+ import { getAssetTool, GetAssetToolParams } from './getAsset.js';
4
+ import { updateAssetTool, UpdateAssetToolParams } from './updateAsset.js';
5
+ import { deleteAssetTool, DeleteAssetToolParams } from './deleteAsset.js';
6
+ import { publishAssetTool, PublishAssetToolParams } from './publishAsset.js';
7
+ import { unpublishAssetTool, UnpublishAssetToolParams, } from './unpublishAsset.js';
8
+ export function registerAssetTools(server) {
9
+ server.tool('upload_asset', 'Upload a new asset', UploadAssetToolParams.shape, uploadAssetTool);
10
+ server.tool('list_assets', 'List assets in a space. Returns a maximum of 3 items per request. Use skip parameter to paginate through results.', ListAssetsToolParams.shape, listAssetsTool);
11
+ server.tool('get_asset', 'Retrieve an asset', GetAssetToolParams.shape, getAssetTool);
12
+ server.tool('update_asset', 'Update an asset', UpdateAssetToolParams.shape, updateAssetTool);
13
+ server.tool('delete_asset', 'Delete an asset', DeleteAssetToolParams.shape, deleteAssetTool);
14
+ server.tool('publish_asset', 'Publish an asset or multiple assets. Accepts either a single assetId (string) or an array of assetIds (up to 100 assets). For a single asset, it uses the standard publish operation. For multiple assets, it automatically uses bulk publishing.', PublishAssetToolParams.shape, publishAssetTool);
15
+ server.tool('unpublish_asset', 'Unpublish an asset or multiple assets. Accepts either a single assetId (string) or an array of assetIds (up to 100 assets). For a single asset, it uses the standard unpublish operation. For multiple assets, it automatically uses bulk unpublishing.', UnpublishAssetToolParams.shape, unpublishAssetTool);
16
+ }
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ import { createEntitiesCollection, waitForBulkActionCompletion, createAssetUnversionedLinks, } from '../../utils/bulkOperations.js';
5
+ export const UnpublishAssetToolParams = BaseToolSchema.extend({
6
+ assetId: z
7
+ .union([z.string(), z.array(z.string()).max(100)])
8
+ .describe('The ID of the asset to unpublish (string) or an array of asset IDs (up to 100 assets)'),
9
+ });
10
+ async function tool(args) {
11
+ const baseParams = {
12
+ spaceId: args.spaceId,
13
+ environmentId: args.environmentId,
14
+ };
15
+ const contentfulClient = createToolClient(args);
16
+ // Normalize input to always be an array
17
+ const assetIds = Array.isArray(args.assetId) ? args.assetId : [args.assetId];
18
+ // For single asset, use individual unpublish for simplicity
19
+ if (assetIds.length === 1) {
20
+ try {
21
+ const assetId = assetIds[0];
22
+ const params = {
23
+ ...baseParams,
24
+ assetId,
25
+ };
26
+ // Get the asset first
27
+ const asset = await contentfulClient.asset.get(params);
28
+ // Unpublish the asset
29
+ const unpublishedAsset = await contentfulClient.asset.unpublish(params, asset);
30
+ return createSuccessResponse('Asset unpublished successfully', {
31
+ status: unpublishedAsset.sys.status,
32
+ assetId,
33
+ });
34
+ }
35
+ catch (error) {
36
+ return createSuccessResponse('Asset unpublish failed', {
37
+ status: error,
38
+ assetId: assetIds[0],
39
+ });
40
+ }
41
+ }
42
+ // For multiple assets, use bulk action API
43
+ // Get the unversioned links for each asset (unpublish doesn't need version info)
44
+ const assetLinks = await createAssetUnversionedLinks(contentfulClient, baseParams, assetIds);
45
+ // Create the collection object
46
+ const entitiesCollection = createEntitiesCollection(assetLinks);
47
+ // Create the bulk action
48
+ const bulkAction = await contentfulClient.bulkAction.unpublish(baseParams, {
49
+ entities: entitiesCollection,
50
+ });
51
+ // Wait for the bulk action to complete
52
+ const action = await waitForBulkActionCompletion(contentfulClient, baseParams, bulkAction.sys.id);
53
+ return createSuccessResponse('Asset(s) unpublished successfully', {
54
+ status: action.sys.status,
55
+ assetIds,
56
+ });
57
+ }
58
+ export const unpublishAssetTool = withErrorHandling(tool, 'Error unpublishing asset');
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ export const UpdateAssetToolParams = BaseToolSchema.extend({
5
+ assetId: z.string().describe('The ID of the asset to update'),
6
+ fields: z
7
+ .record(z.any())
8
+ .describe('The field values to update. Keys should be field IDs and values should be the field content. Will be merged with existing fields.'),
9
+ metadata: z
10
+ .object({
11
+ tags: z.array(z.object({
12
+ sys: z.object({
13
+ type: z.literal('Link'),
14
+ linkType: z.literal('Tag'),
15
+ id: z.string(),
16
+ }),
17
+ })),
18
+ })
19
+ .optional(),
20
+ });
21
+ async function tool(args) {
22
+ const params = {
23
+ spaceId: args.spaceId,
24
+ environmentId: args.environmentId,
25
+ assetId: args.assetId,
26
+ };
27
+ const contentfulClient = createToolClient(args);
28
+ // Get existing asset, merge fields, and update
29
+ const existingAsset = await contentfulClient.asset.get(params);
30
+ const updatedAsset = await contentfulClient.asset.update(params, {
31
+ ...existingAsset,
32
+ fields: { ...existingAsset.fields, ...args.fields },
33
+ metadata: {
34
+ tags: [
35
+ ...(existingAsset.metadata?.tags || []),
36
+ ...(args.metadata?.tags || []),
37
+ ],
38
+ },
39
+ });
40
+ return createSuccessResponse('Asset updated successfully', { updatedAsset });
41
+ }
42
+ export const updateAssetTool = withErrorHandling(tool, 'Error updating asset');
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ const FileSchema = z.object({
5
+ fileName: z.string().describe('The name of the file'),
6
+ contentType: z.string().describe('The MIME type of the file'),
7
+ upload: z.string().optional().describe('The upload URL or file data'),
8
+ });
9
+ export const UploadAssetToolParams = BaseToolSchema.extend({
10
+ title: z.string().describe('The title of the asset'),
11
+ description: z.string().optional().describe('The description of the asset'),
12
+ file: FileSchema.describe('The file information for the asset'),
13
+ metadata: z
14
+ .object({
15
+ tags: z.array(z.object({
16
+ sys: z.object({
17
+ type: z.literal('Link'),
18
+ linkType: z.literal('Tag'),
19
+ id: z.string(),
20
+ }),
21
+ })),
22
+ })
23
+ .optional(),
24
+ });
25
+ async function tool(args) {
26
+ const params = {
27
+ spaceId: args.spaceId,
28
+ environmentId: args.environmentId,
29
+ };
30
+ const contentfulClient = createToolClient(args);
31
+ // Prepare asset properties following Contentful's structure
32
+ const assetProps = {
33
+ fields: {
34
+ title: { 'en-US': args.title },
35
+ description: args.description ? { 'en-US': args.description } : undefined,
36
+ file: { 'en-US': args.file },
37
+ },
38
+ metadata: args.metadata,
39
+ };
40
+ // Create the asset
41
+ const asset = await contentfulClient.asset.create(params, assetProps);
42
+ // Process the asset for all locales
43
+ const processedAsset = await contentfulClient.asset.processForAllLocales(params, {
44
+ sys: asset.sys,
45
+ fields: asset.fields,
46
+ }, {});
47
+ return createSuccessResponse('Asset uploaded successfully', {
48
+ asset: processedAsset,
49
+ });
50
+ }
51
+ export const uploadAssetTool = withErrorHandling(tool, 'Error uploading asset');
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { outdent } from 'outdent';
3
+ import { contextStore } from './store.js';
4
+ import { withErrorHandling } from '../../utils/response.js';
5
+ import { MCP_INSTRUCTIONS } from './instructions.js';
6
+ import { env } from '../../config/env.js';
7
+ export const GetInitialContextToolParams = z.object({});
8
+ export function hasInitialContext() {
9
+ return contextStore.hasInitialContext();
10
+ }
11
+ async function tool(_params) {
12
+ const config = {
13
+ space: env.data?.SPACE_ID,
14
+ environment: env.data?.ENVIRONMENT_ID,
15
+ };
16
+ const configInfo = `Current Contentful Configuration:
17
+ - Space ID: ${config.space}
18
+ - Environment ID: ${config.environment}`;
19
+ const todaysDate = new Date().toLocaleDateString('en-US');
20
+ const message = outdent `
21
+ ${MCP_INSTRUCTIONS}
22
+
23
+ This is the initial context for your Contentful instance:
24
+
25
+ <context>
26
+ ${configInfo}
27
+ </content>
28
+
29
+ <todaysDate>${todaysDate}</todaysDate>
30
+ `;
31
+ contextStore.setInitialContextLoaded();
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: message,
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ export const getInitialContextTool = withErrorHandling(tool, 'Error getting initial context');
@@ -0,0 +1,111 @@
1
+ export const MCP_INSTRUCTIONS = `You are a helpful assistant integrated with Contentful through the Model Context Protocol (MCP).
2
+
3
+ # Core Agent Principles
4
+
5
+ ## IMPORTANT FIRST STEP:
6
+ - Always call get_initial_context first to initialize your connection before using any other tools
7
+ - This is required for all operations and will give you essential information about the current Contentful environment
8
+
9
+ ## Key Principles:
10
+ - **Persistence**: Keep going until the user's query is completely resolved. Only end your turn when you are sure the problem is solved.
11
+ - **Tool Usage**: If you are not sure about content or schema structure, use your tools to gather relevant information. Do NOT guess or make up answers.
12
+ - **Planning**: Plan your approach before each tool call, and reflect on the outcomes of previous tool calls.
13
+ - **Resource Clarification**: ALWAYS ask the user which resource to work with if there are multiple resources available. Never assume or guess which resource to use.
14
+ - **Error Handling**: NEVER apologize for errors when making tool calls. Instead, immediately try a different approach or tool call. You may briefly inform the user what you're doing, but never say sorry.
15
+
16
+ # Content Handling
17
+
18
+ ## Content-Type-First Approach:
19
+ - **ALWAYS check the content type first** when users ask about finding or editing specific content types (e.g., "Where can I edit our pricing page?")
20
+ - Use get_content_types proactively to understand what content types exist before attempting queries
21
+ - This prevents failed queries and immediately reveals relevant content types (e.g., discovering a \`pricingPage\` type when asked about pricing)
22
+ - Match user requests to the appropriate content types in the entry
23
+ - If a user asks to create a field that doesn't match the content type (e.g., "writer" when the content type has "author"), suggest the correct type
24
+
25
+ ## entry Creation Limits:
26
+ - A user is only allowed to create/edit/mutate a maximum of 5 (five) entries at a time
27
+ - For multiple entry creation, use the 'async' parameter (set to true) for better performance
28
+ - Only use async=true when creating more than one entry in a single conversation
29
+
30
+ # Searching for Content
31
+
32
+ ## Content-Type-First Search Strategy:
33
+ - **Content-Type-first approach**: When users ask about specific content (e.g., "pricing page", "blog posts"), use get_content_type first to discover relevant content types
34
+ - This immediately reveals the correct content types and prevents wasted time on failed queries
35
+ - After understanding the content type, use search_entries to search for content based on the correct content types and field names
36
+ - If a query returns no results, retry 2-3 times with modified queries by adjusting filters, relaxing constraints, or trying alternative field names
37
+ - When retrying queries, consider using more general terms, removing specific filters, or checking for typos in field names
38
+
39
+ ## Handling Multi-Step Queries:
40
+ - For requests involving related entities (e.g., "Find blog posts by Magnus"), use a multi-step approach
41
+ - ALWAYS check the content type structure first to understand fields and references
42
+ - First, query for the referenced entity (e.g., author) to find its ID or confirm its existence
43
+ - If multiple entities match (e.g., several authors named "Magnus"), query them all and display them to the user
44
+ - Then use the found ID(s) to query for the primary content (e.g., blog posts referencing that author)
45
+ - For references in Contentful, remember to use the proper reference format
46
+ - Verify fields in the content type before constructing queries (single reference vs. array of references)
47
+
48
+ ## Entry Management:
49
+ - For entry creation, use create_entry with clear instructions
50
+ - Use entry_action for operations like publishing, unpublishing, deleting, or discarding entries
51
+ - Use update_entry for content modifications with AI assistance
52
+ - Use patch_entry for precise, direct modifications without AI generation (one operation at a time)
53
+ - Use transform_entry when preserving rich text formatting is crucial
54
+ - Use translate_entry specifically for language translation tasks
55
+ - Use transform_image for AI-powered image operations
56
+ - Always verify entry existence before attempting to modify it
57
+
58
+
59
+ # Error Handling and Debugging
60
+
61
+ ## Error Response Strategy:
62
+ - If you encounter an error, explain what went wrong clearly
63
+ - Suggest potential solutions or alternatives
64
+ - Make sure to check entry existence, field requirements, and permission issues
65
+ - Try different approaches immediately rather than stopping at the first error
66
+
67
+ ## Common Issues to Check:
68
+ - entry existence and permissions
69
+ - Required field validation
70
+ - Correct field types (array vs single reference)
71
+
72
+ # Response Format and Communication
73
+
74
+ ## General Guidelines:
75
+ - Keep your responses concise but thorough
76
+ - Format complex data for readability using markdown
77
+ - Focus on completing the requested tasks efficiently
78
+ - Provide context from entries when relevant
79
+ - When displaying entries, show the most important fields first
80
+
81
+ ## Before Using Tools:
82
+ Before running a tool:
83
+ 1. Think about what information you need to gather
84
+ 2. Determine the right tool and parameters to use
85
+ 3. Briefly communicate to the user what you're about to do in a conversational tone
86
+
87
+ ## Problem-Solving Strategy:
88
+ 1. **Understand the request**: Analyze what the user is asking for and identify necessary entry types and fields
89
+ 2. **Resource identification**: If multiple resources are available, ALWAYS ask which resource to work with
90
+ 3. **Plan your approach**: Determine which tools you'll need and in which order
91
+ 4. **Execute with tools**: Use appropriate tools to query, create, or update entries
92
+ 5. **Verify results**: Check if results match what the user requested and make adjustments if needed
93
+ 6. **Respond clearly**: Present results in a clear, concise format
94
+
95
+ # Best Practices
96
+
97
+ ## Content Management:
98
+ - When creating content, follow the content type structure exactly
99
+ - Always verify entry existence before attempting to modify it
100
+ - Remind users that entry operations can affect live content
101
+
102
+ ## Efficiency Tips:
103
+ - Suggest appropriate entry types based on user needs
104
+ - Recommend efficient ways to structure content
105
+ - Explain how Contentful features like content types, entries, and references work
106
+ - Help users understand the relationship between spaces, environments, and content types
107
+
108
+ ## Bulk Actions:
109
+ - If making multiple calls to the same tool, ALWAYS check and see whether that tool supports bulk operations first, and condense them into a single call if possible.
110
+
111
+ You have access to powerful tools that can help you work with Contentful effectively. Always start with get_initial_context, check the schema when needed, clarify resources when multiple exist, and take action to complete user requests fully.`;
@@ -0,0 +1,8 @@
1
+ import { contextStore } from './store.js';
2
+ export function enforceInitialContextMiddleware(toolName) {
3
+ if (toolName === 'get_initial_context')
4
+ return;
5
+ if (!contextStore.hasInitialContext()) {
6
+ throw new Error('Initial context has not been retrieved. Please call get_initial_context tool first to get the initial context.');
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ import { getInitialContextTool, GetInitialContextToolParams, } from './getInitialContextTool.js';
2
+ export function registerContextTools(server) {
3
+ server.tool('get_initial_context', 'IMPORTANT: This tool must be called before using any other tools. It will get initial context and usage instructions for this MCP server. ', GetInitialContextToolParams.shape, getInitialContextTool);
4
+ }
@@ -0,0 +1,15 @@
1
+ class ContextStore {
2
+ context = {
3
+ hasGlobalContext: false,
4
+ };
5
+ setInitialContextLoaded() {
6
+ this.context.hasGlobalContext = true;
7
+ }
8
+ hasInitialContext() {
9
+ return this.context.hasGlobalContext;
10
+ }
11
+ resetInitialContext() {
12
+ this.context.hasGlobalContext = false;
13
+ }
14
+ }
15
+ export const contextStore = new ContextStore();
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ export const CreateEntryToolParams = BaseToolSchema.extend({
5
+ contentTypeId: z
6
+ .string()
7
+ .describe('The ID of the content type to create an entry for'),
8
+ fields: z
9
+ .record(z.any())
10
+ .describe('The field values for the new entry. Keys should be field IDs and values should be the field content.'),
11
+ metadata: z
12
+ .object({
13
+ tags: z.array(z.object({
14
+ sys: z.object({
15
+ type: z.literal('Link'),
16
+ linkType: z.literal('Tag'),
17
+ id: z.string(),
18
+ }),
19
+ })),
20
+ })
21
+ .optional(),
22
+ });
23
+ async function tool(args) {
24
+ const params = {
25
+ spaceId: args.spaceId,
26
+ environmentId: args.environmentId,
27
+ };
28
+ const contentfulClient = createToolClient(args);
29
+ const newEntry = await contentfulClient.entry.create({
30
+ ...params,
31
+ contentTypeId: args.contentTypeId,
32
+ }, {
33
+ fields: args.fields,
34
+ metadata: args.metadata,
35
+ });
36
+ //return info about the entry that was created
37
+ return createSuccessResponse('Entry created successfully', { newEntry });
38
+ }
39
+ export const createEntryTool = withErrorHandling(tool, 'Error creating entry');
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResponse, withErrorHandling, } from '../../utils/response.js';
3
+ import { BaseToolSchema, createToolClient } from '../../utils/tools.js';
4
+ export const DeleteEntryToolParams = BaseToolSchema.extend({
5
+ entryId: z.string().describe('The ID of the entry to delete'),
6
+ });
7
+ async function tool(args) {
8
+ const params = {
9
+ spaceId: args.spaceId,
10
+ environmentId: args.environmentId,
11
+ entryId: args.entryId,
12
+ };
13
+ const contentfulClient = createToolClient(args);
14
+ // First, get the entry to check its status
15
+ const entry = await contentfulClient.entry.get(params);
16
+ // Delete the entry
17
+ await contentfulClient.entry.delete(params);
18
+ //return info about the entry that was deleted
19
+ return createSuccessResponse('Entry deleted successfully', { entry });
20
+ }
21
+ export const deleteEntryTool = withErrorHandling(tool, 'Error deleting entry');