contentstack-cursor-mcp
Version:
Contentstack Content Management API MCP
802 lines (701 loc) • 25.3 kB
Markdown
# Contentstack Content Management API MCP
This Multi-Command Plugin (MCP) provides a set of tools to interact with Contentstack's Content Management API directly from Cursor IDE.
## Installation
### NPM Package
```bash
npm install contentstack-cursor-mcp
```
### Manual Installation
1. Clone the repository:
```bash
git clone https://github.com/SamueleReply/contentstack-cursor-mcp.git
cd contentstack-cursor-mcp
```
2. Install dependencies:
```bash
npm install
```
3. Create a `.env` file in the root directory with your Contentstack credentials:
```
CONTENTSTACK_API_KEY=your_api_key
CONTENTSTACK_MANAGEMENT_TOKEN=your_management_token
CONTENTSTACK_DELIVERY_TOKEN=your_delivery_token
CONTENTSTACK_REGION=NA # Optional, defaults to NA
```
## MCP Server Configuration
To use this package as an MCP server in Cursor, add the following configuration to your `.cursor/mcp.json` file:
```json
{
"mcpServers": {
"contentstack": {
"command": "npx",
"args": [
"contentstack-cursor-mcp@latest"
],
"env": {
"CONTENTSTACK_API_KEY": "your_api_key",
"CONTENTSTACK_MANAGEMENT_TOKEN": "your_management_token",
"CONTENTSTACK_REGION": "NA",
"CONTENTSTACK_DELIVERY_TOKEN": "your_delivery_token"
}
}
}
}
```
Replace the environment variables with your actual Contentstack credentials.
### Available MCP Tools
When configured as an MCP server, the following tools are available in Cursor:
- `contentstack_get_content_types` - Get all content types
- `contentstack_get_content_type` - Get a specific content type
- `contentstack_create_content_type` - Create a new content type with custom schema
- `contentstack_update_content_type` - Update an existing content type schema
- `contentstack_field_types_reference` - Get comprehensive reference documentation for all available Contentstack field types with examples
- `mcp_list_tools` - Get a list of all available MCP tools with their schemas
- `contentstack_get_entries` - Get entries for a content type
- `contentstack_get_entry` - Get a specific entry (supports environment and locale)
- `contentstack_create_entry` - Create a new entry (supports environment and locale)
- `contentstack_update_entry` - Update an entry (supports environment and locale)
- `contentstack_delete_entry` - Delete an entry (supports environment and locale)
- `contentstack_get_assets` - Get assets
- `contentstack_get_environments` - Get all environments
- `contentstack_publish_entry` - Publish an entry (supports environment and locale)
- `contentstack_unpublish_entry` - Unpublish an entry (supports environment and locale)
- `contentstack_get_languages` - Get all languages (locales) available in the stack
- `contentstack_localize_entry` - Localize an entry to a specific locale
## Region Support
The MCP supports multiple Contentstack regions. By default, it uses the North America (NA) region. You can specify a different region for each API call.
Available regions:
- `NA` - North America (default)
- `EU` - Europe
- `AZURE_NA` - Azure North America
- `AZURE_EU` - Azure Europe
- `GCP_NA` - GCP North America
- `GCP_EU` - GCP Europe
## Available Tools
### Content Types
- `getContentTypes(config)` - Get all content types
- `getContentType(uid, config)` - Get a specific content type
- `createContentType(data, config)` - Create a new content type
- `updateContentType(uid, data, config)` - Update an existing content type
### Entries
- `getEntries(contentTypeUid, query, config)` - Get all entries of a content type
- `getEntry(contentTypeUid, entryUid, options, config)` - Get a specific entry
- `createEntry(contentTypeUid, data, options, config)` - Create a new entry
- `updateEntry(contentTypeUid, entryUid, data, options, config)` - Update an existing entry
- `deleteEntry(contentTypeUid, entryUid, options, config)` - Delete an entry
### Assets
- `getAssets(query, config)` - Get all assets
- `getAsset(assetUid, config)` - Get a specific asset
- `uploadAsset(data, config)` - Upload a new asset
### Environments
- `getEnvironments(config)` - Get all environments
- `getEnvironment(uid, config)` - Get a specific environment
### Publishing
- `publishEntry(data, options, config)` - Publish an entry
- `unpublishEntry(data, options, config)` - Unpublish an entry
### Languages and Localization
- `getLanguages(config)` - Get all languages (locales) available in the stack
- `localizeEntry(contentTypeUid, entryUid, data, options, config)` - Localize an entry to a specific locale
## Environment and Locale Support
Entry operations (`getEntry`, `createEntry`, `updateEntry`, `deleteEntry`) and publishing operations (`publishEntry`, `unpublishEntry`) now support environment and locale parameters through an `options` object:
```javascript
// Get an entry with specific environment and locale
const entry = await cs.getEntry('content_type_uid', 'entry_uid', {
environment: 'development',
locale: 'en-us'
});
// Create an entry with environment and locale
const newEntry = await cs.createEntry('content_type_uid', entryData, {
environment: 'development',
locale: 'en-us'
});
// Update an entry with environment and locale
const updatedEntry = await cs.updateEntry('content_type_uid', 'entry_uid', updateData, {
environment: 'development',
locale: 'en-us'
});
// Delete an entry with environment and locale
await cs.deleteEntry('content_type_uid', 'entry_uid', {
environment: 'development',
locale: 'en-us'
});
// Publish an entry with environment and locale
const publishResult = await cs.publishEntry({
entry: {
uid: 'entry_uid',
content_type: 'content_type_uid',
version: 1
},
environments: ['development']
}, {
environment: 'development',
locale: 'en-us'
});
// Unpublish an entry with environment and locale
const unpublishResult = await cs.unpublishEntry({
entry: {
uid: 'entry_uid',
content_type: 'content_type_uid'
},
environments: ['development']
}, {
environment: 'development',
locale: 'en-us'
});
```
The `options` parameter supports:
- `environment`: Target environment name
- `locale`: Target locale code (e.g., 'en-us', 'fr-fr')
- Any other query parameters supported by the Contentstack API
## Field Types Reference Tool
The `contentstack_field_types_reference` tool provides comprehensive documentation and examples for all available Contentstack field types. This is especially useful when creating or updating content types.
### Usage
**Get all field types:**
```javascript
// Via MCP tool
const allFieldTypes = await mcpClient.callTool('contentstack_field_types_reference', {});
// Response includes: overview, text, json, number, boolean, isodate, file, link, reference, group, blocks, global_field, extension
```
**Get specific field type:**
```javascript
// Via MCP tool
const textFieldInfo = await mcpClient.callTool('contentstack_field_types_reference', {
fieldType: 'text'
});
// Returns detailed information about text fields including all variants:
// - Single Line Textbox
// - Multi Line Textbox
// - Rich Text Editor (HTML)
// - Markdown
// - Select Dropdown
```
### Content Type Reuse Strategy ⚡
**IMPORTANT:** Before creating new content types or fields, always check what already exists!
#### Best Practices for Reusing Content Types
1. **Check First**: Use `contentstack_get_content_types` to see existing content types
2. **Use References**: Connect content types with reference fields instead of duplicating structures
3. **Global Fields**: Reuse common field groups (SEO, social sharing) across multiple content types
4. **Avoid Duplication**: If you need "author" info, check if an "author" content type exists - reference it instead of creating duplicate fields
#### When to Use References vs Groups
| Use Reference Field | Use Group Field |
|---------------------|-----------------|
| Entity needs independent management (authors, categories) | Data is tightly coupled to parent (address in contact form) |
| Needs to be shared across multiple entries | Only used within this specific content type |
| Has its own workflow and permissions | Simple nested data structure |
| Example: Blog → Author (reference) | Example: Person → Address (group) |
### Available Field Types
| Field Type | Description | Use Cases | Reuse Notes |
|------------|-------------|-----------|-------------|
| `text` | Text content with multiple variants | Titles, descriptions, content, select dropdowns | - |
| `number` | Numeric values | Prices, quantities, ratings | - |
| `boolean` | True/false toggle | Feature flags, published status | - |
| `isodate` | Date and time | Publication dates, event dates | - |
| `file` | Media assets | Images, videos, documents | - |
| `link` | URL with title | External links, CTAs | - |
| `reference` | Entry references | Related content, categories | **Use to reuse existing content types** |
| `json` | JSON Rich Text Editor | Structured rich text content | - |
| `group` | Nested field groups | Address details, coordinates | Use only for tightly coupled data |
| `blocks` | Modular content blocks | Page sections, flexible layouts | - |
| `global_field` | Reusable field groups | SEO metadata, social sharing | **Check if global fields exist first** |
| `extension` | Custom field extensions | Custom widgets, integrations | - |
### Key Field Properties
All fields share these common properties:
- `display_name` - Human-readable label (NOT "title")
- `uid` - Unique identifier in snake_case
- `data_type` - Field type (NOT "type")
- `mandatory` - Boolean for required fields (NOT "required")
- `unique` - Boolean for unique values
- `multiple` - Boolean for multiple values
- `non_localizable` - Boolean for shared values across locales
- `field_metadata` - Field-specific configuration
### Example: Creating a Content Type with Various Field Types
```javascript
const complexContentType = await cs.createContentType({
content_type: {
title: "Blog Post",
uid: "blog_post",
schema: [
// Text - Single Line
{
data_type: "text",
display_name: "Title",
uid: "title",
mandatory: true,
unique: true
},
// Text - Multi Line
{
data_type: "text",
display_name: "Excerpt",
uid: "excerpt",
field_metadata: {
multiline: true
}
},
// JSON RTE
{
data_type: "json",
display_name: "Content",
uid: "content",
field_metadata: {
allow_json_rte: true,
rich_text_type: "advanced"
},
reference_to: ["sys_assets"]
},
// Select Dropdown
{
data_type: "text",
display_name: "Status",
uid: "status",
display_type: "dropdown",
enum: {
advanced: false,
choices: [
{ value: "draft" },
{ value: "published" }
]
}
},
// Number
{
data_type: "number",
display_name: "Read Time (minutes)",
uid: "read_time"
},
// Boolean
{
data_type: "boolean",
display_name: "Featured",
uid: "featured",
field_metadata: {
default_value: false
}
},
// Date
{
data_type: "isodate",
display_name: "Publish Date",
uid: "publish_date"
},
// File/Asset
{
data_type: "file",
display_name: "Featured Image",
uid: "featured_image"
},
// Reference
{
data_type: "reference",
display_name: "Related Posts",
uid: "related_posts",
reference_to: ["blog_post"],
field_metadata: {
ref_multiple: true
},
multiple: true
},
// Group
{
data_type: "group",
display_name: "Author Info",
uid: "author_info",
schema: [
{
data_type: "text",
display_name: "Author Name",
uid: "author_name"
},
{
data_type: "text",
display_name: "Bio",
uid: "bio",
field_metadata: {
multiline: true
}
}
]
}
]
}
});
```
## Content Type Management
### Creating Content Types
You can create content types with various field types including text, number, select fields, JSON RTE, custom asset fields, and taxonomy fields.
#### Basic Content Type Example
```javascript
const newContentType = await cs.createContentType({
content_type: {
title: "Blog Post",
uid: "blog_post",
description: "Blog posts for the website",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true,
field_metadata: {
_default: true
}
},
{
display_name: "Content",
uid: "content",
data_type: "text",
field_metadata: {
multiline: true
}
},
{
display_name: "Publish Date",
uid: "publish_date",
data_type: "isodate",
mandatory: true
}
]
}
});
```
#### Content Type with Select Field (Simple Values)
```javascript
const contentTypeWithSelect = await cs.createContentType({
content_type: {
title: "Product",
uid: "product",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
display_name: "Priority",
uid: "priority",
data_type: "text",
field_metadata: {
description: "Product priority level"
},
enum: {
advanced: false,
choices: [
{ value: "1" },
{ value: "2" },
{ value: "3" }
]
}
}
]
}
});
```
#### Content Type with Select Field (Key-Value Pairs)
```javascript
const contentTypeWithAdvancedSelect = await cs.createContentType({
content_type: {
title: "Location",
uid: "location",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
display_name: "Region",
uid: "region",
data_type: "text",
enum: {
advanced: true,
choices: [
{ key: "New York", value: "NY" },
{ key: "India", value: "IN" },
{ key: "Australia", value: "AUS" }
]
}
}
]
}
});
```
#### Content Type with JSON RTE (Rich Text Editor)
```javascript
const contentTypeWithRTE = await cs.createContentType({
content_type: {
title: "Article",
uid: "article",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
data_type: "json",
display_name: "JSON RTE Content",
uid: "json_rte_content",
field_metadata: {
allow_json_rte: true,
rich_text_type: "advanced",
description: "Rich text content with embedded entries",
default_value: ""
},
reference_to: [
"blog_post",
"product"
],
non_localizable: false,
multiple: false,
mandatory: false,
unique: false
}
]
}
});
```
#### Content Type with Custom Asset Field
```javascript
const contentTypeWithAsset = await cs.createContentType({
content_type: {
title: "Media Gallery",
uid: "media_gallery",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
display_name: "Gallery Images",
uid: "gallery_images",
data_type: "file",
multiple: true,
mandatory: false
}
]
}
});
```
#### Content Type with Taxonomy Fields
```javascript
const contentTypeWithTaxonomy = await cs.createContentType({
content_type: {
title: "Categorized Content",
uid: "categorized_content",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
uid: "taxonomies",
taxonomies: [
{
taxonomy_uid: "taxonomy_1",
max_terms: 5,
mandatory: true,
non_localizable: false
},
{
taxonomy_uid: "taxonomy_2",
max_terms: 10,
mandatory: false,
non_localizable: false
}
],
multiple: true
}
]
}
});
```
#### Content Type with Field Visibility Rules
```javascript
const contentTypeWithRules = await cs.createContentType({
content_type: {
title: "Conditional Form",
uid: "conditional_form",
schema: [
{
display_name: "Title",
uid: "title",
data_type: "text",
mandatory: true,
unique: true
},
{
display_name: "Show Details",
uid: "show_details",
data_type: "boolean",
mandatory: false
},
{
display_name: "Details",
uid: "details",
data_type: "text",
mandatory: false
}
],
field_rules: [
{
conditions: [
{
operand_field: "show_details",
operator: "equals",
value: true
}
],
match_type: "all",
actions: [
{
action: "show",
target_field: "details"
}
]
}
]
}
});
```
### Updating Content Types
To update a content type, you must provide the complete schema including all existing fields plus any new fields:
```javascript
// First, get the existing content type
const existingContentType = await cs.getContentType('blog_post');
// Modify the schema (add a new field, for example)
existingContentType.content_type.schema.push({
display_name: "Author",
uid: "author",
data_type: "text",
mandatory: false
});
// Update the content type
const updatedContentType = await cs.updateContentType('blog_post', {
content_type: existingContentType.content_type
});
```
#### Field Visibility Rule Operators by Data Type
When creating field visibility rules, use these operators based on the operand field's data type:
- **Text**: `matches`, `does_not_match`, `starts_with`, `ends_with`, `contains`
- **Number**: `equals`, `not_equals`, `less_than`, `greater_than`, `less_than_or_equals`, `greater_than_or_equals`
- **Date**: `equals`, `not_equals`, `before_date`, `after_date` (use ISO format)
- **Boolean**: `is`, `is_not`
- **Select**: `is`, `is_not`
- **Reference**: `is`, `is_not`
## Usage Example
### As a Node.js Library
```javascript
const contentstack = require('contentstack-cursor-mcp');
// Initialize with default configuration from .env
const cs = contentstack.initialize();
// Or initialize with custom configuration
const csCustom = contentstack.initialize({
region: 'EU',
apiKey: 'your_api_key',
managementToken: 'your_management_token',
deliveryToken: 'your_delivery_token'
});
// Get all content types
async function example() {
try {
const contentTypes = await cs.getContentTypes();
console.log(contentTypes);
} catch (error) {
console.error(error);
}
}
// Get entries with query parameters
async function getEntriesExample() {
try {
const entries = await cs.getEntries('content_type_uid', {
limit: 10,
skip: 0,
environment: 'production'
});
console.log(entries);
} catch (error) {
console.error(error);
}
}
// Get entry with environment and locale
async function getEntryWithOptions() {
try {
const entry = await cs.getEntry('content_type_uid', 'entry_uid', {
environment: 'development',
locale: 'en-us',
include_schema: true
});
console.log(entry);
} catch (error) {
console.error(error);
}
}
// Get all available languages/locales
async function getLanguagesExample() {
try {
const languages = await cs.getLanguages();
console.log('Available languages:', languages);
} catch (error) {
console.error(error);
}
}
// Localize an entry to a specific locale
async function localizeEntryExample() {
try {
const localizedEntry = await cs.localizeEntry('content_type_uid', 'entry_uid', {
entry: {
title: 'Titre localisé',
description: 'Description en français'
}
}, {
locale: 'fr-fr'
});
console.log('Localized entry:', localizedEntry);
} catch (error) {
console.error(error);
}
}
```
### As an MCP Server in Cursor
Once configured in your `.cursor/mcp.json`, you can use the Contentstack tools directly in Cursor by asking questions like:
- "Get all content types from Contentstack"
- "Show me entries for the 'blog_post' content type"
- "Get the entry with UID 'xyz123' from content type 'product' in the development environment"
- "Create a new blog post entry with title 'My New Post'"
- "Create a new content type called 'Product' with fields for title, description, price, and category"
- "Update the 'blog_post' content type to add an 'author' field"
- "Add field visibility rules to the 'contact_form' content type"
- "Create a content type with a select field for product categories"
- "Get all available languages in the stack"
- "Localize the entry 'xyz123' from content type 'blog_post' to French locale"
## Configuration
Each API call accepts an optional configuration object with the following properties:
- `region`: Contentstack region (NA, EU, AZURE_NA, AZURE_EU, GCP_NA, GCP_EU)
- `apiKey`: Contentstack API Key
- `managementToken`: Contentstack Management Token
- `deliveryToken`: Contentstack Delivery Token
## Error Handling
All API calls are wrapped in try-catch blocks and will throw errors with meaningful messages if something goes wrong. The error message will include the specific error message from the Contentstack API if available.
## Testing
Run the test suite to verify your configuration:
```bash
npm test
```
This will test various API endpoints and verify that your credentials and configuration are working correctly.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the MIT License - see the LICENSE file for details.