@contentful/mcp-server 1.0.2 → 1.0.4
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 +9 -2
- package/build/config/contentful.js +19 -0
- package/build/config/env.js +25 -0
- package/build/config/version.js +2 -0
- package/build/index.js +38 -0
- package/build/prompts/register.js +1 -0
- package/build/resources/register.js +1 -0
- package/build/tools/ai-actions/createAiAction.js +55 -0
- package/build/tools/ai-actions/deleteAiAction.js +20 -0
- package/build/tools/ai-actions/getAiAction.js +20 -0
- package/build/tools/ai-actions/getAiActionInvocation.js +21 -0
- package/build/tools/ai-actions/invokeAiAction.js +74 -0
- package/build/tools/ai-actions/listAiActions.js +65 -0
- package/build/tools/ai-actions/publishAiAction.js +34 -0
- package/build/tools/ai-actions/register.js +20 -0
- package/build/tools/ai-actions/unpublishAiAction.js +28 -0
- package/build/tools/ai-actions/updateAiAction.js +64 -0
- package/build/tools/assets/deleteAsset.js +20 -0
- package/build/tools/assets/getAsset.js +18 -0
- package/build/tools/assets/listAssets.js +68 -0
- package/build/tools/assets/publishAsset.js +58 -0
- package/build/tools/assets/register.js +16 -0
- package/build/tools/assets/unpublishAsset.js +58 -0
- package/build/tools/assets/updateAsset.js +42 -0
- package/build/tools/assets/uploadAsset.js +51 -0
- package/build/tools/context/getInitialContextTool.js +41 -0
- package/build/tools/context/instructions.js +111 -0
- package/build/tools/context/middleware.js +8 -0
- package/build/tools/context/register.js +4 -0
- package/build/tools/context/store.js +15 -0
- package/build/tools/entries/createEntry.js +39 -0
- package/build/tools/entries/deleteEntry.js +21 -0
- package/build/tools/entries/getEntry.js +18 -0
- package/build/tools/entries/publishEntry.js +58 -0
- package/build/tools/entries/register.js +16 -0
- package/build/tools/entries/searchEntries.js +50 -0
- package/build/tools/entries/unpublishEntry.js +58 -0
- package/build/tools/entries/updateEntry.js +50 -0
- package/build/tools/environments/createEnvironment.js +21 -0
- package/build/tools/environments/deleteEnvironment.js +19 -0
- package/build/tools/environments/listEnvironments.js +62 -0
- package/build/tools/environments/register.js +8 -0
- package/build/tools/locales/createLocale.js +49 -0
- package/build/tools/locales/deleteLocale.js +21 -0
- package/build/tools/locales/getLocale.js +18 -0
- package/build/tools/locales/listLocales.js +61 -0
- package/build/tools/locales/register.js +12 -0
- package/build/tools/locales/updateLocale.js +47 -0
- package/build/tools/register.js +20 -0
- package/build/tools/spaces/getSpace.js +20 -0
- package/build/tools/spaces/listSpaces.js +53 -0
- package/build/tools/spaces/register.js +6 -0
- package/build/tools/tags/createTag.js +24 -0
- package/build/tools/tags/listTags.js +50 -0
- package/build/tools/tags/register.js +6 -0
- package/build/tools/types/createContentType.js +33 -0
- package/build/tools/types/deleteContentType.js +20 -0
- package/build/tools/types/getContentType.js +24 -0
- package/build/tools/types/listContentTypes.js +59 -0
- package/build/tools/types/publishContentType.js +22 -0
- package/build/tools/types/register.js +16 -0
- package/build/tools/types/unpublishContentType.js +20 -0
- package/build/tools/types/updateContentType.js +78 -0
- package/build/types/fieldSchema.js +29 -0
- package/build/utils/ai-actions.js +46 -0
- package/build/utils/bulkOperations.js +94 -0
- package/build/utils/formatters.js +39 -0
- package/build/utils/getVersion.js +11 -0
- package/build/utils/response.js +37 -0
- package/build/utils/summarizer.js +39 -0
- package/build/utils/tools.js +20 -0
- package/package.json +1 -1
|
@@ -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');
|
|
@@ -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 GetEntryToolParams = BaseToolSchema.extend({
|
|
5
|
+
entryId: z.string().describe('The ID of the entry to retrieve'),
|
|
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
|
+
// Get the entry
|
|
15
|
+
const entry = await contentfulClient.entry.get(params);
|
|
16
|
+
return createSuccessResponse('Entry retrieved successfully', { entry });
|
|
17
|
+
}
|
|
18
|
+
export const getEntryTool = withErrorHandling(tool, 'Error retrieving entry');
|
|
@@ -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 { createEntryVersionedLinks, createEntitiesCollection, waitForBulkActionCompletion, } from '../../utils/bulkOperations.js';
|
|
5
|
+
export const PublishEntryToolParams = BaseToolSchema.extend({
|
|
6
|
+
entryId: z
|
|
7
|
+
.union([z.string(), z.array(z.string()).max(100)])
|
|
8
|
+
.describe('The ID of the entry to publish (string) or an array of entry IDs (up to 100 entries)'),
|
|
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 entryIds = Array.isArray(args.entryId) ? args.entryId : [args.entryId];
|
|
18
|
+
// For single entry, use individual publish for simplicity
|
|
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
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// For multiple entries, use bulk action API
|
|
43
|
+
// Get the current version of each entry
|
|
44
|
+
const entityVersions = await createEntryVersionedLinks(contentfulClient, baseParams, entryIds);
|
|
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('Entry(s) published successfully', {
|
|
54
|
+
status: action.sys.status,
|
|
55
|
+
entryIds,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export const publishEntryTool = withErrorHandling(tool, 'Error publishing entry');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { searchEntriesTool, SearchEntriesToolParams } from './searchEntries.js';
|
|
2
|
+
import { createEntryTool, CreateEntryToolParams } from './createEntry.js';
|
|
3
|
+
import { deleteEntryTool, DeleteEntryToolParams } from './deleteEntry.js';
|
|
4
|
+
import { updateEntryTool, UpdateEntryToolParams } from './updateEntry.js';
|
|
5
|
+
import { getEntryTool, GetEntryToolParams } from './getEntry.js';
|
|
6
|
+
import { publishEntryTool, PublishEntryToolParams } from './publishEntry.js';
|
|
7
|
+
import { unpublishEntryTool, UnpublishEntryToolParams, } from './unpublishEntry.js';
|
|
8
|
+
export function registerEntriesTools(server) {
|
|
9
|
+
server.tool('search_entries', 'Search for specific entries in your Contentful space', SearchEntriesToolParams.shape, searchEntriesTool);
|
|
10
|
+
server.tool('create_entry', "Create a new entry in Contentful. Before executing this function, you need to know the contentTypeId (not the content type NAME) and the fields of that contentType. You can get the fields definition by using the GET_CONTENT_TYPE tool. IMPORTANT: All field values MUST include a locale key (e.g., 'en-US') for each value, like: { title: { 'en-US': 'My Title' } }. Every field in Contentful requires a locale even for single-language content. TAGS: To add tags to an entry, include a metadata object with a tags array. Each tag should be an object with sys.type='Link', sys.linkType='Tag', and sys.id='tagId'. Example: { metadata: { tags: [{ sys: { type: 'Link', linkType: 'Tag', id: 'myTagId' } }] } }.", CreateEntryToolParams.shape, createEntryTool);
|
|
11
|
+
server.tool('get_entry', 'Retrieve an existing entry', GetEntryToolParams.shape, getEntryTool);
|
|
12
|
+
server.tool('update_entry', "Update an existing entry. The handler will merge your field updates with the existing entry fields, so you only need to provide the fields you want to change. However, for multiple-locale fields, all existing locales must be included in the update. IMPORTANT: All field values MUST include a locale key (e.g., 'en-US') for each value, like: { title: { 'en-US': 'My Updated Title' } }. Every field in Contentful requires a locale even for single-language content. When updating entries with multiple locales, always include all existing locales in the update to prevent overwriting with empty values. RICH TEXT FIELDS: When updating rich text fields, ALL text nodes MUST include a 'marks' property (can be empty array [] for no formatting). Text nodes with formatting need appropriate marks: { nodeType: 'text', value: 'Bold text', marks: [{ type: 'bold' }], data: {} }.", UpdateEntryToolParams.shape, updateEntryTool);
|
|
13
|
+
server.tool('delete_entry', 'Delete a specific content entry from your Contentful space', DeleteEntryToolParams.shape, deleteEntryTool);
|
|
14
|
+
server.tool('publish_entry', 'Publish an entry or multiple entries. Accepts either a single entryId (string) or an array of entryIds (up to 100 entries). For a single entry, it uses the standard publish operation. For multiple entries, it automatically uses bulk publishing.', PublishEntryToolParams.shape, publishEntryTool);
|
|
15
|
+
server.tool('unpublish_entry', 'Unpublish an entry or multiple entries. Accepts either a single entryId (string) or an array of entryIds (up to 100 entries). For a single entry, it uses the standard unpublish operation. For multiple entries, it automatically uses bulk unpublishing.', UnpublishEntryToolParams.shape, unpublishEntryTool);
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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 SearchEntriesToolParams = BaseToolSchema.extend({
|
|
6
|
+
query: z.object({
|
|
7
|
+
content_type: z.string().optional().describe('Filter by content type'),
|
|
8
|
+
include: z
|
|
9
|
+
.number()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe('Include this many levels of linked entries'),
|
|
12
|
+
select: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Comma-separated list of fields to return'),
|
|
16
|
+
links_to_entry: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Find entries that link to the specified entry ID'),
|
|
20
|
+
limit: z
|
|
21
|
+
.number()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Maximum number of entries to return'),
|
|
24
|
+
skip: z.number().optional().describe('Skip this many entries'),
|
|
25
|
+
order: z.string().optional().describe('Order entries by this field'),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
async function tool(args) {
|
|
29
|
+
const params = {
|
|
30
|
+
spaceId: args.spaceId,
|
|
31
|
+
environmentId: args.environmentId,
|
|
32
|
+
};
|
|
33
|
+
const contentfulClient = createToolClient(args);
|
|
34
|
+
const entries = await contentfulClient.entry.getMany({
|
|
35
|
+
...params,
|
|
36
|
+
query: {
|
|
37
|
+
...args.query,
|
|
38
|
+
limit: Math.min(args.query.limit || 3, 3),
|
|
39
|
+
skip: args.query.skip || 0,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const summarized = summarizeData(entries, {
|
|
43
|
+
maxItems: 3,
|
|
44
|
+
remainingMessage: 'To see more entries, please ask me to retrieve the next page.',
|
|
45
|
+
});
|
|
46
|
+
return createSuccessResponse('Entries retrieved successfully', {
|
|
47
|
+
entries: summarized,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export const searchEntriesTool = withErrorHandling(tool, 'Error deleting dataset');
|