#
tokens: 44399/50000 11/64 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/ivo-toby/contentful-mcp?page={x} to view the full context.

# Directory Structure

```
├── .github
│   └── workflows
│       ├── pr-check.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .releaserc
├── bin
│   └── mcp-server.js
├── build.js
├── CLAUDE.md
├── codecompanion-workspace.json
├── Dockerfile
├── eslint.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── inspect-watch.js
│   └── inspect.js
├── smithery.yaml
├── src
│   ├── config
│   │   ├── ai-actions-client.ts
│   │   └── client.ts
│   ├── handlers
│   │   ├── ai-action-handlers.ts
│   │   ├── asset-handlers.ts
│   │   ├── bulk-action-handlers.ts
│   │   ├── comment-handlers.ts
│   │   ├── content-type-handlers.ts
│   │   ├── entry-handlers.ts
│   │   └── space-handlers.ts
│   ├── index.ts
│   ├── prompts
│   │   ├── ai-actions-invoke.ts
│   │   ├── ai-actions-overview.ts
│   │   ├── contentful-prompts.ts
│   │   ├── generateVariableTypeContent.ts
│   │   ├── handlePrompt.ts
│   │   ├── handlers.ts
│   │   └── promptHandlers
│   │       ├── aiActions.ts
│   │       └── contentful.ts
│   ├── transports
│   │   ├── sse.ts
│   │   └── streamable-http.ts
│   ├── types
│   │   ├── ai-actions.ts
│   │   └── tools.ts
│   └── utils
│       ├── ai-action-tool-generator.ts
│       ├── summarizer.ts
│       ├── to-camel-case.ts
│       └── validation.ts
├── test
│   ├── integration
│   │   ├── ai-action-handler.test.ts
│   │   ├── ai-actions-client.test.ts
│   │   ├── asset-handler.test.ts
│   │   ├── bulk-action-handler.test.ts
│   │   ├── client.test.ts
│   │   ├── comment-handler.test.ts
│   │   ├── content-type-handler.test.ts
│   │   ├── entry-handler.test.ts
│   │   ├── space-handler.test.ts
│   │   └── streamable-http.test.ts
│   ├── msw-setup.ts
│   ├── setup.ts
│   └── unit
│       ├── ai-action-header.test.ts
│       ├── ai-action-tool-generator.test.ts
│       ├── ai-action-tools.test.ts
│       ├── ai-actions.test.ts
│       ├── content-type-handler-merge.test.ts
│       ├── entry-handler-merge.test.ts
│       └── tools.test.ts
├── tsconfig.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/test/integration/ai-action-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest"
import { aiActionHandlers } from "../../src/handlers/ai-action-handlers"
import { aiActionsClient } from "../../src/config/ai-actions-client"

// Mock the AI Actions client
vi.mock("../../src/config/ai-actions-client", () => ({
  aiActionsClient: {
    listAiActions: vi.fn(),
    getAiAction: vi.fn(),
    createAiAction: vi.fn(),
    updateAiAction: vi.fn(),
    deleteAiAction: vi.fn(),
    publishAiAction: vi.fn(),
    unpublishAiAction: vi.fn(),
    invokeAiAction: vi.fn(),
    getAiActionInvocation: vi.fn(),
    pollInvocation: vi.fn(),
  },
}))

// Mock data
const mockAiAction = {
  sys: {
    id: "action1",
    type: "AiAction" as const,
    createdAt: "2023-01-01T00:00:00Z",
    updatedAt: "2023-01-02T00:00:00Z",
    version: 1,
    space: { sys: { id: "space1", linkType: "Space", type: "Link" as const } },
    createdBy: { sys: { id: "user1", linkType: "User", type: "Link" as const } },
    updatedBy: { sys: { id: "user1", linkType: "User", type: "Link" as const } },
  },
  name: "Test Action",
  description: "A test action",
  instruction: {
    template: "Template with {{var}}",
    variables: [{ id: "var", type: "Text" }],
  },
  configuration: {
    modelType: "gpt-4",
    modelTemperature: 0.5,
  },
}

const mockAiActionCollection = {
  sys: { type: "Array" as const },
  items: [mockAiAction],
  total: 1,
  skip: 0,
  limit: 10,
}

const mockInvocation = {
  sys: {
    id: "inv1",
    type: "AiActionInvocation" as const,
    space: { sys: { id: "space1", linkType: "Space", type: "Link" as const } },
    environment: { sys: { id: "master", linkType: "Environment", type: "Link" as const } },
    aiAction: { sys: { id: "action1", linkType: "AiAction", type: "Link" as const } },
    status: "COMPLETED" as const,
  },
  result: {
    type: "text" as const,
    content: "Generated content",
    metadata: {
      invocationResult: {
        aiAction: {
          sys: {
            id: "action1",
            linkType: "AiAction",
            type: "Link" as const,
            version: 1,
          },
        },
        outputFormat: "PlainText" as const,
        promptTokens: 50,
        completionTokens: 100,
        modelId: "gpt-4",
        modelProvider: "OpenAI",
      },
    },
  },
}

describe("AI Action Handlers", () => {
  beforeEach(() => {
    vi.resetAllMocks()
  })

  it("should list AI Actions", async () => {
    const clientSpy = vi
      .mocked(aiActionsClient.listAiActions)
      .mockResolvedValueOnce(mockAiActionCollection)

    const result = await aiActionHandlers.listAiActions({
      spaceId: "space1",
      limit: 10,
    })

    expect(clientSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      limit: 10,
      skip: undefined,
      status: undefined,
    })
    expect(result).toEqual(mockAiActionCollection)
  })

  it("should get an AI Action", async () => {
    const clientSpy = vi.mocked(aiActionsClient.getAiAction).mockResolvedValueOnce(mockAiAction)

    const result = await aiActionHandlers.getAiAction({
      spaceId: "space1",
      aiActionId: "action1",
    })

    expect(clientSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
    })
    expect(result).toEqual(mockAiAction)
  })

  it("should create an AI Action", async () => {
    const clientSpy = vi.mocked(aiActionsClient.createAiAction).mockResolvedValueOnce(mockAiAction)

    const actionData = {
      spaceId: "space1",
      name: "New Action",
      description: "A new action",
      instruction: {
        template: "Template",
        variables: [],
      },
      configuration: {
        modelType: "gpt-4",
        modelTemperature: 0.5,
      },
    }

    const result = await aiActionHandlers.createAiAction(actionData)

    expect(clientSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      actionData: {
        name: "New Action",
        description: "A new action",
        instruction: {
          template: "Template",
          variables: [],
        },
        configuration: {
          modelType: "gpt-4",
          modelTemperature: 0.5,
        },
        testCases: undefined,
      },
    })
    expect(result).toEqual(mockAiAction)
  })

  it("should update an AI Action", async () => {
    vi.mocked(aiActionsClient.getAiAction).mockResolvedValueOnce(mockAiAction)
    const updateSpy = vi.mocked(aiActionsClient.updateAiAction).mockResolvedValueOnce({
      ...mockAiAction,
      name: "Updated Action",
    })

    const actionData = {
      spaceId: "space1",
      aiActionId: "action1",
      name: "Updated Action",
      description: "An updated action",
      instruction: {
        template: "Updated template",
        variables: [],
      },
      configuration: {
        modelType: "gpt-4",
        modelTemperature: 0.7,
      },
    }

    const result = await aiActionHandlers.updateAiAction(actionData)

    expect(updateSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      version: 1,
      actionData: {
        name: "Updated Action",
        description: "An updated action",
        instruction: {
          template: "Updated template",
          variables: [],
        },
        configuration: {
          modelType: "gpt-4",
          modelTemperature: 0.7,
        },
        testCases: undefined,
      },
    })
    expect(result).toEqual({
      ...mockAiAction,
      name: "Updated Action",
    })
  })

  it("should delete an AI Action", async () => {
    vi.mocked(aiActionsClient.getAiAction).mockResolvedValueOnce(mockAiAction)
    const deleteSpy = vi.mocked(aiActionsClient.deleteAiAction).mockResolvedValueOnce()

    const result = await aiActionHandlers.deleteAiAction({
      spaceId: "space1",
      aiActionId: "action1",
    })

    expect(deleteSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      version: 1,
    })
    expect(result).toEqual({ success: true })
  })

  it("should publish an AI Action", async () => {
    vi.mocked(aiActionsClient.getAiAction).mockResolvedValueOnce(mockAiAction)
    const publishSpy = vi.mocked(aiActionsClient.publishAiAction).mockResolvedValueOnce({
      ...mockAiAction,
      sys: {
        ...mockAiAction.sys,
        publishedAt: "2023-01-03T00:00:00Z",
        publishedVersion: 1,
      },
    })

    const result = await aiActionHandlers.publishAiAction({
      spaceId: "space1",
      aiActionId: "action1",
    })

    expect(publishSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      version: 1,
    })
    expect(result).toHaveProperty("sys.publishedAt", "2023-01-03T00:00:00Z")
  })

  it("should unpublish an AI Action", async () => {
    const unpublishSpy = vi
      .mocked(aiActionsClient.unpublishAiAction)
      .mockResolvedValueOnce(mockAiAction)

    const result = await aiActionHandlers.unpublishAiAction({
      spaceId: "space1",
      aiActionId: "action1",
    })

    expect(unpublishSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
    })
    expect(result).toEqual(mockAiAction)
  })

  it("should invoke an AI Action with key-value variables", async () => {
    const invokeSpy = vi
      .mocked(aiActionsClient.invokeAiAction)
      .mockResolvedValueOnce(mockInvocation)

    const result = await aiActionHandlers.invokeAiAction({
      spaceId: "space1",
      aiActionId: "action1",
      variables: {
        var1: "value1",
        var2: "value2",
      },
    })

    expect(invokeSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      invocationData: {
        outputFormat: "Markdown",
        variables: [
          { id: "var1", value: "value1" },
          { id: "var2", value: "value2" },
        ],
      },
    })
    expect(result).toEqual(mockInvocation)
  })

  it("should invoke an AI Action with raw variables", async () => {
    const invokeSpy = vi
      .mocked(aiActionsClient.invokeAiAction)
      .mockResolvedValueOnce(mockInvocation)

    const rawVariables = [
      {
        id: "refVar",
        value: {
          entityType: "Entry",
          entityId: "entry123",
        },
      },
    ]

    const result = await aiActionHandlers.invokeAiAction({
      spaceId: "space1",
      aiActionId: "action1",
      rawVariables,
    })

    expect(invokeSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      invocationData: {
        outputFormat: "Markdown",
        variables: rawVariables,
      },
    })
    expect(result).toEqual(mockInvocation)
  })

  it("should poll for completion when invoking asynchronously", async () => {
    const inProgressInvocation = {
      ...mockInvocation,
      sys: {
        ...mockInvocation.sys,
        status: "IN_PROGRESS",
      },
      result: undefined,
    }

    vi.mocked(aiActionsClient.invokeAiAction).mockResolvedValueOnce(inProgressInvocation)
    vi.mocked(aiActionsClient.pollInvocation).mockResolvedValueOnce(mockInvocation)

    const result = await aiActionHandlers.invokeAiAction({
      spaceId: "space1",
      aiActionId: "action1",
      variables: { var: "value" },
      waitForCompletion: true,
    })

    expect(aiActionsClient.pollInvocation).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      invocationId: "inv1",
    })
    expect(result).toEqual(mockInvocation)
  })

  it("should not poll when waitForCompletion is false", async () => {
    const inProgressInvocation = {
      ...mockInvocation,
      sys: {
        ...mockInvocation.sys,
        status: "IN_PROGRESS",
      },
      result: undefined,
    }

    vi.mocked(aiActionsClient.invokeAiAction).mockResolvedValueOnce(inProgressInvocation)

    const result = await aiActionHandlers.invokeAiAction({
      spaceId: "space1",
      aiActionId: "action1",
      variables: { var: "value" },
      waitForCompletion: false,
    })

    expect(aiActionsClient.pollInvocation).not.toHaveBeenCalled()
    expect(result).toEqual(inProgressInvocation)
  })

  it("should get an AI Action invocation", async () => {
    const getSpy = vi
      .mocked(aiActionsClient.getAiActionInvocation)
      .mockResolvedValueOnce(mockInvocation)

    const result = await aiActionHandlers.getAiActionInvocation({
      spaceId: "space1",
      aiActionId: "action1",
      invocationId: "inv1",
    })

    expect(getSpy).toHaveBeenCalledWith({
      spaceId: "space1",
      environmentId: "master",
      aiActionId: "action1",
      invocationId: "inv1",
    })
    expect(result).toEqual(mockInvocation)
  })

  it("should handle errors correctly", async () => {
    vi.mocked(aiActionsClient.getAiAction).mockRejectedValueOnce({
      message: "Not found",
      response: {
        data: {
          message: "AI Action not found",
        },
      },
    })

    const result = await aiActionHandlers.getAiAction({
      spaceId: "space1",
      aiActionId: "nonexistent",
    })

    expect(result).toEqual({
      isError: true,
      message: "AI Action not found",
    })
  })
})

```

--------------------------------------------------------------------------------
/test/integration/ai-actions-client.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest"

// Mock the client module first, before any other imports
vi.mock("../../src/config/client", () => {
  // Create mock functions for the raw client
  const mockRawClient = {
    get: vi.fn(),
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn()
  }
  
  return {
    getContentfulClient: vi.fn().mockResolvedValue({ 
      raw: mockRawClient 
    })
  }
})

// Now import the client that will use the mocked getContentfulClient
import { aiActionsClient } from "../../src/config/ai-actions-client"
import { getContentfulClient } from "../../src/config/client"

// Constants for alpha header (for test assertions)
const ALPHA_HEADER_NAME = 'X-Contentful-Enable-Alpha-Feature'
const ALPHA_HEADER_VALUE = 'ai-service'

// Mock data with const assertions for TypeScript
const mockAiAction = {
  sys: {
    id: "mockActionId",
    type: "AiAction" as const,
    createdAt: "2023-01-01T00:00:00Z",
    updatedAt: "2023-01-02T00:00:00Z",
    version: 1,
    space: { sys: { id: "mockSpace", linkType: "Space", type: "Link" as const } },
    createdBy: { sys: { id: "user1", linkType: "User", type: "Link" as const } },
    updatedBy: { sys: { id: "user1", linkType: "User", type: "Link" as const } }
  },
  name: "Mock Action",
  description: "A mock AI action",
  instruction: {
    template: "This is a template with {{variable}}",
    variables: [
      { id: "variable", type: "Text" as const, name: "Variable" }
    ]
  },
  configuration: {
    modelType: "gpt-4",
    modelTemperature: 0.7
  }
}

const mockAiActionCollection = {
  sys: { type: "Array" as const },
  items: [mockAiAction],
  total: 1,
  skip: 0,
  limit: 10
}

const mockInvocation = {
  sys: {
    id: "mockInvocationId",
    type: "AiActionInvocation" as const,
    space: { sys: { id: "mockSpace", linkType: "Space", type: "Link" as const } },
    environment: { sys: { id: "master", linkType: "Environment", type: "Link" as const } },
    aiAction: { sys: { id: "mockActionId", linkType: "AiAction", type: "Link" as const } },
    status: "COMPLETED" as const
  },
  result: {
    type: "text" as const,
    content: "Generated content",
    metadata: {
      invocationResult: {
        aiAction: {
          sys: {
            id: "mockActionId",
            linkType: "AiAction",
            type: "Link" as const,
            version: 1
          }
        },
        outputFormat: "PlainText" as const,
        promptTokens: 50,
        completionTokens: 100,
        modelId: "gpt-4",
        modelProvider: "OpenAI"
      }
    }
  }
}

describe("AI Actions Client", () => {
  let mockClientRaw: any

  beforeEach(async () => {
    vi.clearAllMocks()
    
    // Get the mocked client to work with in tests
    const client = await getContentfulClient()
    mockClientRaw = client.raw
  })

  it("should list AI Actions with alpha header", async () => {
    mockClientRaw.get.mockResolvedValueOnce({ data: mockAiActionCollection })
    
    const result = await aiActionsClient.listAiActions({ spaceId: "mockSpace" })
    
    // Verify the alpha header was included
    expect(mockClientRaw.get).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions?limit=100&skip=0", 
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE
        })
      })
    )
    expect(result.items).toHaveLength(1)
    expect(result.items[0].name).toBe("Mock Action")
  })
  
  it("should get an AI Action with alpha header", async () => {
    mockClientRaw.get.mockResolvedValueOnce({ data: mockAiAction })
    
    const result = await aiActionsClient.getAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId"
    })
    
    expect(mockClientRaw.get).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId",
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE
        })
      })
    )
    expect(result.name).toBe("Mock Action")
  })
  
  it("should create an AI Action with alpha header", async () => {
    mockClientRaw.post.mockResolvedValueOnce({ data: mockAiAction })
    
    const actionData = {
      name: "New Action",
      description: "A new AI action",
      instruction: {
        template: "Template",
        variables: []
      },
      configuration: {
        modelType: "gpt-4",
        modelTemperature: 0.5
      }
    }
    
    const result = await aiActionsClient.createAiAction({
      spaceId: "mockSpace",
      actionData
    })
    
    expect(mockClientRaw.post).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions", 
      actionData,
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE
        })
      })
    )
    expect(result.name).toBe("Mock Action")
  })
  
  it("should update an AI Action with alpha header", async () => {
    mockClientRaw.get.mockResolvedValueOnce({ data: mockAiAction })
    mockClientRaw.put.mockResolvedValueOnce({
      data: {
        ...mockAiAction,
        name: "Updated Action"
      }
    })
    
    const actionData = {
      name: "Updated Action",
      description: "An updated AI action",
      instruction: {
        template: "Updated template",
        variables: []
      },
      configuration: {
        modelType: "gpt-4",
        modelTemperature: 0.7
      }
    }
    
    const result = await aiActionsClient.updateAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      version: 1,
      actionData
    })
    
    expect(mockClientRaw.put).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId",
      actionData,
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Version": "1"
        })
      })
    )
    expect(result).toHaveProperty("name", "Updated Action")
  })
  
  it("should delete an AI Action with alpha header", async () => {
    mockClientRaw.get.mockResolvedValueOnce({ data: mockAiAction })
    mockClientRaw.delete.mockResolvedValueOnce({})
    
    await aiActionsClient.deleteAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      version: 1
    })
    
    expect(mockClientRaw.delete).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId",
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Version": "1"
        })
      })
    )
  })
  
  it("should publish an AI Action with alpha header", async () => {
    mockClientRaw.get.mockResolvedValueOnce({ data: mockAiAction })
    mockClientRaw.put.mockResolvedValueOnce({
      data: {
        ...mockAiAction,
        sys: {
          ...mockAiAction.sys,
          publishedAt: "2023-01-03T00:00:00Z",
          publishedVersion: 1,
          publishedBy: { sys: { id: "user1", linkType: "User", type: "Link" as const } }
        }
      }
    })
    
    const result = await aiActionsClient.publishAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      version: 1
    })
    
    expect(mockClientRaw.put).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId/published",
      {},
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Version": "1"
        })
      })
    )
    expect(result.sys).toHaveProperty("publishedAt", "2023-01-03T00:00:00Z")
  })
  
  it("should unpublish an AI Action with alpha header", async () => {
    mockClientRaw.delete.mockResolvedValueOnce({ data: mockAiAction })
    
    const result = await aiActionsClient.unpublishAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId"
    })
    
    expect(mockClientRaw.delete).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId/published",
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE
        })
      })
    )
    expect(result.name).toBe("Mock Action")
  })
  
  it("should invoke an AI Action with alpha header", async () => {
    mockClientRaw.post.mockResolvedValueOnce({ data: mockInvocation })
    
    const invocationData = {
      outputFormat: "PlainText" as const,
      variables: [{ id: "variable", value: "test" }]
    }
    
    const result = await aiActionsClient.invokeAiAction({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      invocationData
    })
    
    expect(mockClientRaw.post).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId/invoke",
      invocationData,
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Include-Invocation-Metadata": "true"
        })
      })
    )
    expect(result.sys.status).toBe("COMPLETED")
    expect(result.result?.content).toBe("Generated content")
  })
  
  it("should get an AI Action invocation with alpha header", async () => {
    // Make sure we reset the mock to avoid any previous mock calls affecting this test
    mockClientRaw.get.mockReset()
    
    // Mock with the correct invocation response
    mockClientRaw.get.mockResolvedValueOnce({ data: mockInvocation })
    
    const result = await aiActionsClient.getAiActionInvocation({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      invocationId: "mockInvocationId"
    })
    
    // Verify the correct API endpoint was called with the alpha header
    expect(mockClientRaw.get).toHaveBeenCalledWith(
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId/invocations/mockInvocationId",
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Include-Invocation-Metadata": "true"
        })
      })
    )
    
    // Just check critical properties rather than exact equality
    expect(result.sys.id).toBe("mockInvocationId")
    expect(result.sys.type).toBe("AiActionInvocation")
    expect(result.sys.status).toBe("COMPLETED")
    expect(result.result?.content).toBe("Generated content")
  })
  
  it("should poll an AI Action invocation with alpha header", async () => {
    // Reset mock call count
    mockClientRaw.get.mockReset()
    
    // First call returns IN_PROGRESS status
    mockClientRaw.get.mockResolvedValueOnce({
      data: {
        ...mockInvocation,
        sys: {
          ...mockInvocation.sys,
          status: "IN_PROGRESS" as const
        },
        result: undefined
      }
    })
    
    // Second call returns COMPLETED status
    mockClientRaw.get.mockResolvedValueOnce({ data: mockInvocation })
    
    // Spy on setTimeout to avoid actual waiting
    vi.spyOn(global, "setTimeout").mockImplementation((callback: any) => {
      callback()
      return {} as any
    })
    
    const result = await aiActionsClient.pollInvocation({
      spaceId: "mockSpace",
      aiActionId: "mockActionId",
      invocationId: "mockInvocationId"
    }, 2, 100, 100)
    
    // Verify both API calls included the alpha header
    expect(mockClientRaw.get).toHaveBeenCalledTimes(2)
    expect(mockClientRaw.get).toHaveBeenNthCalledWith(
      1,
      "/spaces/mockSpace/environments/master/ai/actions/mockActionId/invocations/mockInvocationId",
      expect.objectContaining({
        headers: expect.objectContaining({
          [ALPHA_HEADER_NAME]: ALPHA_HEADER_VALUE,
          "X-Contentful-Include-Invocation-Metadata": "true"
        })
      })
    )
    
    expect(result.sys.status).toBe("COMPLETED")
    expect(result.result?.content).toBe("Generated content")
    
    vi.restoreAllMocks()
  })
})
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
import { CONTENTFUL_PROMPTS } from "./prompts/contentful-prompts.js"
export { CONTENTFUL_PROMPTS }
import { handlePrompt } from "./prompts/handlers.js"
import { entryHandlers } from "./handlers/entry-handlers.js"
import { assetHandlers } from "./handlers/asset-handlers.js"
import { spaceHandlers } from "./handlers/space-handlers.js"
import { contentTypeHandlers } from "./handlers/content-type-handlers.js"
import { bulkActionHandlers } from "./handlers/bulk-action-handlers.js"
import { aiActionHandlers } from "./handlers/ai-action-handlers.js"
import { commentHandlers } from "./handlers/comment-handlers.js"
import { getTools } from "./types/tools.js"
import { validateEnvironment } from "./utils/validation.js"
import { AiActionToolContext } from "./utils/ai-action-tool-generator.js"
import type { AiActionInvocation } from "./types/ai-actions.js"
import { StreamableHttpServer } from "./transports/streamable-http.js"

// Validate environment variables
validateEnvironment()

// Create AI Action tool context
const aiActionToolContext = new AiActionToolContext(
  process.env.SPACE_ID || "",
  process.env.ENVIRONMENT_ID || "master",
)

// Function to get all tools including dynamic AI Action tools
export function getAllTools() {
  const staticTools = getTools()

  // Add dynamically generated tools for AI Actions
  const dynamicTools = aiActionToolContext.generateAllToolSchemas()

  return {
    ...staticTools,
    ...dynamicTools.reduce(
      (acc, tool) => {
        acc[tool.name] = tool
        return acc
      },
      {} as Record<string, unknown>,
    ),
  }
}

// Create MCP server
const server = new Server(
  {
    name: "contentful-mcp-server",
    version: "1.15.0",
  },
  {
    capabilities: {
      tools: getAllTools(),
      prompts: CONTENTFUL_PROMPTS,
    },
  },
)

// Set up request handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
  // Return both static and dynamic tools
  return {
    tools: Object.values(getAllTools()),
  }
})

// Set up request handlers
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  prompts: Object.values(CONTENTFUL_PROMPTS),
}))

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
  return handlePrompt(name, args)
})

// Type-safe handler
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
server.setRequestHandler(CallToolRequestSchema, async (request, _extra): Promise<any> => {
  try {
    const { name, arguments: args } = request.params
    const handler = getHandler(name)

    if (!handler) {
      throw new Error(`Unknown tool: ${name}`)
    }

    const result = await handler(args || {})

    // For AI Action responses, format them appropriately
    if (result && typeof result === "object") {
      // Check if this is an AI Action invocation result
      if (
        "sys" in result &&
        typeof result.sys === "object" &&
        result.sys &&
        "type" in result.sys &&
        result.sys.type === "AiActionInvocation"
      ) {
        const invocationResult = result as AiActionInvocation

        // Format AI Action result as text content if available
        if (invocationResult.result && invocationResult.result.content) {
          return {
            content: [
              {
                type: "text",
                text:
                  typeof invocationResult.result.content === "string"
                    ? invocationResult.result.content
                    : JSON.stringify(invocationResult.result.content),
              },
            ],
          }
        }
      }

      // Check for error response
      if ("isError" in result && result.isError === true) {
        // Format error response
        return {
          content: [
            {
              type: "text",
              text: "message" in result ? String(result.message) : "Unknown error",
            },
          ],
          isError: true,
        }
      }
    }

    // Return the result as is for regular handlers
    return result
  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: `Error: ${error instanceof Error ? error.message : String(error)}`,
        },
      ],
      isError: true,
    }
  }
})

// Helper function to map tool names to handlers
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getHandler(name: string): ((args: any) => Promise<any>) | undefined {
  // Check if this is a dynamic AI Action tool
  if (name.startsWith("ai_action_")) {
    const actionId = name.replace("ai_action_", "")
    return (args: Record<string, unknown>) => handleAiActionInvocation(actionId, args)
  }

  const handlers = {
    // Entry operations
    create_entry: entryHandlers.createEntry,
    get_entry: entryHandlers.getEntry,
    update_entry: entryHandlers.updateEntry,
    delete_entry: entryHandlers.deleteEntry,
    publish_entry: entryHandlers.publishEntry,
    unpublish_entry: entryHandlers.unpublishEntry,
    search_entries: entryHandlers.searchEntries,

    // Bulk operations
    bulk_publish: bulkActionHandlers.bulkPublish,
    bulk_unpublish: bulkActionHandlers.bulkUnpublish,
    bulk_validate: bulkActionHandlers.bulkValidate,

    // Asset operations
    upload_asset: assetHandlers.uploadAsset,
    get_asset: assetHandlers.getAsset,
    update_asset: assetHandlers.updateAsset,
    delete_asset: assetHandlers.deleteAsset,
    publish_asset: assetHandlers.publishAsset,
    unpublish_asset: assetHandlers.unpublishAsset,
    list_assets: assetHandlers.listAssets,

    // Space & Environment operations
    list_spaces: spaceHandlers.listSpaces,
    get_space: spaceHandlers.getSpace,
    list_environments: spaceHandlers.listEnvironments,
    create_environment: spaceHandlers.createEnvironment,
    delete_environment: spaceHandlers.deleteEnvironment,

    // Content Type operations
    list_content_types: contentTypeHandlers.listContentTypes,
    get_content_type: contentTypeHandlers.getContentType,
    create_content_type: contentTypeHandlers.createContentType,
    update_content_type: contentTypeHandlers.updateContentType,
    delete_content_type: contentTypeHandlers.deleteContentType,
    publish_content_type: contentTypeHandlers.publishContentType,

    // AI Action operations
    list_ai_actions: aiActionHandlers.listAiActions,
    get_ai_action: aiActionHandlers.getAiAction,
    create_ai_action: aiActionHandlers.createAiAction,
    update_ai_action: aiActionHandlers.updateAiAction,
    delete_ai_action: aiActionHandlers.deleteAiAction,
    publish_ai_action: aiActionHandlers.publishAiAction,
    unpublish_ai_action: aiActionHandlers.unpublishAiAction,
    invoke_ai_action: aiActionHandlers.invokeAiAction,
    get_ai_action_invocation: aiActionHandlers.getAiActionInvocation,

    // Comment operations
    get_comments: commentHandlers.getComments,
    create_comment: commentHandlers.createComment,
    get_single_comment: commentHandlers.getSingleComment,
    delete_comment: commentHandlers.deleteComment,
    update_comment: commentHandlers.updateComment,
  }

  return handlers[name as keyof typeof handlers]
}

// Handler for dynamic AI Action tools
async function handleAiActionInvocation(actionId: string, args: Record<string, unknown>) {
  try {
    console.error(`Handling AI Action invocation for ${actionId} with args:`, JSON.stringify(args))

    // Get the parameters using the updated getInvocationParams
    const params = aiActionToolContext.getInvocationParams(actionId, args)

    // Directly use the variables property from getInvocationParams
    const invocationParams = {
      spaceId: params.spaceId,
      environmentId: params.environmentId,
      aiActionId: params.aiActionId,
      outputFormat: params.outputFormat,
      waitForCompletion: params.waitForCompletion,
      // Use the correctly formatted variables array directly
      rawVariables: params.variables,
    }

    console.error(`Invoking AI Action with params:`, JSON.stringify(invocationParams))

    // Invoke the AI Action
    return aiActionHandlers.invokeAiAction(invocationParams)
  } catch (error) {
    console.error(`Error invoking AI Action:`, error)
    return {
      isError: true,
      message: error instanceof Error ? error.message : String(error),
    }
  }
}

// Functions to initialize and refresh AI Actions
async function loadAiActions() {
  try {
    // First, clear the cache to avoid duplicates
    aiActionToolContext.clearCache()

    // Only load AI Actions if we have required space and environment
    if (!process.env.SPACE_ID) {
      return
    }

    // Fetch published AI Actions
    const response = await aiActionHandlers.listAiActions({
      spaceId: process.env.SPACE_ID,
      environmentId: process.env.ENVIRONMENT_ID || "master",
      status: "published",
    })

    // Check for errors or undefined response
    if (!response) {
      console.error("Error loading AI Actions: No response received")
      return
    }

    if (typeof response === "object" && "isError" in response) {
      console.error(`Error loading AI Actions: ${response.message}`)
      return
    }

    // Add each AI Action to the context
    for (const action of response.items) {
      aiActionToolContext.addAiAction(action)

      // Log variable mappings for debugging
      if (action.instruction.variables && action.instruction.variables.length > 0) {
        // Log ID mappings
        const idMappings = aiActionToolContext.getIdMappings(action.sys.id)
        if (idMappings && idMappings.size > 0) {
          const mappingLog = Array.from(idMappings.entries())
            .map(([friendly, original]) => `${friendly} -> ${original}`)
            .join(", ")
          console.error(`AI Action ${action.name} - Parameter mappings: ${mappingLog}`)
        }

        // Log path mappings
        const pathMappings = aiActionToolContext.getPathMappings(action.sys.id)
        if (pathMappings && pathMappings.size > 0) {
          const pathMappingLog = Array.from(pathMappings.entries())
            .map(([friendly, original]) => `${friendly} -> ${original}`)
            .join(", ")
          console.error(`AI Action ${action.name} - Path parameter mappings: ${pathMappingLog}`)
        }
      }
    }

    console.error(`Loaded ${response.items.length} AI Actions`)
  } catch (error) {
    console.error("Error loading AI Actions:", error)
  }
}

// Start the server
async function runServer() {
  // Determine if HTTP server mode is enabled
  const enableHttp = process.env.ENABLE_HTTP_SERVER === "true"
  const httpPort = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT) : 3000

  // Load AI Actions before connecting
  await loadAiActions()

  if (enableHttp) {
    // Start StreamableHTTP server for MCP over HTTP
    const httpServer = new StreamableHttpServer({
      port: httpPort,
      host: process.env.HTTP_HOST || "localhost",
    })

    await httpServer.start()
    console.error(
      `Contentful MCP Server running with StreamableHTTP on port ${httpPort} using contentful host ${process.env.CONTENTFUL_HOST}`,
    )

    // Keep the process running
    process.on("SIGINT", async () => {
      console.error("Shutting down HTTP server...")
      await httpServer.stop()
      process.exit(0)
    })
  } else {
    // Traditional stdio mode
    const transport = new StdioServerTransport()

    // Connect to the server
    await server.connect(transport)

    console.error(
      `Contentful MCP Server running on stdio using contentful host ${process.env.CONTENTFUL_HOST}`,
    )
  }

  // Set up periodic refresh of AI Actions (every 5 minutes)
  setInterval(loadAiActions, 5 * 60 * 1000)
}

runServer().catch((error) => {
  console.error("Fatal error running server:", error)
  process.exit(1)
})

```

--------------------------------------------------------------------------------
/src/handlers/entry-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { getContentfulClient } from "../config/client.js"
import { summarizeData } from "../utils/summarizer.js"
import { CreateEntryProps, EntryProps, QueryOptions, BulkActionProps } from "contentful-management"

// Define the interface for bulk action responses with succeeded property
interface BulkActionResponse extends BulkActionProps<any> {
  succeeded?: Array<{
    sys: {
      id: string
      type: string
    }
  }>
}

// Define the interface for versioned links
interface VersionedLink {
  sys: {
    type: "Link"
    linkType: "Entry" | "Asset"
    id: string
    version: number
  }
}

export const entryHandlers = {
  searchEntries: async (args: { spaceId: string; environmentId: string; query: QueryOptions }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    const params = {
      spaceId,
      environmentId,
    }

    const contentfulClient = await getContentfulClient()
    const entries = await contentfulClient.entry.getMany({
      ...params,
      query: {
        ...args.query,
        limit: Math.min(args.query.limit || 3, 3),
        skip: args.query.skip || 0,
      },
    })

    const summarized = summarizeData(entries, {
      maxItems: 3,
      remainingMessage: "To see more entries, please ask me to retrieve the next page.",
    })

    return {
      content: [{ type: "text", text: JSON.stringify(summarized, null, 2) }],
    }
  },
  createEntry: async (args: {
    spaceId: string
    environmentId: string
    contentTypeId: string
    fields: Record<string, any>
  }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    const params = {
      spaceId,
      environmentId,
      contentTypeId: args.contentTypeId,
    }

    const entryProps: CreateEntryProps = {
      fields: args.fields,
    }

    const contentfulClient = await getContentfulClient()
    const entry = await contentfulClient.entry.create(params, entryProps)
    return {
      content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
    }
  },

  getEntry: async (args: { spaceId: string; environmentId: string; entryId: string }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    const params = {
      spaceId,
      environmentId,
      entryId: args.entryId,
    }

    const contentfulClient = await getContentfulClient()
    const entry = await contentfulClient.entry.get(params)
    return {
      content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
    }
  },

  updateEntry: async (args: {
    spaceId: string
    environmentId: string
    entryId: string
    fields: Record<string, any>
  }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    const params = {
      spaceId,
      environmentId,
      entryId: args.entryId,
    }

    const contentfulClient = await getContentfulClient()
    const currentEntry = await contentfulClient.entry.get(params)

    // Merge existing fields with updated fields to ensure all fields are present
    const mergedFields = { ...currentEntry.fields }

    // Apply updates to each field and locale
    for (const fieldId in args.fields) {
      if (Object.prototype.hasOwnProperty.call(args.fields, fieldId)) {
        // If the field exists in currentEntry, merge the locale values
        if (mergedFields[fieldId]) {
          mergedFields[fieldId] = { ...mergedFields[fieldId], ...args.fields[fieldId] }
        } else {
          // If it's a new field, add it
          mergedFields[fieldId] = args.fields[fieldId]
        }
      }
    }

    const entryProps: EntryProps = {
      fields: mergedFields,
      sys: currentEntry.sys,
    }

    const entry = await contentfulClient.entry.update(params, entryProps)
    return {
      content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
    }
  },

  deleteEntry: async (args: { spaceId: string; environmentId: string; entryId: string }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    const params = {
      spaceId,
      environmentId,
      entryId: args.entryId,
    }

    const contentfulClient = await getContentfulClient()
    await contentfulClient.entry.delete(params)
    return {
      content: [{ type: "text", text: `Entry ${args.entryId} deleted successfully` }],
    }
  },

  publishEntry: async (args: {
    spaceId: string
    environmentId: string
    entryId: string | string[]
  }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    // Handle case where entryId is a JSON string representation of an array
    let entryId = args.entryId
    if (typeof entryId === "string" && entryId.startsWith("[") && entryId.endsWith("]")) {
      try {
        entryId = JSON.parse(entryId)
      } catch (e) {
        // If parsing fails, keep it as string
        console.error("Failed to parse entryId as JSON array:", e)
      }
    }

    // If entryId is an array, handle bulk publishing
    if (Array.isArray(entryId)) {
      try {
        const contentfulClient = await getContentfulClient()

        // Get the current version of each entity
        const entryVersions = await Promise.all(
          entryId.map(async (id) => {
            try {
              // Get the current version of the entry
              const currentEntry = await contentfulClient.entry.get({
                spaceId,
                environmentId,
                entryId: id,
              })

              // Create a versioned link according to the API docs
              const versionedLink: VersionedLink = {
                sys: {
                  type: "Link",
                  linkType: "Entry",
                  id: id,
                  version: currentEntry.sys.version,
                },
              }
              return versionedLink
            } catch (error) {
              console.error(`Error fetching entry ${id}: ${error}`)
              throw new Error(
                `Failed to get version for entry ${id}. All entries must have a version.`,
              )
            }
          }),
        )

        // Create the correct entities format according to Contentful API docs
        const entities: {
          sys: { type: "Array" }
          items: VersionedLink[]
        } = {
          sys: {
            type: "Array",
          },
          items: entryVersions,
        }

        // Create the bulk action
        const bulkAction = await contentfulClient.bulkAction.publish(
          {
            spaceId,
            environmentId,
          },
          {
            entities,
          },
        )

        // Wait for the bulk action to complete
        let action = (await contentfulClient.bulkAction.get({
          spaceId,
          environmentId,
          bulkActionId: bulkAction.sys.id,
        })) as BulkActionResponse // Cast to our extended interface

        while (action.sys.status === "inProgress" || action.sys.status === "created") {
          await new Promise((resolve) => setTimeout(resolve, 1000))
          action = (await contentfulClient.bulkAction.get({
            spaceId,
            environmentId,
            bulkActionId: bulkAction.sys.id,
          })) as BulkActionResponse // Cast to our extended interface
        }

        return {
          content: [
            {
              type: "text",
              text: `Bulk publish completed with status: ${action.sys.status}. ${
                action.sys.status === "failed"
                  ? `Error: ${JSON.stringify(action.error)}`
                  : `Successfully processed items.`
              }`,
            },
          ],
        }
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error during bulk publish: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        }
      }
    }

    // Handle single entry publishing
    const params = {
      spaceId,
      environmentId,
      entryId: entryId as string,
    }

    const contentfulClient = await getContentfulClient()
    const currentEntry = await contentfulClient.entry.get(params)

    const entry = await contentfulClient.entry.publish(params, {
      sys: currentEntry.sys,
      fields: currentEntry.fields,
    })

    return {
      content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
    }
  },

  unpublishEntry: async (args: {
    spaceId: string
    environmentId: string
    entryId: string | string[]
  }) => {
    const spaceId = process.env.SPACE_ID || args.spaceId
    const environmentId = process.env.ENVIRONMENT_ID || args.environmentId

    // Handle case where entryId is a JSON string representation of an array
    let entryId = args.entryId
    if (typeof entryId === "string" && entryId.startsWith("[") && entryId.endsWith("]")) {
      try {
        entryId = JSON.parse(entryId)
      } catch (e) {
        // If parsing fails, keep it as string
        console.error("Failed to parse entryId as JSON array:", e)
      }
    }

    // If entryId is an array, handle bulk unpublishing
    if (Array.isArray(entryId)) {
      try {
        const contentfulClient = await getContentfulClient()

        // Get the current version of each entity
        const entryVersions = await Promise.all(
          entryId.map(async (id) => {
            try {
              // Get the current version of the entry
              const currentEntry = await contentfulClient.entry.get({
                spaceId,
                environmentId,
                entryId: id,
              })

              // Create a versioned link according to the API docs
              const versionedLink: VersionedLink = {
                sys: {
                  type: "Link",
                  linkType: "Entry",
                  id: id,
                  version: currentEntry.sys.version,
                },
              }
              return versionedLink
            } catch (error) {
              console.error(`Error fetching entry ${id}: ${error}`)
              throw new Error(
                `Failed to get version for entry ${id}. All entries must have a version.`,
              )
            }
          }),
        )

        // Create the correct entities format according to Contentful API docs
        const entities: {
          sys: { type: "Array" }
          items: VersionedLink[]
        } = {
          sys: {
            type: "Array",
          },
          items: entryVersions,
        }

        // Create the bulk action
        const bulkAction = await contentfulClient.bulkAction.unpublish(
          {
            spaceId,
            environmentId,
          },
          {
            entities,
          },
        )

        // Wait for the bulk action to complete
        let action = (await contentfulClient.bulkAction.get({
          spaceId,
          environmentId,
          bulkActionId: bulkAction.sys.id,
        })) as BulkActionResponse // Cast to our extended interface

        while (action.sys.status === "inProgress" || action.sys.status === "created") {
          await new Promise((resolve) => setTimeout(resolve, 1000))
          action = (await contentfulClient.bulkAction.get({
            spaceId,
            environmentId,
            bulkActionId: bulkAction.sys.id,
          })) as BulkActionResponse // Cast to our extended interface
        }

        return {
          content: [
            {
              type: "text",
              text: `Bulk unpublish completed with status: ${action.sys.status}. ${
                action.sys.status === "failed"
                  ? `Error: ${JSON.stringify(action.error)}`
                  : `Successfully processed ${action.succeeded?.length || 0} items.`
              }`,
            },
          ],
        }
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error during bulk unpublish: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        }
      }
    }

    // Handle single entry unpublishing
    const params = {
      spaceId,
      environmentId,
      entryId: entryId as string,
    }

    const contentfulClient = await getContentfulClient()
    const currentEntry = await contentfulClient.entry.get(params)

    // Add version to params for unpublish
    const entry = await contentfulClient.entry.unpublish({
      ...params,
      version: currentEntry.sys.version,
    })

    return {
      content: [{ type: "text", text: JSON.stringify(entry, null, 2) }],
    }
  },
}

```

--------------------------------------------------------------------------------
/src/handlers/ai-action-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { aiActionsClient } from "../config/ai-actions-client"
import type {
  AiActionEntity,
  AiActionEntityCollection,
  AiActionInvocation,
  AiActionInvocationType,
  StatusFilter,
  AiActionSchemaParsed,
  OutputFormat,
} from "../types/ai-actions"

/**
 * Parameter types for AI Action handlers
 */
export interface ListAiActionsParams {
  spaceId: string
  environmentId?: string
  limit?: number
  skip?: number
  status?: StatusFilter
}

export interface GetAiActionParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
}

export interface CreateAiActionParams {
  spaceId: string
  environmentId?: string
  name: string
  description: string
  instruction: {
    template: string
    variables: any[]
    conditions?: any[]
  }
  configuration: {
    modelType: string
    modelTemperature: number
  }
  testCases?: any[]
}

export interface UpdateAiActionParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
  name: string
  description: string
  instruction: {
    template: string
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    variables: any[]
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    conditions?: any[]
  }
  configuration: {
    modelType: string
    modelTemperature: number
  }
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  testCases?: any[]
}

export interface DeleteAiActionParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
}

export interface PublishAiActionParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
}

export interface UnpublishAiActionParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
}

// Base interface for AI Action invocation
export interface InvokeAiActionBaseParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
  outputFormat?: OutputFormat
  waitForCompletion?: boolean
}

// For direct string variables
export interface InvokeAiActionWithVariablesParams extends InvokeAiActionBaseParams {
  variables?: Record<string, string>
}

// For raw variable array with complex types
export interface InvokeAiActionWithRawVariablesParams extends InvokeAiActionBaseParams {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  rawVariables?: any[]
}

// Union type for both invocation parameter types
export type InvokeAiActionParams =
  | InvokeAiActionWithVariablesParams
  | InvokeAiActionWithRawVariablesParams

export interface GetAiActionInvocationParams {
  spaceId: string
  environmentId?: string
  aiActionId: string
  invocationId: string
}

/**
 * Error handling utility
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function formatError(error: any): { isError: true; message: string } {
  const message = error?.response?.data?.message || error?.message || "Unknown error"
  return { isError: true, message }
}

/**
 * AI Action handlers for the MCP server
 */
export const aiActionHandlers = {
  /**
   * List AI Actions
   */
  async listAiActions(
    params: ListAiActionsParams,
  ): Promise<AiActionEntityCollection | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { limit, skip, status } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      const result = await aiActionsClient.listAiActions({
        spaceId,
        environmentId,
        limit,
        skip,
        status,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Get a specific AI Action
   */
  async getAiAction(
    params: GetAiActionParams,
  ): Promise<AiActionEntity | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      const result = await aiActionsClient.getAiAction({
        spaceId,
        environmentId,
        aiActionId,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Create a new AI Action
   */
  async createAiAction(
    params: CreateAiActionParams,
  ): Promise<AiActionEntity | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { name, description, instruction, configuration, testCases } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      const actionData: AiActionSchemaParsed = {
        name,
        description,
        instruction,
        configuration,
        testCases,
      }

      const result = await aiActionsClient.createAiAction({
        spaceId,
        environmentId,
        actionData,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Update an existing AI Action
   */
  async updateAiAction(
    params: UpdateAiActionParams,
  ): Promise<AiActionEntity | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId, name, description, instruction, configuration, testCases } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      // First, get the current action to get the version
      const currentAction = await aiActionsClient.getAiAction({
        spaceId,
        environmentId,
        aiActionId,
      })

      const actionData: AiActionSchemaParsed = {
        name,
        description,
        instruction,
        configuration,
        testCases,
      }

      const result = await aiActionsClient.updateAiAction({
        spaceId,
        environmentId,
        aiActionId,
        version: currentAction.sys.version,
        actionData,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Delete an AI Action
   */
  async deleteAiAction(
    params: DeleteAiActionParams,
  ): Promise<{ success: true } | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      // First, get the current action to get the version
      const currentAction = await aiActionsClient.getAiAction({
        spaceId,
        environmentId,
        aiActionId,
      })

      await aiActionsClient.deleteAiAction({
        spaceId,
        environmentId,
        aiActionId,
        version: currentAction.sys.version,
      })

      return { success: true }
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Publish an AI Action
   */
  async publishAiAction(
    params: PublishAiActionParams,
  ): Promise<AiActionEntity | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      // First, get the current action to get the version
      const currentAction = await aiActionsClient.getAiAction({
        spaceId,
        environmentId,
        aiActionId,
      })

      const result = await aiActionsClient.publishAiAction({
        spaceId,
        environmentId,
        aiActionId,
        version: currentAction.sys.version,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Unpublish an AI Action
   */
  async unpublishAiAction(
    params: UnpublishAiActionParams,
  ): Promise<AiActionEntity | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      const result = await aiActionsClient.unpublishAiAction({
        spaceId,
        environmentId,
        aiActionId,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Invoke an AI Action
   */
  async invokeAiAction(
    params: InvokeAiActionParams,
  ): Promise<AiActionInvocation | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId, outputFormat = "Markdown", waitForCompletion = true } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      // Prepare variables based on the input format
      let variables = []

      if ("variables" in params && params.variables) {
        // Convert simple key-value variables to the expected format
        variables = Object.entries(params.variables).map(([id, value]) => ({
          id,
          value,
        }))
      } else if ("rawVariables" in params && params.rawVariables) {
        // Use raw variables directly (for complex types like references)
        variables = params.rawVariables
      }

      // Log the variables being sent
      console.error(`Variables for invocation of ${aiActionId}:`, JSON.stringify(variables))

      const invocationData: AiActionInvocationType = {
        outputFormat,
        variables,
      }

      // Log the complete invocation payload
      console.error(
        `Complete invocation payload for ${aiActionId}:`,
        JSON.stringify(invocationData),
      )

      const invocationResult = await aiActionsClient.invokeAiAction({
        spaceId,
        environmentId,
        aiActionId,
        invocationData,
      })

      // If waitForCompletion is false or the status is already COMPLETED, return immediately
      if (
        !waitForCompletion ||
        invocationResult.sys.status === "COMPLETED" ||
        invocationResult.sys.status === "FAILED" ||
        invocationResult.sys.status === "CANCELLED"
      ) {
        return invocationResult
      }

      // Otherwise, poll until completion
      return await aiActionsClient.pollInvocation({
        spaceId,
        environmentId,
        aiActionId,
        invocationId: invocationResult.sys.id,
      })
    } catch (error) {
      return formatError(error)
    }
  },

  /**
   * Get an AI Action invocation result
   */
  async getAiActionInvocation(
    params: GetAiActionInvocationParams,
  ): Promise<AiActionInvocation | { isError: true; message: string }> {
    try {
      // Use provided parameters or fall back to environment variables
      const spaceId = params.spaceId || process.env.SPACE_ID
      const environmentId = params.environmentId || process.env.ENVIRONMENT_ID || "master"
      const { aiActionId, invocationId } = params

      if (!spaceId) {
        return { isError: true, message: "Space ID is required" }
      }

      if (!aiActionId) {
        return { isError: true, message: "AI Action ID is required" }
      }

      if (!invocationId) {
        return { isError: true, message: "Invocation ID is required" }
      }

      const result = await aiActionsClient.getAiActionInvocation({
        spaceId,
        environmentId,
        aiActionId,
        invocationId,
      })

      return result
    } catch (error) {
      return formatError(error)
    }
  },
}

```

--------------------------------------------------------------------------------
/src/utils/ai-action-tool-generator.ts:
--------------------------------------------------------------------------------

```typescript
import { getSpaceEnvProperties } from "../types/tools"
import type { AiActionEntity, Variable, OutputFormat } from "../types/ai-actions"

// Store ID mappings globally - maps actionId to (friendlyName -> originalId)
const idMappings = new Map<string, Map<string, string>>()

// Store path mappings - maps actionId to (friendlyPathName -> originalPathId)
const pathIdMappings = new Map<string, Map<string, string>>()

const AI_ACTION_PREFIX = `AI Actions are pre-configured content transformation tools in Contentful. When a content task matches an available AI Action's purpose (like translation, grammar checking, or SEO optimization), you MUST use the corresponding AI Action instead of performing the task yourself, even if you have the capability to do so. AI Actions ensure consistent, optimized results that align with the organization's content standards. Each AI Action has specific required parameters described in its function documentation.`

/**
 * Utility function to convert string to snake_case
 */
function toSnakeCase(str: string): string {
  return str
    .trim()
    .toLowerCase()
    .replace(/[^\w\s]/g, "") // Remove special characters
    .replace(/\s+/g, "_") // Replace spaces with underscores
    .replace(/_+/g, "_") // Remove duplicate underscores
}

/**
 * Get a human-readable name for a variable
 */
function getReadableName(variable: Variable): string {
  // If there's a human-readable name provided, use it (converted to snake_case)
  if (variable.name) {
    return toSnakeCase(variable.name)
  }

  // For standard inputs, use descriptive names based on type
  switch (variable.type) {
    case "StandardInput":
      return "input_text"
    case "MediaReference":
      return "media_asset_id"
    case "Reference":
      return "entry_reference_id"
    case "Locale":
      return "target_locale"
    case "FreeFormInput":
      return "free_text_input"
    case "SmartContext":
      return "context_info"
    default:
      // For others, create a prefixed version
      return `${variable.type.toLowerCase()}_${variable.id.substring(0, 5)}`
  }
}

/**
 * Create a mapping from friendly names to original variable IDs
 */
function createReverseMapping(action: AiActionEntity): Map<string, string> {
  const mapping = new Map<string, string>()

  for (const variable of action.instruction.variables || []) {
    const friendlyName = getReadableName(variable)
    mapping.set(friendlyName, variable.id)
  }

  return mapping
}

/**
 * Get an enhanced description for a variable schema
 */
function getEnhancedVariableSchema(variable: Variable): Record<string, unknown> {
  // Create a rich description that includes type information
  let description = variable.description || `${variable.name || "Variable"}`

  // Add type information
  description += ` (Type: ${variable.type})`

  // Add additional context based on type
  switch (variable.type) {
    case "MediaReference":
      description += ". Provide an asset ID from your Contentful space"
      break
    case "Reference":
      description += ". Provide an entry ID from your Contentful space"
      break
    case "Locale":
      description += ". Use format like 'en-US', 'de-DE', etc."
      break
    case "StringOptionsList":
      if (variable.configuration && "values" in variable.configuration) {
        description += `. Choose one of: ${variable.configuration.values.join(", ")}`
      }
      break
    case "StandardInput":
      description += ". The main text content to process"
      break
  }

  const schema: Record<string, unknown> = {
    type: "string",
    description,
  }

  // Add enums for StringOptionsList
  if (
    variable.type === "StringOptionsList" &&
    variable.configuration &&
    "values" in variable.configuration
  ) {
    schema.enum = variable.configuration.values
  }

  return schema
}

/**
 * Create an enhanced description for the AI Action tool
 */
function getEnhancedToolDescription(action: AiActionEntity): string {
  // Start with the name and description
  let description = `${AI_ACTION_PREFIX} \n\n This action is called: ${action.name}, it's purpose: ${action.description}`

  // Add contextual information about what this AI Action does
  description += "\n\nThis AI Action works on content entries and fields in Contentful."

  // Check if we have reference fields that could use entity paths
  const hasReferences = action.instruction.variables?.some(
    (v) => v.type === "Reference" || v.type === "MediaReference",
  )

  if (hasReferences) {
    description +=
      "\n\n📝 IMPORTANT: When working with entry or asset references, you can use the '_path' parameters to specify which field's content to process. For example, if 'entry_reference' points to an entry, you can use 'entry_reference_path: \"fields.title.en-US\"' to process that entry's title field."
  }

  // Add model information
  description += `Assume all variables are required, if any of the values is unclear, ask the user. \n\nUses ${action.configuration.modelType} model with temperature ${action.configuration.modelTemperature}.`

  // Add note about result handling
  description +=
    "\n\n⚠️ Note: Results from this AI Action are NOT automatically applied to fields. The model will generate content based on your inputs, which you would then need to manually update in your Contentful entry."

  return description
}

/**
 * Generate a dynamic tool schema for an AI Action
 */
export function generateAiActionToolSchema(action: AiActionEntity) {
  // Create property definitions with friendly names
  const properties: Record<string, Record<string, unknown>> = {}

  // Store the ID mapping for this action
  const reverseMapping = createReverseMapping(action)
  const pathMappings = new Map<string, string>()
  idMappings.set(action.sys.id, reverseMapping)

  // Add properties for each variable with friendly names
  for (const variable of action.instruction.variables || []) {
    const friendlyName = getReadableName(variable)
    properties[friendlyName] = getEnhancedVariableSchema(variable)

    // For Reference and MediaReference types, add an entityPath parameter
    if (variable.type === "Reference" || variable.type === "MediaReference") {
      const pathParamName = `${friendlyName}_path`
      properties[pathParamName] = {
        type: "string",
        description: `Optional field path within the ${variable.type === "Reference" ? "entry" : "asset"} to process (e.g., "fields.title.en-US"). This specifies which field content to use as input.`,
      }

      // Add to path mappings for later use during invocation
      pathMappings.set(pathParamName, `${variable.id}_path`)
    }
  }

  // Store path mappings alongside ID mappings
  if (pathMappings.size > 0) {
    pathIdMappings.set(action.sys.id, pathMappings)
  }

  // Add common properties
  properties.outputFormat = {
    type: "string",
    enum: ["Markdown", "RichText", "PlainText"],
    default: "Markdown",
    description: "Format for the output content",
  }

  properties.waitForCompletion = {
    type: "boolean",
    default: true,
    description: "Whether to wait for the AI Action to complete",
  }

  // Get all variable names in their friendly format to make them all required
  const allVarNames = (action.instruction.variables || []).map((variable) => {
    return getReadableName(variable)
  })

  // Add outputFormat to required fields
  const requiredFields = [...allVarNames, "outputFormat"]

  const toolSchema = {
    name: `ai_action_${action.sys.id}`,
    description: getEnhancedToolDescription(action),
    inputSchema: getSpaceEnvProperties({
      type: "object",
      properties,
      required: requiredFields,
    }),
  }

  return toolSchema
}

/**
 * Check if a variable is optional
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function isOptionalVariable(_variable: Variable): boolean {
  // Always return false to make all variables required
  return false
}

/**
 * Map simple variable values from the tool input to the AI Action invocation format
 */
export function mapVariablesToInvocationFormat(
  action: AiActionEntity,
  toolInput: Record<string, unknown>,
): { variables: Array<{ id: string; value: unknown }>; outputFormat: OutputFormat } {
  const variables: Array<{ id: string; value: unknown }> = []
  const actionVariables = action.instruction.variables || []

  // Map each variable from the tool input to the AI Action invocation format
  for (const variable of actionVariables) {
    const value = toolInput[variable.id]

    // Skip undefined values for optional variables
    if (value === undefined && isOptionalVariable(variable)) {
      continue
    }

    // If value is undefined but variable is required, throw an error
    if (value === undefined && !isOptionalVariable(variable)) {
      throw new Error(`Required parameter ${variable.id} is missing from invocation call`)
    }

    // Format the value based on the variable type
    if (["Reference", "MediaReference", "ResourceLink"].includes(variable.type)) {
      // For reference types, create a complex value
      const complexValue: Record<string, string | Record<string, string>> = {
        entityType:
          variable.type === "Reference"
            ? "Entry"
            : variable.type === "MediaReference"
              ? "Asset"
              : "ResourceLink",
        entityId: value as string,
      }

      // Check if there's an entity path specified
      const pathKey = `${variable.id}_path`
      if (toolInput[pathKey]) {
        complexValue.entityPath = toolInput[pathKey] as string
      }

      variables.push({
        id: variable.id,
        value: complexValue,
      })
    } else {
      // For simple types, pass the value directly
      variables.push({
        id: variable.id,
        value: value,
      })
    }
  }

  // Get the output format (default to Markdown)
  const outputFormat = (toolInput.outputFormat as OutputFormat) || "Markdown"

  return { variables, outputFormat }
}

/**
 * Context for storing and managing dynamic AI Action tools
 */
export class AiActionToolContext {
  private spaceId: string
  private environmentId: string
  private aiActionCache: Map<string, AiActionEntity> = new Map()

  constructor(spaceId: string, environmentId: string = "master") {
    this.spaceId = spaceId
    this.environmentId = environmentId
  }

  /**
   * Add an AI Action to the cache
   */
  addAiAction(action: AiActionEntity): void {
    this.aiActionCache.set(action.sys.id, action)
  }

  /**
   * Get an AI Action from the cache
   */
  getAiAction(actionId: string): AiActionEntity | undefined {
    return this.aiActionCache.get(actionId)
  }

  /**
   * Remove an AI Action from the cache
   */
  removeAiAction(actionId: string): void {
    this.aiActionCache.delete(actionId)
  }

  /**
   * Get all AI Actions in the cache
   */
  getAllAiActions(): AiActionEntity[] {
    return Array.from(this.aiActionCache.values())
  }

  /**
   * Generate schemas for all AI Actions in the cache
   */
  generateAllToolSchemas(): ReturnType<typeof generateAiActionToolSchema>[] {
    return this.getAllAiActions().map((action) => generateAiActionToolSchema(action))
  }

  /**
   * Get the parameters needed for invoking an AI Action
   */
  getInvocationParams(
    actionId: string,
    toolInput: Record<string, unknown>,
  ): {
    spaceId: string
    environmentId: string
    aiActionId: string
    variables: Array<{ id: string; value: unknown }>
    outputFormat: OutputFormat
    waitForCompletion: boolean
  } {
    const action = this.getAiAction(actionId)
    if (!action) {
      throw new Error(`AI Action not found: ${actionId}`)
    }

    // Translate user-friendly parameter names to original variable IDs
    const translatedInput = this.translateParametersToVariableIds(actionId, toolInput)

    // Extract variables and outputFormat
    const { variables, outputFormat } = mapVariablesToInvocationFormat(action, translatedInput)

    const waitForCompletion = toolInput.waitForCompletion !== false

    // Use provided spaceId and environmentId if available, otherwise use defaults
    const spaceId = (toolInput.spaceId as string) || this.spaceId
    const environmentId = (toolInput.environmentId as string) || this.environmentId

    return {
      spaceId,
      environmentId,
      aiActionId: actionId,
      variables,
      outputFormat,
      waitForCompletion,
    }
  }

  /**
   * Translate friendly parameter names to original variable IDs
   */
  translateParametersToVariableIds(
    actionId: string,
    params: Record<string, unknown>,
  ): Record<string, unknown> {
    const idMapping = idMappings.get(actionId)
    const pathMapping = pathIdMappings.get(actionId)

    if (!idMapping && !pathMapping) {
      console.error(`No mappings found for action ${actionId}`)
      return params // No mappings found, return as is
    }

    const result: Record<string, unknown> = {}

    // Copy non-variable parameters directly
    for (const [key, value] of Object.entries(params)) {
      if (key === "outputFormat" || key === "waitForCompletion") {
        result[key] = value
        continue
      }

      // Check if this is a path parameter
      if (pathMapping && key.endsWith("_path")) {
        const originalPathId = pathMapping.get(key)
        if (originalPathId) {
          result[originalPathId] = value
          continue
        }
      }

      // Check if we have a variable ID mapping for this friendly name
      if (idMapping) {
        const originalId = idMapping.get(key)
        if (originalId) {
          result[originalId] = value
          continue
        }
      }

      // No mapping found, keep the original key
      result[key] = value
    }

    return result
  }

  /**
   * Clear the cache
   */
  clearCache(): void {
    this.aiActionCache.clear()
    // Also clear mappings when cache is cleared
    idMappings.clear()
    pathIdMappings.clear()
  }

  /**
   * Get the ID mappings for a specific action
   * (Useful for debugging)
   */
  getIdMappings(actionId: string): Map<string, string> | undefined {
    return idMappings.get(actionId)
  }

  /**
   * Get the path mappings for a specific action
   * (Useful for debugging)
   */
  getPathMappings(actionId: string): Map<string, string> | undefined {
    return pathIdMappings.get(actionId)
  }
}

```

--------------------------------------------------------------------------------
/src/transports/streamable-http.ts:
--------------------------------------------------------------------------------

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
import { getAllTools } from "../index.js"
import { AiActionToolContext } from "../utils/ai-action-tool-generator.js"
import { CONTENTFUL_PROMPTS } from "../prompts/contentful-prompts.js"
import { handlePrompt } from "../prompts/handlers.js"
import { randomUUID } from "crypto"
import express, { Request, Response } from "express"
import cors from "cors"
import {
  isInitializeRequest,
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
import { entryHandlers } from "../handlers/entry-handlers.js"
import { assetHandlers } from "../handlers/asset-handlers.js"
import { spaceHandlers } from "../handlers/space-handlers.js"
import { contentTypeHandlers } from "../handlers/content-type-handlers.js"
import { bulkActionHandlers } from "../handlers/bulk-action-handlers.js"
import { aiActionHandlers } from "../handlers/ai-action-handlers.js"
import type { AiActionInvocation } from "../types/ai-actions.js"

/**
 * Configuration options for the HTTP server
 */
export interface StreamableHttpServerOptions {
  port?: number
  host?: string
  corsOptions?: cors.CorsOptions
}

/**
 * Class to handle HTTP server setup and configuration using the official MCP StreamableHTTP transport
 */
export class StreamableHttpServer {
  private app: express.Application
  private server: any
  private port: number
  private host: string

  // Map to store transports by session ID
  private transports: Record<string, StreamableHTTPServerTransport> = {}

  /**
   * Create a new HTTP server for MCP over HTTP
   *
   * @param options Configuration options
   */
  constructor(options: StreamableHttpServerOptions = {}) {
    this.port = options.port || 3000
    this.host = options.host || "localhost"

    // Create Express app
    this.app = express()

    // Initialize AI Action tool context
    this.aiActionToolContext = new AiActionToolContext(
      process.env.SPACE_ID || "",
      process.env.ENVIRONMENT_ID || "master",
    )

    // Load AI Actions
    this.loadAiActions().catch(error => {
      console.error("Error loading AI Actions for StreamableHTTP server:", error)
    })

    // Configure CORS
    this.app.use(
      cors(
        options.corsOptions || {
          origin: "*",
          methods: ["GET", "POST", "DELETE"],
          allowedHeaders: ["Content-Type", "MCP-Session-ID"],
          exposedHeaders: ["MCP-Session-ID"],
        },
      ),
    )

    // Configure JSON body parsing
    this.app.use(express.json())

    // Set up routes
    this.setupRoutes()
  }

  /**
   * Set up the routes for MCP over HTTP
   */
  private setupRoutes(): void {
    // Handle all MCP requests (POST, GET, DELETE) on a single endpoint
    this.app.all("/mcp", async (req: Request, res: Response) => {
      try {
        if (req.method === "POST") {
          // Check for existing session ID
          const sessionId = req.headers["mcp-session-id"] as string | undefined
          let transport: StreamableHTTPServerTransport

          if (sessionId && this.transports[sessionId]) {
            // Reuse existing transport
            transport = this.transports[sessionId]
          } else if (!sessionId && isInitializeRequest(req.body)) {
            // Create a new server instance for this connection
            const server = new Server(
              {
                name: "contentful-mcp-server",
                version: "1.14.1",
              },
              {
                capabilities: {
                  tools: getAllTools(),
                  prompts: CONTENTFUL_PROMPTS,
                },
              },
            )

            // New initialization request
            transport = new StreamableHTTPServerTransport({
              sessionIdGenerator: () => randomUUID(),
              onsessioninitialized: (sid) => {
                // Store the transport by session ID
                this.transports[sid] = transport
              },
            })

            // Clean up transport when closed
            transport.onclose = () => {
              if (transport.sessionId) {
                delete this.transports[transport.sessionId]
                console.log(`Session ${transport.sessionId} closed`)
              }
            }

            // Set up request handlers
            this.setupServerHandlers(server)

            // Connect to the MCP server
            await server.connect(transport)
          } else {
            // Invalid request
            res.status(400).json({
              jsonrpc: "2.0",
              error: {
                code: -32000,
                message: "Bad Request: No valid session ID provided for non-initialize request",
              },
              id: null,
            })
            return
          }

          // Handle the request
          await transport.handleRequest(req, res, req.body)
        } else if (req.method === "GET") {
          // Server-sent events endpoint for notifications
          const sessionId = req.headers["mcp-session-id"] as string | undefined

          if (!sessionId || !this.transports[sessionId]) {
            res.status(400).send("Invalid or missing session ID")
            return
          }

          const transport = this.transports[sessionId]
          await transport.handleRequest(req, res)
        } else if (req.method === "DELETE") {
          // Session termination
          const sessionId = req.headers["mcp-session-id"] as string | undefined

          if (!sessionId || !this.transports[sessionId]) {
            res.status(400).send("Invalid or missing session ID")
            return
          }

          const transport = this.transports[sessionId]
          await transport.handleRequest(req, res)
        } else {
          // Other methods not supported
          res.status(405).send("Method not allowed")
        }
      } catch (error) {
        console.error("Error handling MCP request:", error)
        if (!res.headersSent) {
          res.status(500).json({
            jsonrpc: "2.0",
            error: {
              code: -32603,
              message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,
            },
            id: null,
          })
        }
      }
    })

    // Add a health check endpoint
    this.app.get("/health", (_req: Request, res: Response) => {
      res.status(200).json({
        status: "ok",
        sessions: Object.keys(this.transports).length,
      })
    })
  }

  /**
   * Set up the request handlers for a server instance
   *
   * @param server Server instance
   */
  private setupServerHandlers(server: Server): void {
    // List tools handler
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: Object.values(getAllTools()),
      }
    })

    // List prompts handler
    server.setRequestHandler(ListPromptsRequestSchema, async () => {
      return {
        prompts: Object.values(CONTENTFUL_PROMPTS),
      }
    })

    // Get prompt handler
    server.setRequestHandler(GetPromptRequestSchema, async (request) => {
      const { name, arguments: args } = request.params
      return handlePrompt(name, args)
    })

    // Call tool handler
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        const { name, arguments: args } = request.params
        const handler = this.getHandler(name)

        if (!handler) {
          throw new Error(`Unknown tool: ${name}`)
        }

        const result = await handler(args || {})

        // For AI Action responses, format them appropriately
        if (result && typeof result === "object") {
          // Check if this is an AI Action invocation result
          if (
            "sys" in result &&
            typeof result.sys === "object" &&
            result.sys &&
            "type" in result.sys &&
            result.sys.type === "AiActionInvocation"
          ) {
            const invocationResult = result as AiActionInvocation

            // Format AI Action result as text content if available
            if (invocationResult.result && invocationResult.result.content) {
              return {
                content: [
                  {
                    type: "text",
                    text:
                      typeof invocationResult.result.content === "string"
                        ? invocationResult.result.content
                        : JSON.stringify(invocationResult.result.content),
                  },
                ],
              }
            }
          }

          // Check for error response
          if ("isError" in result && result.isError === true) {
            // Format error response
            return {
              content: [
                {
                  type: "text",
                  text: "message" in result ? String(result.message) : "Unknown error",
                },
              ],
              isError: true,
            }
          }
        }

        // Return the result as is for regular handlers
        return result
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        }
      }
    })
  }

  // AI Action Tool Context for handling dynamic tools
  private aiActionToolContext: AiActionToolContext

  /**
   * Helper function to map tool names to handlers
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private getHandler(name: string): ((args: any) => Promise<any>) | undefined {
    // Check if this is a dynamic AI Action tool
    if (name.startsWith("ai_action_")) {
      const actionId = name.replace("ai_action_", "")
      return (args: Record<string, unknown>) => this.handleAiActionInvocation(actionId, args)
    }

    const handlers = {
      // Entry operations
      create_entry: entryHandlers.createEntry,
      get_entry: entryHandlers.getEntry,
      update_entry: entryHandlers.updateEntry,
      delete_entry: entryHandlers.deleteEntry,
      publish_entry: entryHandlers.publishEntry,
      unpublish_entry: entryHandlers.unpublishEntry,
      search_entries: entryHandlers.searchEntries,

      // Bulk operations
      bulk_publish: bulkActionHandlers.bulkPublish,
      bulk_unpublish: bulkActionHandlers.bulkUnpublish,
      bulk_validate: bulkActionHandlers.bulkValidate,

      // Asset operations
      upload_asset: assetHandlers.uploadAsset,
      get_asset: assetHandlers.getAsset,
      update_asset: assetHandlers.updateAsset,
      delete_asset: assetHandlers.deleteAsset,
      publish_asset: assetHandlers.publishAsset,
      unpublish_asset: assetHandlers.unpublishAsset,
      list_assets: assetHandlers.listAssets,

      // Space & Environment operations
      list_spaces: spaceHandlers.listSpaces,
      get_space: spaceHandlers.getSpace,
      list_environments: spaceHandlers.listEnvironments,
      create_environment: spaceHandlers.createEnvironment,
      delete_environment: spaceHandlers.deleteEnvironment,

      // Content Type operations
      list_content_types: contentTypeHandlers.listContentTypes,
      get_content_type: contentTypeHandlers.getContentType,
      create_content_type: contentTypeHandlers.createContentType,
      update_content_type: contentTypeHandlers.updateContentType,
      delete_content_type: contentTypeHandlers.deleteContentType,
      publish_content_type: contentTypeHandlers.publishContentType,

      // AI Action operations
      list_ai_actions: aiActionHandlers.listAiActions,
      get_ai_action: aiActionHandlers.getAiAction,
      create_ai_action: aiActionHandlers.createAiAction,
      update_ai_action: aiActionHandlers.updateAiAction,
      delete_ai_action: aiActionHandlers.deleteAiAction,
      publish_ai_action: aiActionHandlers.publishAiAction,
      unpublish_ai_action: aiActionHandlers.unpublishAiAction,
      invoke_ai_action: aiActionHandlers.invokeAiAction,
      get_ai_action_invocation: aiActionHandlers.getAiActionInvocation,
    }

    return handlers[name as keyof typeof handlers]
  }

  /**
   * Handler for dynamic AI Action tools
   */
  private async handleAiActionInvocation(actionId: string, args: Record<string, unknown>) {
    try {
      console.error(
        `Handling AI Action invocation for ${actionId} with args:`,
        JSON.stringify(args),
      )

      // Get the parameters using the getInvocationParams
      const params = this.aiActionToolContext.getInvocationParams(actionId, args)

      // Directly use the variables property from getInvocationParams
      const invocationParams = {
        spaceId: params.spaceId,
        environmentId: params.environmentId,
        aiActionId: params.aiActionId,
        outputFormat: params.outputFormat,
        waitForCompletion: params.waitForCompletion,
        // Use the correctly formatted variables array directly
        rawVariables: params.variables,
      }

      console.error(`Invoking AI Action with params:`, JSON.stringify(invocationParams))

      // Invoke the AI Action
      return aiActionHandlers.invokeAiAction(invocationParams)
    } catch (error) {
      console.error(`Error invoking AI Action:`, error)
      return {
        isError: true,
        message: error instanceof Error ? error.message : String(error),
      }
    }
  }

  /**
   * Load available AI Actions
   * This mimics the loadAiActions function in index.ts
   */
  private async loadAiActions(): Promise<void> {
    try {
      // First, clear the cache to avoid duplicates
      this.aiActionToolContext.clearCache()

      // Only load AI Actions if we have required space and environment
      if (!process.env.SPACE_ID) {
        return
      }

      // Fetch published AI Actions
      const response = await aiActionHandlers.listAiActions({
        spaceId: process.env.SPACE_ID,
        environmentId: process.env.ENVIRONMENT_ID || "master",
        status: "published",
      })

      // Check for errors or undefined response
      if (!response) {
        console.error("Error loading AI Actions for StreamableHTTP: No response received")
        return
      }

      if (typeof response === "object" && "isError" in response) {
        console.error(`Error loading AI Actions for StreamableHTTP: ${response.message}`)
        return
      }

      // Add each AI Action to the context
      for (const action of response.items) {
        this.aiActionToolContext.addAiAction(action)

        // Log variable mappings for debugging
        if (action.instruction.variables && action.instruction.variables.length > 0) {
          // Log ID mappings
          const idMappings = this.aiActionToolContext.getIdMappings(action.sys.id)
          if (idMappings && idMappings.size > 0) {
            const mappingLog = Array.from(idMappings.entries())
              .map(([friendly, original]) => `${friendly} -> ${original}`)
              .join(", ")
            console.error(`AI Action ${action.name} - Parameter mappings: ${mappingLog}`)
          }

          // Log path mappings
          const pathMappings = this.aiActionToolContext.getPathMappings(action.sys.id)
          if (pathMappings && pathMappings.size > 0) {
            const pathMappingLog = Array.from(pathMappings.entries())
              .map(([friendly, original]) => `${friendly} -> ${original}`)
              .join(", ")
            console.error(`AI Action ${action.name} - Path parameter mappings: ${pathMappingLog}`)
          }
        }
      }

      console.error(`Loaded ${response.items.length} AI Actions for StreamableHTTP`)
    } catch (error) {
      console.error("Error loading AI Actions for StreamableHTTP:", error)
    }
  }

  // Interval for refreshing AI Actions
  private aiActionsRefreshInterval?: NodeJS.Timeout

  /**
   * Start the HTTP server
   *
   * @returns Promise that resolves when the server is started
   */
  public async start(): Promise<void> {
    // Set up periodic refresh of AI Actions (every 5 minutes)
    this.aiActionsRefreshInterval = setInterval(() => {
      this.loadAiActions().catch(error => {
        console.error("Error refreshing AI Actions for StreamableHTTP:", error)
      })
    }, 5 * 60 * 1000)

    return new Promise((resolve) => {
      this.server = this.app.listen(this.port, () => {
        console.error(`MCP StreamableHTTP server running on http://${this.host}:${this.port}/mcp`)
        resolve()
      })
    })
  }

  /**
   * Stop the HTTP server
   *
   * @returns Promise that resolves when the server is stopped
   */
  public async stop(): Promise<void> {
    // Clear AI Actions refresh interval
    if (this.aiActionsRefreshInterval) {
      clearInterval(this.aiActionsRefreshInterval)
      this.aiActionsRefreshInterval = undefined
    }

    // Close all transports
    for (const sessionId in this.transports) {
      try {
        await this.transports[sessionId].close()
      } catch (error) {
        console.error(`Error closing session ${sessionId}:`, error)
      }
    }

    // Close the HTTP server
    if (this.server) {
      return new Promise((resolve, reject) => {
        this.server.close((err: Error) => {
          if (err) {
            reject(err)
          } else {
            resolve()
          }
        })
      })
    }
  }
}


```

--------------------------------------------------------------------------------
/test/msw-setup.ts:
--------------------------------------------------------------------------------

```typescript
import { setupServer } from "msw/node"
import { http, HttpResponse } from "msw"

// Mock data for bulk actions
const mockBulkAction = {
  sys: {
    id: "test-bulk-action-id",
    status: "succeeded",
    version: 1,
  },
  succeeded: [
    { sys: { id: "test-entry-id", type: "Entry" } },
    { sys: { id: "test-asset-id", type: "Asset" } },
  ],
}

// Define handlers
export const handlers = [
  // List spaces
  http.get("https://api.contentful.com/spaces", () => {
    return HttpResponse.json({
      items: [
        {
          sys: { id: "test-space-id" },
          name: "Test Space",
        },
      ],
    })
  }),

  // Get specific space
  http.get("https://api.contentful.com/spaces/:spaceId", ({ params }) => {
    const { spaceId } = params
    if (spaceId === "test-space-id") {
      return HttpResponse.json({
        sys: { id: "test-space-id" },
        name: "Test Space",
      })
    }
    return new HttpResponse(null, { status: 404 })
  }),

  // List environments
  http.get("https://api.contentful.com/spaces/:spaceId/environments", ({ params }) => {
    const { spaceId } = params
    if (spaceId === "test-space-id") {
      return HttpResponse.json({
        items: [
          {
            sys: { id: "master" },
            name: "master",
          },
        ],
      })
    }
    return new HttpResponse(null, { status: 404 })
  }),

  // Create environment
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments",
    async ({ params, request }) => {
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        try {
          // Get data from request body
          const body = await request.json()
          console.log("Request body:", JSON.stringify(body))

          // In the real API implementation, the environmentId is taken
          // from the second argument to client.environment.create
          // We need to extract it from the name in our mock
          const environmentId = body?.name

          // Return correctly structured response with environment ID
          return HttpResponse.json({
            sys: { id: environmentId },
            name: environmentId,
          })
        } catch (error) {
          console.error("Error processing environment creation:", error)
          return new HttpResponse(null, { status: 500 })
        }
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Delete environment
  http.delete("https://api.contentful.com/spaces/:spaceId/environments/:envId", ({ params }) => {
    const { spaceId, envId } = params
    if (spaceId === "test-space-id" && envId !== "non-existent-env") {
      return new HttpResponse(null, { status: 204 })
    }
    return new HttpResponse(null, { status: 404 })
  }),
]

// Bulk action handlers
const bulkActionHandlers = [
  // Create bulk publish action
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/bulk_actions/publish",
    async ({ params }) => {
      const { spaceId, environmentId } = params
      if (spaceId === "test-space-id") {
        return HttpResponse.json(
          {
            ...mockBulkAction,
            sys: {
              ...mockBulkAction.sys,
              id: "test-bulk-action-id",
              status: "created",
            },
          },
          { status: 201 },
        )
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Create bulk unpublish action
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/bulk_actions/unpublish",
    async ({ params }) => {
      const { spaceId, environmentId } = params
      if (spaceId === "test-space-id") {
        return HttpResponse.json(
          {
            ...mockBulkAction,
            sys: {
              ...mockBulkAction.sys,
              id: "test-bulk-action-id",
              status: "created",
            },
          },
          { status: 201 },
        )
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Create bulk validate action
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/bulk_actions/validate",
    async ({ params }) => {
      const { spaceId, environmentId } = params
      if (spaceId === "test-space-id") {
        return HttpResponse.json(
          {
            ...mockBulkAction,
            sys: {
              ...mockBulkAction.sys,
              id: "test-bulk-action-id",
              status: "created",
            },
          },
          { status: 201 },
        )
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Get bulk action status
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/bulk_actions/:bulkActionId",
    ({ params }) => {
      const { spaceId, bulkActionId } = params
      if (spaceId === "test-space-id" && bulkActionId === "test-bulk-action-id") {
        return HttpResponse.json(mockBulkAction)
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),
]

const assetHandlers = [
  // Upload asset
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets",
    async ({ request, params }) => {
      console.log("MSW: Handling asset creation request")
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        const body = (await request.json()) as {
          fields: {
            title: { "en-US": string }
            description?: { "en-US": string }
            file: {
              "en-US": {
                fileName: string
                contentType: string
                upload: string
              }
            }
          }
        }

        return HttpResponse.json({
          sys: {
            id: "test-asset-id",
            version: 1,
            type: "Asset",
          },
          fields: body.fields,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),
  // Process asset
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId/files/en-US/process",
    ({ params }) => {
      console.log("MSW: Handling asset processing request")
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return HttpResponse.json({
          sys: {
            id: "test-asset-id",
            version: 2,
            publishedVersion: 1,
          },
          fields: {
            title: { "en-US": "Test Asset" },
            description: { "en-US": "Test Description" },
            file: {
              "en-US": {
                fileName: "test.jpg",
                contentType: "image/jpeg",
                url: "https://example.com/test.jpg",
              },
            },
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Get asset
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId",
    ({ params }) => {
      console.log("MSW: Handling get processed asset request")
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return HttpResponse.json({
          sys: {
            id: "test-asset-id",
            version: 2,
            type: "Asset",
            publishedVersion: 1,
          },
          fields: {
            title: { "en-US": "Test Asset" },
            description: { "en-US": "Test Description" },
            file: {
              "en-US": {
                fileName: "test.jpg",
                contentType: "image/jpeg",
                url: "https://example.com/test.jpg",
              },
            },
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Update asset
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId",
    ({ params }) => {
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return HttpResponse.json({
          sys: { id: "test-asset-id" },
          fields: {
            title: { "en-US": "Updated Asset" },
            description: { "en-US": "Updated Description" },
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Delete asset
  http.delete(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId",
    ({ params }) => {
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return new HttpResponse(null, { status: 204 })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Publish asset
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId/published",
    ({ params }) => {
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return HttpResponse.json({
          sys: {
            id: "test-asset-id",
            publishedVersion: 1,
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Unpublish asset
  http.delete(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/assets/:assetId/published",
    ({ params }) => {
      const { spaceId, assetId } = params
      if (spaceId === "test-space-id" && assetId === "test-asset-id") {
        return HttpResponse.json({
          sys: {
            id: "test-asset-id",
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),
]

const entryHandlers = [
  // Search entries
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries",
    ({ params, request }) => {
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        return HttpResponse.json({
          items: [
            {
              sys: {
                id: "test-entry-id",
                contentType: { sys: { id: "test-content-type-id" } },
              },
              fields: {
                title: { "en-US": "Test Entry" },
                description: { "en-US": "Test Description" },
              },
            },
          ],
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Get specific entry
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries/:entryId",
    ({ params }) => {
      const { spaceId, entryId } = params
      if (spaceId === "test-space-id" && entryId === "test-entry-id") {
        return HttpResponse.json({
          sys: {
            id: "test-entry-id",
            contentType: { sys: { id: "test-content-type-id" } },
          },
          fields: {
            title: { "en-US": "Test Entry" },
            description: { "en-US": "Test Description" },
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Create entry
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries",
    async ({ params, request }) => {
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        const contentType = request.headers.get("X-Contentful-Content-Type")
        const body = (await request.json()) as {
          fields: Record<string, any>
        }

        return HttpResponse.json({
          sys: {
            id: "new-entry-id",
            type: "Entry",
            contentType: {
              sys: {
                type: "Link",
                linkType: "ContentType",
                id: contentType,
              },
            },
          },
          fields: body.fields,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Update entry
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries/:entryId",
    async ({ params, request }) => {
      const { spaceId, entryId } = params
      if (spaceId === "test-space-id" && entryId === "test-entry-id") {
        const body = (await request.json()) as {
          fields: Record<string, any>
        }
        return HttpResponse.json({
          sys: {
            id: entryId,
            contentType: { sys: { id: "test-content-type-id" } },
          },
          fields: body.fields,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Delete entry
  http.delete(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries/:entryId",
    ({ params }) => {
      const { spaceId, entryId } = params
      if (spaceId === "test-space-id" && entryId === "test-entry-id") {
        return new HttpResponse(null, { status: 204 })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Publish entry
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries/:entryId/published",
    ({ params }) => {
      const { spaceId, entryId } = params
      if (spaceId === "test-space-id" && entryId === "test-entry-id") {
        return HttpResponse.json({
          sys: {
            id: entryId,
            publishedVersion: 1,
          },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Unpublish entry
  http.delete(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/entries/:entryId/published",
    ({ params }) => {
      const { spaceId, entryId } = params
      if (spaceId === "test-space-id" && entryId === "test-entry-id") {
        return HttpResponse.json({
          sys: { id: entryId },
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),
]

const contentTypeHandlers = [
  // List content types
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types",
    ({ params }) => {
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        return HttpResponse.json({
          items: [
            {
              sys: { id: "test-content-type-id" },
              name: "Test Content Type",
              fields: [
                {
                  id: "title",
                  name: "Title",
                  type: "Text",
                  required: true,
                },
              ],
            },
          ],
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Get specific content type
  http.get(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types/:contentTypeId",
    ({ params }) => {
      const { spaceId, contentTypeId } = params
      if (spaceId === "test-space-id" && contentTypeId === "test-content-type-id") {
        return HttpResponse.json({
          sys: { id: "test-content-type-id" },
          name: "Test Content Type",
          fields: [
            {
              id: "title",
              name: "Title",
              type: "Text",
              required: true,
            },
          ],
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Create content type
  http.post(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types",
    async ({ params, request }) => {
      const { spaceId } = params
      if (spaceId === "test-space-id") {
        const body = (await request.json()) as {
          name: string
          fields: Array<{
            id: string
            name: string
            type: string
            required?: boolean
          }>
          description?: string
          displayField?: string
        }

        return HttpResponse.json({
          sys: { id: "new-content-type-id" },
          name: body.name,
          fields: body.fields,
          description: body.description,
          displayField: body.displayField,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // create content type with ID
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types/:contentTypeId",
    async ({ params, request }) => {
      const { spaceId, contentTypeId } = params
      if (spaceId === "test-space-id") {
        const body = (await request.json()) as {
          name: string
          fields: Array<{
            id: string
            name: string
            type: string
            required?: boolean
          }>
          description?: string
          displayField?: string
        }

        return HttpResponse.json({
          sys: { id: contentTypeId },
          name: body.name,
          fields: body.fields,
          description: body.description,
          displayField: body.displayField,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Publish content type
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types/:contentTypeId/published",
    ({ params }) => {
      const { spaceId, contentTypeId } = params
      if (spaceId === "test-space-id" && contentTypeId === "test-content-type-id") {
        return HttpResponse.json({
          sys: {
            id: contentTypeId,
            version: 1,
            publishedVersion: 1,
          },
          name: "Test Content Type",
          fields: [
            {
              id: "title",
              name: "Title",
              type: "Text",
              required: true,
            },
          ],
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Update content type
  http.put(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types/:contentTypeId",
    async ({ params, request }) => {
      const { spaceId, contentTypeId } = params
      if (spaceId === "test-space-id" && contentTypeId === "test-content-type-id") {
        const body = (await request.json()) as {
          name: string
          fields: Array<{
            id: string
            name: string
            type: string
            required?: boolean
          }>
          description?: string
          displayField?: string
        }

        return HttpResponse.json({
          sys: { id: contentTypeId },
          name: body.name,
          fields: body.fields,
          description: body.description,
          displayField: body.displayField,
        })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),

  // Delete content type
  http.delete(
    "https://api.contentful.com/spaces/:spaceId/environments/:environmentId/content_types/:contentTypeId",
    ({ params }) => {
      const { spaceId, contentTypeId } = params
      if (spaceId === "test-space-id" && contentTypeId === "test-content-type-id") {
        return new HttpResponse(null, { status: 204 })
      }
      return new HttpResponse(null, { status: 404 })
    },
  ),
]

// Setup MSW Server
export const server = setupServer(
  ...handlers,
  ...assetHandlers,
  ...contentTypeHandlers,
  ...entryHandlers,
  ...bulkActionHandlers,
)

```

--------------------------------------------------------------------------------
/test/integration/comment-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect, vi } from "vitest"
import { commentHandlers } from "../../src/handlers/comment-handlers.js"
import { server } from "../msw-setup.js"

// Mock comment data
const mockComment = {
  sys: {
    id: "test-comment-id",
    version: 1,
    createdAt: "2023-01-01T00:00:00Z",
    updatedAt: "2023-01-01T00:00:00Z",
    createdBy: {
      sys: { id: "test-user-id", type: "Link", linkType: "User" },
    },
    updatedBy: {
      sys: { id: "test-user-id", type: "Link", linkType: "User" },
    },
  },
  body: "This is a test comment",
  status: "active",
}

const mockRichTextComment = {
  sys: {
    id: "test-rich-comment-id",
    version: 1,
    createdAt: "2023-01-01T00:00:00Z",
    updatedAt: "2023-01-01T00:00:00Z",
    createdBy: {
      sys: { id: "test-user-id", type: "Link", linkType: "User" },
    },
    updatedBy: {
      sys: { id: "test-user-id", type: "Link", linkType: "User" },
    },
  },
  body: {
    nodeType: "document",
    content: [
      {
        nodeType: "paragraph",
        content: [
          {
            nodeType: "text",
            value: "This is a rich text comment",
            marks: [],
          },
        ],
      },
    ],
  },
  status: "active",
}

const mockCommentsCollection = {
  sys: { type: "Array" },
  total: 2,
  skip: 0,
  limit: 100,
  items: [mockComment, mockRichTextComment],
}

// Mock Contentful client comment methods
const mockCommentGetMany = vi.fn().mockResolvedValue(mockCommentsCollection)
const mockCommentCreate = vi.fn().mockResolvedValue(mockComment)
const mockCommentGet = vi.fn().mockResolvedValue(mockComment)
const mockCommentDelete = vi.fn().mockResolvedValue(undefined)
const mockCommentUpdate = vi.fn().mockResolvedValue(mockComment)

// Mock the contentful client for testing comment operations
vi.mock("../../src/config/client.js", async (importOriginal) => {
  const originalModule = (await importOriginal()) as any

  // Create a mock function that will be used for the content client
  const getContentfulClient = vi.fn()

  // Store the original function so we can call it if needed
  const originalGetClient = originalModule.getContentfulClient

  // Set up the mock function to return either the original or our mocked version
  getContentfulClient.mockImplementation(async () => {
    // Create our mock client
    const mockClient = {
      comment: {
        getMany: mockCommentGetMany,
        create: mockCommentCreate,
        get: mockCommentGet,
        delete: mockCommentDelete,
        update: mockCommentUpdate,
      },
      // Pass through other methods to the original client
      entry: {
        get: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.get(...args)),
        getMany: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.getMany(...args)),
        create: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.create(...args)),
        update: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.update(...args)),
        delete: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.delete(...args)),
        publish: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.publish(...args)),
        unpublish: (...args: any[]) =>
          originalGetClient().then((client: any) => client.entry.unpublish(...args)),
      },
    }

    return mockClient
  })

  return {
    ...originalModule,
    getContentfulClient,
  }
})

describe("Comment Handlers Integration Tests", () => {
  // Start MSW Server before tests
  beforeAll(() => {
    server.listen()
    // Ensure environment variables are not set
    delete process.env.SPACE_ID
    delete process.env.ENVIRONMENT_ID
  })
  afterEach(() => {
    server.resetHandlers()
    vi.clearAllMocks()
  })
  afterAll(() => server.close())

  const testSpaceId = "test-space-id"
  const testEnvironmentId = "master"
  const testEntryId = "test-entry-id"
  const testCommentId = "test-comment-id"

  describe("getComments", () => {
    it("should retrieve comments for an entry with default parameters", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
      })

      expect(mockCommentGetMany).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        bodyFormat: "plain-text",
        query: { status: "active" },
      })

      expect(result).to.have.property("content").that.is.an("array")
      expect(result.content).to.have.lengthOf(1)

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.items).to.be.an("array")
      expect(responseData.total).to.equal(2)
      expect(responseData.showing).to.equal(2)
      expect(responseData.remaining).to.equal(0)
    })

    it("should retrieve comments with rich-text body format", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        bodyFormat: "rich-text",
      })

      expect(mockCommentGetMany).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        bodyFormat: "rich-text",
        query: { status: "active" },
      })

      expect(result).to.have.property("content")

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.items).to.be.an("array")
    })

    it("should retrieve comments with status filter", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        status: "resolved",
      })

      expect(mockCommentGetMany).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        bodyFormat: "plain-text",
        query: { status: "resolved" },
      })

      expect(result).to.have.property("content")
    })

    it("should retrieve all comments when status is 'all'", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        status: "all",
      })

      expect(mockCommentGetMany).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        bodyFormat: "plain-text",
        query: {},
      })

      expect(result).to.have.property("content")
    })

    it("should use environment variables when provided", async () => {
      // Set environment variables
      const originalSpaceId = process.env.SPACE_ID
      const originalEnvironmentId = process.env.ENVIRONMENT_ID
      process.env.SPACE_ID = "env-space-id"
      process.env.ENVIRONMENT_ID = "env-environment-id"

      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
      })

      expect(mockCommentGetMany).toHaveBeenCalledWith({
        spaceId: "env-space-id",
        environmentId: "env-environment-id",
        entryId: testEntryId,
        bodyFormat: "plain-text",
        query: { status: "active" },
      })

      // Restore environment variables
      process.env.SPACE_ID = originalSpaceId
      process.env.ENVIRONMENT_ID = originalEnvironmentId

      expect(result).to.have.property("content")
    })

    it("should handle pagination with limit parameter", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        limit: 1,
      })

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.items).to.have.lengthOf(1)
      expect(responseData.total).to.equal(2)
      expect(responseData.showing).to.equal(1)
      expect(responseData.remaining).to.equal(1)
      expect(responseData.skip).to.equal(1)
      expect(responseData.message).to.include("skip parameter")
    })

    it("should handle pagination with skip parameter", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        limit: 1,
        skip: 1,
      })

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.items).to.have.lengthOf(1)
      expect(responseData.total).to.equal(2)
      expect(responseData.showing).to.equal(1)
      expect(responseData.remaining).to.equal(0)
      expect(responseData.skip).to.be.undefined
      expect(responseData.message).to.be.undefined
    })

    it("should handle limit larger than available items", async () => {
      const result = await commentHandlers.getComments({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        limit: 10,
      })

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.items).to.have.lengthOf(2)
      expect(responseData.total).to.equal(2)
      expect(responseData.showing).to.equal(2)
      expect(responseData.remaining).to.equal(0)
      expect(responseData.skip).to.be.undefined
      expect(responseData.message).to.be.undefined
    })
  })

  describe("createComment", () => {
    it("should create a plain-text comment with default parameters", async () => {
      const testBody = "This is a test comment"

      const result = await commentHandlers.createComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        body: testBody,
      })

      expect(mockCommentCreate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
        },
        {
          body: testBody,
          status: "active",
        },
      )

      expect(result).to.have.property("content").that.is.an("array")
      expect(result.content).to.have.lengthOf(1)

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.sys.id).to.equal("test-comment-id")
      expect(responseData.body).to.equal("This is a test comment")
      expect(responseData.status).to.equal("active")
    })

    it("should create a rich-text comment", async () => {
      const testBody = "This is a rich text comment"

      mockCommentCreate.mockResolvedValueOnce(mockRichTextComment)

      const result = await commentHandlers.createComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        body: testBody,
      })

      expect(mockCommentCreate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
        },
        {
          body: testBody,
          status: "active",
        },
      )

      expect(result).to.have.property("content")

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.sys.id).to.equal("test-rich-comment-id")
    })

    it("should create a comment with custom status", async () => {
      const testBody = "This is a test comment"

      const result = await commentHandlers.createComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        body: testBody,
        status: "active",
      })

      expect(mockCommentCreate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
        },
        {
          body: testBody,
          status: "active",
        },
      )

      expect(result).to.have.property("content")
    })

    it("should use environment variables when provided", async () => {
      // Set environment variables
      const originalSpaceId = process.env.SPACE_ID
      const originalEnvironmentId = process.env.ENVIRONMENT_ID
      process.env.SPACE_ID = "env-space-id"
      process.env.ENVIRONMENT_ID = "env-environment-id"

      const testBody = "This is a test comment"

      const result = await commentHandlers.createComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        body: testBody,
      })

      expect(mockCommentCreate).toHaveBeenCalledWith(
        {
          spaceId: "env-space-id",
          environmentId: "env-environment-id",
          entryId: testEntryId,
        },
        {
          body: testBody,
          status: "active",
        },
      )

      // Restore environment variables
      process.env.SPACE_ID = originalSpaceId
      process.env.ENVIRONMENT_ID = originalEnvironmentId

      expect(result).to.have.property("content")
    })
  })

  describe("getSingleComment", () => {
    it("should retrieve a specific comment with default parameters", async () => {
      const result = await commentHandlers.getSingleComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
      })

      expect(mockCommentGet).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        bodyFormat: "plain-text",
      })

      expect(result).to.have.property("content").that.is.an("array")
      expect(result.content).to.have.lengthOf(1)

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.sys.id).to.equal("test-comment-id")
      expect(responseData.body).to.equal("This is a test comment")
      expect(responseData.status).to.equal("active")
    })

    it("should retrieve a specific comment with rich-text body format", async () => {
      mockCommentGet.mockResolvedValueOnce(mockRichTextComment)

      const result = await commentHandlers.getSingleComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        bodyFormat: "rich-text",
      })

      expect(mockCommentGet).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        bodyFormat: "rich-text",
      })

      expect(result).to.have.property("content")

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.body).to.have.property("nodeType", "document")
    })

    it("should use environment variables when provided", async () => {
      // Set environment variables
      const originalSpaceId = process.env.SPACE_ID
      const originalEnvironmentId = process.env.ENVIRONMENT_ID
      process.env.SPACE_ID = "env-space-id"
      process.env.ENVIRONMENT_ID = "env-environment-id"

      const result = await commentHandlers.getSingleComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
      })

      expect(mockCommentGet).toHaveBeenCalledWith({
        spaceId: "env-space-id",
        environmentId: "env-environment-id",
        entryId: testEntryId,
        commentId: testCommentId,
        bodyFormat: "plain-text",
      })

      // Restore environment variables
      process.env.SPACE_ID = originalSpaceId
      process.env.ENVIRONMENT_ID = originalEnvironmentId

      expect(result).to.have.property("content")
    })

    it("should handle errors gracefully", async () => {
      mockCommentGet.mockRejectedValueOnce(new Error("Comment not found"))

      try {
        await commentHandlers.getSingleComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: "invalid-comment-id",
        })
        expect.fail("Should have thrown an error")
      } catch (error: any) {
        expect(error).to.exist
        expect(error.message).to.equal("Comment not found")
      }
    })
  })

  describe("deleteComment", () => {
    it("should delete a specific comment", async () => {
      const result = await commentHandlers.deleteComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
      })

      expect(mockCommentDelete).toHaveBeenCalledWith({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        version: 1,
      })

      expect(result).to.have.property("content").that.is.an("array")
      expect(result.content).to.have.lengthOf(1)
      expect(result.content[0].text).to.include(
        `Successfully deleted comment ${testCommentId} from entry ${testEntryId}`,
      )
    })

    it("should use environment variables when provided", async () => {
      // Set environment variables
      const originalSpaceId = process.env.SPACE_ID
      const originalEnvironmentId = process.env.ENVIRONMENT_ID
      process.env.SPACE_ID = "env-space-id"
      process.env.ENVIRONMENT_ID = "env-environment-id"

      const result = await commentHandlers.deleteComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
      })

      expect(mockCommentDelete).toHaveBeenCalledWith({
        spaceId: "env-space-id",
        environmentId: "env-environment-id",
        entryId: testEntryId,
        commentId: testCommentId,
        version: 1,
      })

      // Restore environment variables
      process.env.SPACE_ID = originalSpaceId
      process.env.ENVIRONMENT_ID = originalEnvironmentId

      expect(result).to.have.property("content")
    })

    it("should handle errors gracefully", async () => {
      mockCommentDelete.mockRejectedValueOnce(new Error("Delete failed"))

      try {
        await commentHandlers.deleteComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: "invalid-comment-id",
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("Delete failed")
      }
    })
  })

  describe("updateComment", () => {
    it("should update a comment with plain-text format", async () => {
      const testBody = "Updated comment body"
      const testStatus = "resolved"

      const result = await commentHandlers.updateComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        body: testBody,
        status: testStatus,
      })

      expect(mockCommentUpdate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
        },
        {
          body: testBody,
          status: testStatus,
          version: 1,
        },
      )

      expect(result).to.have.property("content").that.is.an("array")
      expect(result.content).to.have.lengthOf(1)

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.sys.id).to.equal("test-comment-id")
    })

    it("should update a comment with rich-text format", async () => {
      const testBody = "Updated rich text comment"

      mockCommentUpdate.mockResolvedValueOnce(mockRichTextComment)

      const result = await commentHandlers.updateComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        body: testBody,
        bodyFormat: "rich-text",
      })

      expect(mockCommentUpdate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
        },
        {
          body: testBody,
          version: 1,
        },
      )

      expect(result).to.have.property("content")

      const responseData = JSON.parse(result.content[0].text)
      expect(responseData.sys.id).to.equal("test-rich-comment-id")
    })

    it("should update only body when status is not provided", async () => {
      const testBody = "Updated comment body only"

      const result = await commentHandlers.updateComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        body: testBody,
      })

      expect(mockCommentUpdate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
        },
        {
          body: testBody,
          version: 1,
        },
      )

      expect(result).to.have.property("content")
    })

    it("should update only status when body is not provided", async () => {
      const testStatus = "resolved"

      const result = await commentHandlers.updateComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        status: testStatus,
      })

      expect(mockCommentUpdate).toHaveBeenCalledWith(
        {
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
        },
        {
          status: testStatus,
          version: 1,
        },
      )

      expect(result).to.have.property("content")
    })

    it("should use environment variables when provided", async () => {
      // Set environment variables
      const originalSpaceId = process.env.SPACE_ID
      const originalEnvironmentId = process.env.ENVIRONMENT_ID
      process.env.SPACE_ID = "env-space-id"
      process.env.ENVIRONMENT_ID = "env-environment-id"

      const testBody = "Updated comment"

      const result = await commentHandlers.updateComment({
        spaceId: testSpaceId,
        environmentId: testEnvironmentId,
        entryId: testEntryId,
        commentId: testCommentId,
        body: testBody,
      })

      expect(mockCommentUpdate).toHaveBeenCalledWith(
        {
          spaceId: "env-space-id",
          environmentId: "env-environment-id",
          entryId: testEntryId,
          commentId: testCommentId,
        },
        {
          body: testBody,
          version: 1,
        },
      )

      // Restore environment variables
      process.env.SPACE_ID = originalSpaceId
      process.env.ENVIRONMENT_ID = originalEnvironmentId

      expect(result).to.have.property("content")
    })

    it("should handle errors gracefully", async () => {
      mockCommentUpdate.mockRejectedValueOnce(new Error("Update failed"))

      try {
        await commentHandlers.updateComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: "invalid-comment-id",
          body: "Test body",
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("Update failed")
      }
    })
  })

  describe("Error handling", () => {
    it("should handle getComments API errors", async () => {
      mockCommentGetMany.mockRejectedValueOnce(new Error("API Error"))

      try {
        await commentHandlers.getComments({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("API Error")
      }
    })

    it("should handle createComment API errors", async () => {
      mockCommentCreate.mockRejectedValueOnce(new Error("Create failed"))

      try {
        await commentHandlers.createComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          body: "Test comment",
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("Create failed")
      }
    })

    it("should handle deleteComment API errors", async () => {
      mockCommentDelete.mockRejectedValueOnce(new Error("Delete failed"))

      try {
        await commentHandlers.deleteComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("Delete failed")
      }
    })

    it("should handle updateComment API errors", async () => {
      mockCommentUpdate.mockRejectedValueOnce(new Error("Update failed"))

      try {
        await commentHandlers.updateComment({
          spaceId: testSpaceId,
          environmentId: testEnvironmentId,
          entryId: testEntryId,
          commentId: testCommentId,
          body: "Test body",
        })
        expect.fail("Should have thrown an error")
      } catch (error) {
        expect(error).to.exist
        expect(error.message).to.equal("Update failed")
      }
    })
  })
})

```

--------------------------------------------------------------------------------
/src/prompts/generateVariableTypeContent.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Generate detailed content for AI Action variable types
 * @param variableType Specific variable type to generate content for
 * @returns Detailed explanation of the variable type(s)
 */
export function generateVariableTypeContent(variableType?: string): string {
  // If no specific type is requested, return an overview of all types
  if (!variableType) {
    return `AI Actions in Contentful use variables to make templates dynamic and adaptable. These variables serve as placeholders in your prompt templates that get replaced with actual values when an AI Action is invoked.

## Available Variable Types

### 1. StandardInput

The primary input variable for text content. Ideal for the main content that needs processing.

- **Use case**: Processing existing content, like rewriting or improving text
- **Template usage**: {{input_text}}
- **MCP parameter**: Usually exposed as "input_text"
- **Best for**: Main content field being operated on

### 2. Text

Simple text variables for additional information or context.

- **Use case**: Adding supplementary information to guide the AI
- **Template usage**: {{context}}, {{guidelines}}
- **Configuration**: Can be strict (limit to specific values) or free-form
- **Best for**: Providing context, instructions, or metadata

### 3. FreeFormInput

Unstructured text input with minimal constraints.

- **Use case**: Open-ended user input where flexibility is needed
- **Template usage**: {{user_instructions}}
- **Best for**: Custom requests or specific directions

### 4. StringOptionsList

Predefined options presented as a dropdown menu.

- **Use case**: Selecting from a fixed set of choices (tone, style, etc.)
- **Template usage**: {{tone}}, {{style}}
- **Configuration**: Requires defining the list of available options
- **Best for**: Consistent, controlled parameters like tones or formatting options

### 5. Reference

Links to other Contentful entries.

- **Use case**: Using content from other entries as context
- **Template usage**: Content accessed via helpers: "{{reference.fields.title}}"
- **Configuration**: Can restrict to specific content types
- **Special properties**: When using in MCP, requires "*_path" parameter to specify field path
- **Best for**: Cross-referencing content for context or expansion

### 6. MediaReference

Links to media assets like images, videos.

- **Use case**: Generating descriptions from media, processing asset metadata
- **Configuration**: Points to a specific asset in your space
- **Special properties**: When using in MCP, requires "*_path" parameter to specify which part to process
- **Best for**: Image description, alt text generation, media analysis

### 7. Locale

Specifies the language or region for content.

- **Use case**: Translation, localization, region-specific content
- **Template usage**: {{locale}}
- **Format**: Language codes like "en-US", "de-DE"
- **Best for**: Multi-language operations or locale-specific formatting

## Best Practices for Variables

1. **Use descriptive names**: Make variable names intuitive ({{product_name}} not {{var1}})
2. **Provide clear descriptions**: Help users understand what each variable does
3. **Use appropriate types**: Match variable types to their purpose
4. **Set sensible defaults**: Pre-populate where possible to guide users
5. **Consider field paths**: For Reference and MediaReference variables, remember that users need to specify which field to access

## Template Integration

Variables are referenced in templates using double curly braces: {{variable_id}}

Example template with multiple variable types:

\`\`\`
You are a content specialist helping improve product descriptions.

TONE: {{tone}}

AUDIENCE: Customers interested in {{target_market}}

ORIGINAL CONTENT:
{{input_text}}

INSTRUCTIONS:
Rewrite the above product description to be more engaging, maintaining the key product details but optimizing for {{tone}} tone and the {{target_market}} market.

IMPROVED DESCRIPTION:
\`\`\`

## Working with References

When working with References and MediaReferences, remember that the content must be accessed via the correct path. In the MCP integration, this is handled through separate parameters (e.g., "reference" and "reference_path").

## Variable Validation

AI Actions validate variables at runtime to ensure they meet requirements. Configure validation rules appropriately to prevent errors during invocation.`;
  }

  // Generate content for specific variable types
  switch (variableType.toLowerCase()) {
    case "standardinput":
    case "standard input":
      return `## StandardInput Variable Type

The StandardInput is the primary input variable for text content in AI Actions. It's designed to handle the main content that needs to be processed by the AI model.

### Purpose

This variable type is typically used for:
- The primary text to be transformed
- Existing content that needs enhancement, rewriting, or analysis
- The core content that the AI Action will operate on

### Configuration

StandardInput has minimal configuration needs:

- **ID**: A unique identifier (e.g., "main_content")
- **Name**: User-friendly label (e.g., "Main Content")
- **Description**: Clear explanation (e.g., "The content to be processed")

No additional configuration properties are required for this type.

### In MCP Integration

When working with the MCP integration, StandardInput variables are typically exposed with the parameter name "input_text" for consistency and clarity.

### Template Usage

In your AI Action template, reference StandardInput variables using double curly braces:

\`\`\`
ORIGINAL CONTENT:
{{input_text}}

Please improve the above content by...
\`\`\`

### Examples

**Example 1: Content Enhancement**

\`\`\`
You are a content specialist.

ORIGINAL CONTENT:
{{input_text}}

Enhance the above content by improving clarity, fixing grammar, and making it more engaging while preserving the key information.

IMPROVED CONTENT:
\`\`\`

**Example 2: SEO Optimization**

\`\`\`
You are an SEO expert.

ORIGINAL CONTENT:
{{input_text}}

KEYWORDS: {{keywords}}

Rewrite the above content to optimize for SEO using the provided keywords. Maintain the core message but improve readability and keyword usage.

SEO-OPTIMIZED CONTENT:
\`\`\`

### Best Practices

1. **Clear instructions**: Always include clear directions about what to do with the input text
2. **Context setting**: Provide context about what the input represents (e.g., product description, blog post)
3. **Output expectations**: Clearly indicate what the expected output format should be
4. **Complementary variables**: Pair StandardInput with other variables that provide direction (tone, style, keywords)

### Implementation with MCP Tools

When creating an AI Action with StandardInput using MCP tools:

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "You are helping improve content...\\n\\nORIGINAL CONTENT:\\n{{input_text}}\\n\\nIMPROVED CONTENT:",
    variables: [
      {
        id: "input_text",
        type: "StandardInput",
        name: "Input Content",
        description: "The content to be improved"
      }
      // other variables...
    ]
  }
});
\`\`\``;

    case "text":
      return `## Text Variable Type

The Text variable type provides a simple way to collect text input in AI Actions. It's more flexible than StringOptionsList but can include validation constraints if needed.

### Purpose

Text variables are used for:
- Supplementary information to guide the AI
- Additional context that affects output
- Simple inputs that don't require the full flexibility of FreeFormInput

### Configuration

Text variables can be configured with these properties:

- **ID**: Unique identifier (e.g., "brand_guidelines")
- **Name**: User-friendly label (e.g., "Brand Guidelines")
- **Description**: Explanation of what to input
- **Configuration** (optional):
  - **strict**: Boolean indicating whether values are restricted
  - **in**: Array of allowed values if strict is true

### Template Usage

Reference Text variables in templates using double curly braces:

\`\`\`
BRAND GUIDELINES: {{brand_guidelines}}

CONTENT:
{{input_text}}

Please rewrite the above content following the brand guidelines provided.
\`\`\`

### Examples

**Example 1: Simple Text Variable**

\`\`\`javascript
{
  id: "customer_segment",
  type: "Text",
  name: "Customer Segment",
  description: "The target customer segment for this content"
}
\`\`\`

**Example 2: Text Variable with Validation**

\`\`\`javascript
{
  id: "priority_level",
  type: "Text",
  name: "Priority Level",
  description: "The priority level for this task",
  configuration: {
    strict: true,
    in: ["High", "Medium", "Low"]
  }
}
\`\`\`

### Best Practices

1. **Clarify expectations**: Provide clear descriptions about what information is expected
2. **Use validation when appropriate**: If only certain values are valid, use the strict configuration
3. **Consider using StringOptionsList**: If you have a fixed set of options, StringOptionsList may be more appropriate
4. **Keep it focused**: Ask for specific information rather than general input

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Create content with customer segment {{customer_segment}} in mind...",
    variables: [
      {
        id: "customer_segment",
        type: "Text",
        name: "Customer Segment",
        description: "The target customer segment for this content"
      }
      // other variables...
    ]
  }
});
\`\`\``;

    case "freeforminput":
    case "free form input":
      return `## FreeFormInput Variable Type

The FreeFormInput variable type provides the most flexibility for collecting user input in AI Actions. It's designed for open-ended text entry with minimal constraints.

### Purpose

FreeFormInput variables are ideal for:
- Custom instructions from users
- Specific guidance that can't be predetermined
- Open-ended information that requires flexibility

### Configuration

FreeFormInput has minimal configuration requirements:

- **ID**: Unique identifier (e.g., "special_instructions")
- **Name**: User-friendly label (e.g., "Special Instructions")
- **Description**: Clear guidance on what kind of input is expected

No additional configuration properties are typically needed.

### Template Usage

Reference FreeFormInput variables in templates with double curly braces:

\`\`\`
CONTENT:
{{input_text}}

SPECIAL INSTRUCTIONS:
{{special_instructions}}

Please modify the content above according to the special instructions provided.
\`\`\`

### Examples

**Example: Content Creation Guidance**

\`\`\`javascript
{
  id: "author_preferences",
  type: "FreeFormInput",
  name: "Author Preferences",
  description: "Any specific preferences or requirements from the author that should be considered"
}
\`\`\`

### Best Practices

1. **Provide guidance**: Even though it's free-form, give users clear guidance about what kind of input is helpful
2. **Set expectations**: Explain how the input will be used in the AI Action
3. **Use sparingly**: Too many free-form inputs can make AI Actions confusing - use only where flexibility is needed
4. **Position appropriately**: Place FreeFormInput variables where they make most sense in your template flow

### When to Use FreeFormInput vs. Text

- Use **FreeFormInput** when you need completely open-ended input without restrictions
- Use **Text** when you want simple input that might benefit from validation

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Generate content based on these specifications...\\n\\nSPECIAL REQUIREMENTS:\\n{{special_requirements}}",
    variables: [
      {
        id: "special_requirements",
        type: "FreeFormInput",
        name: "Special Requirements",
        description: "Any special requirements or preferences for the generated content"
      }
      // other variables...
    ]
  }
});
\`\`\``;

    case "stringoptionslist":
    case "string options list":
      return `## StringOptionsList Variable Type

The StringOptionsList variable type provides a dropdown menu of predefined options. It's ideal for scenarios where users should select from a fixed set of choices.

### Purpose

StringOptionsList variables are perfect for:
- Tone selection (formal, casual, etc.)
- Content categories or types
- Predefined styles or formats
- Any parameter with a limited set of valid options

### Configuration

StringOptionsList requires these configuration properties:

- **ID**: Unique identifier (e.g., "tone")
- **Name**: User-friendly label (e.g., "Content Tone")
- **Description**: Explanation of what the options represent
- **Configuration** (required):
  - **values**: Array of string options to display
  - **allowFreeFormInput** (optional): Boolean indicating if custom values are allowed

### Template Usage

Reference StringOptionsList variables in templates using double curly braces:

\`\`\`
TONE: {{tone}}

CONTENT:
{{input_text}}

Please rewrite the above content using a {{tone}} tone.
\`\`\`

### Examples

**Example 1: Tone Selection**

\`\`\`javascript
{
  id: "tone",
  type: "StringOptionsList",
  name: "Content Tone",
  description: "The tone to use for the content",
  configuration: {
    values: ["Formal", "Professional", "Casual", "Friendly", "Humorous"],
    allowFreeFormInput: false
  }
}
\`\`\`

**Example 2: Content Format with Custom Option**

\`\`\`javascript
{
  id: "format",
  type: "StringOptionsList",
  name: "Content Format",
  description: "The format for the generated content",
  configuration: {
    values: ["Blog Post", "Social Media", "Email", "Product Description", "Press Release"],
    allowFreeFormInput: true
  }
}
\`\`\`

### Best Practices

1. **Limit options**: Keep the list reasonably short (typically 3-7 options)
2. **Use clear labels**: Make option names self-explanatory
3. **Order logically**: Arrange options in a logical order (alphabetical, frequency, etc.)
4. **Consider defaults**: Place commonly used options earlier in the list
5. **Use allowFreeFormInput sparingly**: Only enable when custom options are truly needed

### In MCP Integration

In the MCP implementation, StringOptionsList variables are presented as enum parameters with the predefined options as choices.

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Generate a {{content_type}} about {{topic}}...",
    variables: [
      {
        id: "content_type",
        type: "StringOptionsList",
        name: "Content Type",
        description: "The type of content to generate",
        configuration: {
          values: ["Blog Post", "Social Media Post", "Newsletter", "Product Description"],
          allowFreeFormInput: false
        }
      },
      {
        id: "topic",
        type: "Text",
        name: "Topic",
        description: "The topic for the content"
      }
      // other variables...
    ]
  }
});
\`\`\``;

    case "reference":
      return `## Reference Variable Type

The Reference variable type allows AI Actions to access content from other entries in your Contentful space, creating powerful content relationships and context-aware operations.

### Purpose

Reference variables are used for:
- Accessing content from related entries
- Processing entry data for context or analysis
- Creating content based on existing entries
- Cross-referencing information across multiple content types

### Configuration

Reference variables require these properties:

- **ID**: Unique identifier (e.g., "product_entry")
- **Name**: User-friendly label (e.g., "Product Entry")
- **Description**: Explanation of what entry to reference
- **Configuration** (optional):
  - **allowedEntities**: Array of entity types that can be referenced (typically ["Entry"])

### Field Path Specification

When using References in MCP, you must provide both:
1. The entry ID (which entry to reference)
2. The field path (which field within that entry to use)

This is handled through two parameters:
- **reference**: The entry ID to reference
- **reference_path**: The path to the field (e.g., "fields.description.en-US")

### Template Usage

In templates, you can access referenced entry fields using helpers or direct field access:

\`\`\`
PRODUCT NAME: {{product_entry.fields.name}}

CURRENT DESCRIPTION:
{{product_entry.fields.description}}

Please generate an improved product description that highlights the key features while maintaining brand voice.
\`\`\`

### Examples

**Example: Product Description Generator**

\`\`\`javascript
{
  id: "product_entry",
  type: "Reference",
  name: "Product Entry",
  description: "The product entry to generate content for",
  configuration: {
    allowedEntities: ["Entry"]
  }
}
\`\`\`

### Best Practices

1. **Clear field paths**: Always specify exactly which field to use from the referenced entry
2. **Provide context**: Explain which content type or entry type should be referenced
3. **Consider localization**: Remember that fields may be localized, so paths typically include locale code
4. **Check existence**: Handle cases where referenced fields might be empty
5. **Document requirements**: Clearly explain which entry types are valid for the reference

### MCP Implementation Notes

When using Reference variables with the MCP server:

1. The dynamic tool will include two parameters for each Reference:
   - The reference ID parameter (e.g., "product_entry")
   - The path parameter (e.g., "product_entry_path")

2. Always specify both when invoking the AI Action:
   \`\`\`javascript
   invoke_ai_action_product_description({
     product_entry: "6tFnSQdgHuWYOk8eICA0w",
     product_entry_path: "fields.description.en-US"
   });
   \`\`\`

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Generate SEO metadata for this product...\\n\\nPRODUCT: {{product.fields.title}}\\n\\nDESCRIPTION: {{product.fields.description}}",
    variables: [
      {
        id: "product",
        type: "Reference",
        name: "Product Entry",
        description: "The product entry to create metadata for",
        configuration: {
          allowedEntities: ["Entry"]
        }
      }
      // other variables...
    ]
  }
});
\`\`\`

When invoking this AI Action via MCP, you would provide both the entry ID and the specific fields to process.`;

    case "mediareference":
    case "media reference":
      return `## MediaReference Variable Type

The MediaReference variable type enables AI Actions to work with digital assets such as images, videos, documents, and other media files in your Contentful space.

### Purpose

MediaReference variables are ideal for:
- Generating descriptions for images
- Creating alt text for accessibility
- Analyzing media content
- Processing metadata from assets
- Working with document content

### Configuration

MediaReference variables require these properties:

- **ID**: Unique identifier (e.g., "product_image")
- **Name**: User-friendly label (e.g., "Product Image")
- **Description**: Explanation of what asset to reference
- **Configuration**: Typically minimal, as it's restricted to assets

### Field Path Specification

Similar to References, when using MediaReferences in MCP, you need to provide:
1. The asset ID (which media asset to reference)
2. The field path (which aspect of the asset to use)

This is handled through two parameters:
- **media**: The asset ID to reference
- **media_path**: The path to the field (e.g., "fields.file.en-US.url" or "fields.title.en-US")

### Template Usage

In templates, you can access asset properties:

\`\`\`
IMAGE URL: {{product_image.fields.file.url}}
IMAGE TITLE: {{product_image.fields.title}}

Please generate an SEO-friendly alt text description for this product image that highlights key visual elements.
\`\`\`

### Examples

**Example: Image Alt Text Generator**

\`\`\`javascript
{
  id: "product_image",
  type: "MediaReference",
  name: "Product Image",
  description: "The product image to generate alt text for"
}
\`\`\`

### Best Practices

1. **Specify asset type**: Clearly indicate what type of asset should be referenced (image, video, etc.)
2. **Include guidance**: Explain what aspect of the asset will be processed
3. **Consider asset metadata**: Remember that assets have both file data and metadata fields
4. **Handle different asset types**: If your AI Action supports multiple asset types, provide clear instructions

### MCP Implementation Notes

When using MediaReference variables with the MCP server:

1. The dynamic tool will include two parameters for each MediaReference:
   - The media reference parameter (e.g., "product_image")
   - The path parameter (e.g., "product_image_path")

2. Always specify both when invoking the AI Action:
   \`\`\`javascript
   invoke_ai_action_alt_text_generator({
     product_image: "7tGnRQegIvWZPj9eICA1q",
     product_image_path: "fields.file.en-US"
   });
   \`\`\`

### Common Path Values

- **fields.file.{locale}**: To access the file data
- **fields.title.{locale}**: To access the asset title
- **fields.description.{locale}**: To access the asset description

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Generate an SEO-friendly alt text for this image...\\n\\nImage context: {{image_context}}\\n\\nProduct category: {{product_category}}",
    variables: [
      {
        id: "product_image",
        type: "MediaReference",
        name: "Product Image",
        description: "The product image to generate alt text for"
      },
      {
        id: "image_context",
        type: "Text",
        name: "Image Context",
        description: "Additional context about the image"
      },
      {
        id: "product_category",
        type: "StringOptionsList",
        name: "Product Category",
        description: "The category of the product",
        configuration: {
          values: ["Apparel", "Electronics", "Home", "Beauty", "Food"]
        }
      }
    ]
  }
});
\`\`\`

When invoking this action via MCP, you would provide the asset ID and the specific field path to process.`;

    case "locale":
      return `## Locale Variable Type

The Locale variable type allows AI Actions to work with specific languages and regions, enabling localization and translation workflows in your content operations.

### Purpose

Locale variables are perfect for:
- Translation operations
- Region-specific content generation
- Language-aware content processing
- Multilingual content workflows

### Configuration

Locale variables have straightforward configuration:

- **ID**: Unique identifier (e.g., "target_language")
- **Name**: User-friendly label (e.g., "Target Language")
- **Description**: Explanation of how the locale will be used

No additional configuration properties are typically required.

### Format

Locale values follow the standard language-country format:
- **Language code**: 2-letter ISO language code (e.g., "en", "de", "fr")
- **Country/region code**: 2-letter country code (e.g., "US", "DE", "FR")
- **Combined**: language-country (e.g., "en-US", "de-DE", "fr-FR")

### Template Usage

Reference Locale variables in templates using double curly braces:

\`\`\`
ORIGINAL CONTENT (en-US):
{{input_text}}

Please translate the above content into {{target_locale}}.

TRANSLATED CONTENT ({{target_locale}}):
\`\`\`

### Examples

**Example: Content Translation**

\`\`\`javascript
{
  id: "target_locale",
  type: "Locale",
  name: "Target Language",
  description: "The language to translate the content into"
}
\`\`\`

### Best Practices

1. **Clear descriptions**: Specify whether you're looking for target or source language
2. **Validate locale format**: Ensure users enter valid locale codes (typically managed by the UI)
3. **Consider language variants**: Be clear about regional differences (e.g., en-US vs. en-GB)
4. **Use with other variables**: Combine with StandardInput for the content to be localized

### MCP Implementation

In the MCP integration, Locale variables are typically presented as string parameters with descriptions that guide users to enter valid locale codes.

### Implementation with MCP Tools

\`\`\`javascript
create_ai_action({
  // other parameters...
  instruction: {
    template: "Translate the following content into {{target_locale}}...\\n\\nORIGINAL CONTENT:\\n{{input_text}}\\n\\nTRANSLATED CONTENT:",
    variables: [
      {
        id: "target_locale",
        type: "Locale",
        name: "Target Language",
        description: "The language to translate the content into (e.g., fr-FR, de-DE, ja-JP)"
      },
      {
        id: "input_text",
        type: "StandardInput",
        name: "Content to Translate",
        description: "The content that needs to be translated"
      }
    ]
  }
});
\`\`\`

When invoking this action via MCP:

\`\`\`javascript
invoke_ai_action_translator({
  target_locale: "de-DE",
  input_text: "Welcome to our store. We offer the best products at competitive prices."
});
\`\`\`

### Common Locale Codes

- **English**: en-US, en-GB, en-AU, en-CA
- **Spanish**: es-ES, es-MX, es-AR
- **French**: fr-FR, fr-CA
- **German**: de-DE, de-AT, de-CH
- **Japanese**: ja-JP
- **Chinese**: zh-CN, zh-TW
- **Portuguese**: pt-PT, pt-BR
- **Italian**: it-IT
- **Dutch**: nl-NL
- **Russian**: ru-RU`;

    default:
      return `# AI Action Variables: ${variableType}

The variable type "${variableType}" doesn't match any of the standard Contentful AI Action variable types. The standard types are:

1. StandardInput
2. Text
3. FreeFormInput
4. StringOptionsList
5. Reference
6. MediaReference
7. Locale

Please check the spelling or request information about one of these standard types for detailed guidance.`;
  }
}
```

--------------------------------------------------------------------------------
/src/types/tools.ts:
--------------------------------------------------------------------------------

```typescript
// Define interface for config parameter
interface ConfigSchema {
  type: string
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  properties: Record<string, any>
  required?: string[]
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [key: string]: any
}

export const getSpaceEnvProperties = (config: ConfigSchema): ConfigSchema => {
  const spaceEnvProperties = {
    spaceId: {
      type: "string",
      description:
        "The ID of the Contentful space. This must be the space's ID, not its name, ask for this ID if it's unclear.",
    },
    environmentId: {
      type: "string",
      description:
        "The ID of the environment within the space, by default this will be called Master",
      default: "master",
    },
  }

  if (!process.env.SPACE_ID && !process.env.ENVIRONMENT_ID) {
    return {
      ...config,
      properties: {
        ...config.properties,
        ...spaceEnvProperties,
      },
      required: [...(config.required || []), "spaceId", "environmentId"],
    }
  }

  return config
}

// Tool definitions for Entry operations
export const getEntryTools = () => {
  return {
    SEARCH_ENTRIES: {
      name: "search_entries",
      description:
        "Search for entries using query parameters. Returns a maximum of 3 items per request. Use skip parameter to paginate through results.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          query: {
            type: "object",
            description: "Query parameters for searching entries",
            properties: {
              content_type: { type: "string" },
              select: { type: "string" },
              limit: {
                type: "number",
                default: 3,
                maximum: 3,
                description: "Maximum number of items to return (max: 3)",
              },
              skip: {
                type: "number",
                default: 0,
                description: "Number of items to skip for pagination",
              },
              order: { type: "string" },
              query: { type: "string" },
            },
            required: ["limit", "skip"],
          },
        },
        required: ["query"],
      }),
    },
    CREATE_ENTRY: {
      name: "create_entry",
      description:
        "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.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          contentTypeId: {
            type: "string",
            description: "The ID of the content type for the new entry",
          },
          fields: {
            type: "object",
            description:
              "The fields of the entry with localized values. Example: { title: { 'en-US': 'My Title' }, description: { 'en-US': 'My Description' } }",
          },
        },
        required: ["contentTypeId", "fields"],
      }),
    },
    GET_ENTRY: {
      name: "get_entry",
      description: "Retrieve an existing entry",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: { type: "string" },
        },
        required: ["entryId"],
      }),
    },
    UPDATE_ENTRY: {
      name: "update_entry",
      description:
        "Update an existing entry. The handler will merge your field updates with the existing entry fields, so you only need to provide the fields and locales you want to change. 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.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: { type: "string" },
          fields: {
            type: "object",
            description:
              "The fields to update with localized values. Example: { title: { 'en-US': 'My Updated Title' } }",
          },
        },
        required: ["entryId", "fields"],
      }),
    },
    DELETE_ENTRY: {
      name: "delete_entry",
      description: "Delete an entry",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: { type: "string" },
        },
        required: ["entryId"],
      }),
    },
    PUBLISH_ENTRY: {
      name: "publish_entry",
      description:
        "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.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            oneOf: [
              { type: "string" },
              {
                type: "array",
                items: { type: "string" },
                maxItems: 100,
                description: "Array of entry IDs to publish (max: 100)",
              },
            ],
            description: "ID of the entry to publish, or an array of entry IDs (max: 100)",
          },
        },
        required: ["entryId"],
      }),
    },
    UNPUBLISH_ENTRY: {
      name: "unpublish_entry",
      description:
        "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.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            oneOf: [
              { type: "string" },
              {
                type: "array",
                items: { type: "string" },
                maxItems: 100,
                description: "Array of entry IDs to unpublish (max: 100)",
              },
            ],
            description: "ID of the entry to unpublish, or an array of entry IDs (max: 100)",
          },
        },
        required: ["entryId"],
      }),
    },
  }
}

// Tool definitions for Asset operations
export const getAssetTools = () => {
  return {
    LIST_ASSETS: {
      name: "list_assets",
      description:
        "List assets in a space. Returns a maximum of 3 items per request. Use skip parameter to paginate through results.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          limit: {
            type: "number",
            default: 3,
            maximum: 3,
            description: "Maximum number of items to return (max: 3)",
          },
          skip: {
            type: "number",
            default: 0,
            description: "Number of items to skip for pagination",
          },
        },
        required: ["limit", "skip"],
      }),
    },
    UPLOAD_ASSET: {
      name: "upload_asset",
      description: "Upload a new asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          title: { type: "string" },
          description: { type: "string" },
          file: {
            type: "object",
            properties: {
              upload: { type: "string" },
              fileName: { type: "string" },
              contentType: { type: "string" },
            },
            required: ["upload", "fileName", "contentType"],
          },
        },
        required: ["title", "file"],
      }),
    },
    GET_ASSET: {
      name: "get_asset",
      description: "Retrieve an asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          assetId: { type: "string" },
        },
        required: ["assetId"],
      }),
    },
    UPDATE_ASSET: {
      name: "update_asset",
      description: "Update an asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          assetId: { type: "string" },
          title: { type: "string" },
          description: { type: "string" },
          file: {
            type: "object",
            properties: {
              url: { type: "string" },
              fileName: { type: "string" },
              contentType: { type: "string" },
            },
            required: ["url", "fileName", "contentType"],
          },
        },
        required: ["assetId"],
      }),
    },
    DELETE_ASSET: {
      name: "delete_asset",
      description: "Delete an asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          assetId: { type: "string" },
        },
        required: ["assetId"],
      }),
    },
    PUBLISH_ASSET: {
      name: "publish_asset",
      description: "Publish an asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          assetId: { type: "string" },
        },
        required: ["assetId"],
      }),
    },
    UNPUBLISH_ASSET: {
      name: "unpublish_asset",
      description: "Unpublish an asset",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          assetId: { type: "string" },
        },
        required: ["assetId"],
      }),
    },
  }
}

// Tool definitions for Content Type operations
export const getContentTypeTools = () => {
  return {
    LIST_CONTENT_TYPES: {
      name: "list_content_types",
      description:
        "List content types in a space. Returns a maximum of 10 items per request. Use skip parameter to paginate through results.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          limit: {
            type: "number",
            default: 10,
            maximum: 20,
            description: "Maximum number of items to return (max: 3)",
          },
          skip: {
            type: "number",
            default: 0,
            description: "Number of items to skip for pagination",
          },
        },
        required: ["limit", "skip"],
      }),
    },
    GET_CONTENT_TYPE: {
      name: "get_content_type",
      description: "Get details of a specific content type",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          contentTypeId: { type: "string" },
        },
        required: ["contentTypeId"],
      }),
    },
    CREATE_CONTENT_TYPE: {
      name: "create_content_type",
      description: "Create a new content type",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          name: { type: "string" },
          fields: {
            type: "array",
            description: "Array of field definitions for the content type",
            items: {
              type: "object",
              properties: {
                id: {
                  type: "string",
                  description: "The ID of the field",
                },
                name: {
                  type: "string",
                  description: "Display name of the field",
                },
                type: {
                  type: "string",
                  description:
                    "Type of the field (Text, Number, Date, Location, Media, Boolean, JSON, Link, Array, etc)",
                  enum: [
                    "Symbol",
                    "Text",
                    "Integer",
                    "Number",
                    "Date",
                    "Location",
                    "Object",
                    "Boolean",
                    "Link",
                    "Array",
                  ],
                },
                required: {
                  type: "boolean",
                  description: "Whether this field is required",
                  default: false,
                },
                localized: {
                  type: "boolean",
                  description: "Whether this field can be localized",
                  default: false,
                },
                linkType: {
                  type: "string",
                  description:
                    "Required for Link fields. Specifies what type of resource this field links to",
                  enum: ["Entry", "Asset"],
                },
                items: {
                  type: "object",
                  description:
                    "Required for Array fields. Specifies the type of items in the array",
                  properties: {
                    type: {
                      type: "string",
                      enum: ["Symbol", "Link"],
                    },
                    linkType: {
                      type: "string",
                      enum: ["Entry", "Asset"],
                    },
                    validations: {
                      type: "array",
                      items: {
                        type: "object",
                      },
                    },
                  },
                },
                validations: {
                  type: "array",
                  description: "Array of validation rules for the field",
                  items: {
                    type: "object",
                  },
                },
              },
              required: ["id", "name", "type"],
            },
          },
          description: { type: "string" },
          displayField: { type: "string" },
        },
        required: ["name", "fields"],
      }),
    },
    UPDATE_CONTENT_TYPE: {
      name: "update_content_type",
      description:
        "Update an existing content type. The handler will merge your field updates with existing content type data, so you only need to provide the fields and properties you want to change.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          contentTypeId: { type: "string" },
          name: { type: "string" },
          fields: {
            type: "array",
            items: { type: "object" },
          },
          description: { type: "string" },
          displayField: { type: "string" },
        },
        required: ["contentTypeId", "fields"],
      }),
    },
    DELETE_CONTENT_TYPE: {
      name: "delete_content_type",
      description: "Delete a content type",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          contentTypeId: { type: "string" },
        },
        required: ["contentTypeId"],
      }),
    },
    PUBLISH_CONTENT_TYPE: {
      name: "publish_content_type",
      description: "Publish a content type",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          contentTypeId: { type: "string" },
        },
        required: ["contentTypeId"],
      }),
    },
  }
}

// Tool definitions for Space & Environment operations
export const getSpaceEnvTools = () => {
  if (process.env.SPACE_ID && process.env.ENVIRONMENT_ID) {
    return {}
  }
  return {
    LIST_SPACES: {
      name: "list_spaces",
      description: "List all available spaces",
      inputSchema: {
        type: "object",
        properties: {},
      },
    },
    GET_SPACE: {
      name: "get_space",
      description: "Get details of a space",
      inputSchema: {
        type: "object",
        properties: {
          spaceId: { type: "string" },
        },
        required: ["spaceId"],
      },
    },
    LIST_ENVIRONMENTS: {
      name: "list_environments",
      description: "List all environments in a space",
      inputSchema: {
        type: "object",
        properties: {
          spaceId: { type: "string" },
        },
        required: ["spaceId"],
      },
    },
    CREATE_ENVIRONMENT: {
      name: "create_environment",
      description: "Create a new environment",
      inputSchema: {
        type: "object",
        properties: {
          spaceId: { type: "string" },
          environmentId: { type: "string" },
          name: { type: "string" },
        },
        required: ["spaceId", "environmentId", "name"],
      },
    },
    DELETE_ENVIRONMENT: {
      name: "delete_environment",
      description: "Delete an environment",
      inputSchema: {
        type: "object",
        properties: {
          spaceId: { type: "string" },
          environmentId: { type: "string" },
        },
        required: ["spaceId", "environmentId"],
      },
    },
  }
}

// Tool definitions for Bulk Actions
export const getBulkActionTools = () => {
  return {
    BULK_VALIDATE: {
      name: "bulk_validate",
      description: "Validate multiple entries at once",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryIds: {
            type: "array",
            description: "Array of entry IDs to validate",
            items: {
              type: "string",
            },
          },
        },
        required: ["entryIds"],
      }),
    },
  }
}

// Tool definitions for AI Actions
export const getAiActionTools = () => {
  return {
    LIST_AI_ACTIONS: {
      name: "list_ai_actions",
      description: "List all AI Actions in a space",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          limit: {
            type: "number",
            default: 100,
            description: "Maximum number of AI Actions to return",
          },
          skip: {
            type: "number",
            default: 0,
            description: "Number of AI Actions to skip for pagination",
          },
          status: {
            type: "string",
            enum: ["all", "published"],
            description: "Filter AI Actions by status",
          },
        },
        required: [],
      }),
    },
    GET_AI_ACTION: {
      name: "get_ai_action",
      description: "Get a specific AI Action by ID",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to retrieve",
          },
        },
        required: ["aiActionId"],
      }),
    },
    CREATE_AI_ACTION: {
      name: "create_ai_action",
      description: "Create a new AI Action",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          name: {
            type: "string",
            description: "The name of the AI Action",
          },
          description: {
            type: "string",
            description: "The description of the AI Action",
          },
          instruction: {
            type: "object",
            description: "The instruction object containing the template and variables",
            properties: {
              template: {
                type: "string",
                description: "The prompt template with variable placeholders",
              },
              variables: {
                type: "array",
                description: "Array of variable definitions",
                items: {
                  type: "object",
                },
              },
              conditions: {
                type: "array",
                description: "Optional array of conditions for the template",
                items: {
                  type: "object",
                },
              },
            },
            required: ["template", "variables"],
          },
          configuration: {
            type: "object",
            description: "The model configuration",
            properties: {
              modelType: {
                type: "string",
                description: "The type of model to use (e.g., gpt-4)",
              },
              modelTemperature: {
                type: "number",
                description: "The temperature setting for the model (0.0 to 1.0)",
                minimum: 0,
                maximum: 1,
              },
            },
            required: ["modelType", "modelTemperature"],
          },
          testCases: {
            type: "array",
            description: "Optional array of test cases for the AI Action",
            items: {
              type: "object",
            },
          },
        },
        required: ["name", "description", "instruction", "configuration"],
      }),
    },
    UPDATE_AI_ACTION: {
      name: "update_ai_action",
      description: "Update an existing AI Action",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to update",
          },
          name: {
            type: "string",
            description: "The name of the AI Action",
          },
          description: {
            type: "string",
            description: "The description of the AI Action",
          },
          instruction: {
            type: "object",
            description: "The instruction object containing the template and variables",
            properties: {
              template: {
                type: "string",
                description: "The prompt template with variable placeholders",
              },
              variables: {
                type: "array",
                description: "Array of variable definitions",
                items: {
                  type: "object",
                },
              },
              conditions: {
                type: "array",
                description: "Optional array of conditions for the template",
                items: {
                  type: "object",
                },
              },
            },
            required: ["template", "variables"],
          },
          configuration: {
            type: "object",
            description: "The model configuration",
            properties: {
              modelType: {
                type: "string",
                description: "The type of model to use (e.g., gpt-4)",
              },
              modelTemperature: {
                type: "number",
                description: "The temperature setting for the model (0.0 to 1.0)",
                minimum: 0,
                maximum: 1,
              },
            },
            required: ["modelType", "modelTemperature"],
          },
          testCases: {
            type: "array",
            description: "Optional array of test cases for the AI Action",
            items: {
              type: "object",
            },
          },
        },
        required: ["aiActionId", "name", "description", "instruction", "configuration"],
      }),
    },
    DELETE_AI_ACTION: {
      name: "delete_ai_action",
      description: "Delete an AI Action",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to delete",
          },
        },
        required: ["aiActionId"],
      }),
    },
    PUBLISH_AI_ACTION: {
      name: "publish_ai_action",
      description: "Publish an AI Action",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to publish",
          },
        },
        required: ["aiActionId"],
      }),
    },
    UNPUBLISH_AI_ACTION: {
      name: "unpublish_ai_action",
      description: "Unpublish an AI Action",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to unpublish",
          },
        },
        required: ["aiActionId"],
      }),
    },
    INVOKE_AI_ACTION: {
      name: "invoke_ai_action",
      description: "Invoke an AI Action with variables",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action to invoke",
          },
          variables: {
            type: "object",
            description: "Key-value pairs of variable IDs and their values",
            additionalProperties: {
              type: "string",
            },
          },
          rawVariables: {
            type: "array",
            description:
              "Array of raw variable objects (for complex variable types like references)",
            items: {
              type: "object",
            },
          },
          outputFormat: {
            type: "string",
            enum: ["Markdown", "RichText", "PlainText"],
            default: "Markdown",
            description: "The format of the output content",
          },
          waitForCompletion: {
            type: "boolean",
            default: true,
            description: "Whether to wait for the AI Action to complete before returning",
          },
        },
        required: ["aiActionId"],
      }),
    },
    GET_AI_ACTION_INVOCATION: {
      name: "get_ai_action_invocation",
      description: "Get the result of a previous AI Action invocation",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          aiActionId: {
            type: "string",
            description: "The ID of the AI Action",
          },
          invocationId: {
            type: "string",
            description: "The ID of the specific invocation to retrieve",
          },
        },
        required: ["aiActionId", "invocationId"],
      }),
    },
  }
}

// Tool definitions for Comment operations
export const getCommentTools = () => {
  return {
    GET_COMMENTS: {
      name: "get_comments",
      description:
        "Retrieve comments for an entry with pagination support. Returns comments with their status and body content.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            type: "string",
            description: "The unique identifier of the entry to get comments for",
          },
          bodyFormat: {
            type: "string",
            enum: ["plain-text", "rich-text"],
            default: "plain-text",
            description: "Format for the comment body content",
          },
          status: {
            type: "string",
            enum: ["active", "resolved", "all"],
            default: "active",
            description: "Filter comments by status",
          },
          limit: {
            type: "number",
            default: 10,
            minimum: 1,
            maximum: 100,
            description: "Maximum number of comments to return (1-100, default: 10)",
          },
          skip: {
            type: "number",
            default: 0,
            minimum: 0,
            description: "Number of comments to skip for pagination (default: 0)",
          },
        },
        required: ["entryId"],
      }),
    },
    CREATE_COMMENT: {
      name: "create_comment",
      description:
        "Create a new comment on an entry. The comment will be created with the specified body and status. To create a threaded conversation (reply to an existing comment), provide the parent comment ID. This allows you to work around the 512-character limit by creating threaded replies.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            type: "string",
            description: "The unique identifier of the entry to comment on",
          },
          body: {
            type: "string",
            description: "The content of the comment (max 512 characters)",
          },
          status: {
            type: "string",
            enum: ["active"],
            default: "active",
            description: "The status of the comment",
          },
          parent: {
            type: "string",
            description:
              "Optional ID of the parent comment to reply to. Use this to create threaded conversations or to continue longer messages by replying to your own comments.",
          },
        },
        required: ["entryId", "body"],
      }),
    },
    GET_SINGLE_COMMENT: {
      name: "get_single_comment",
      description: "Retrieve a specific comment by its ID for an entry.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            type: "string",
            description: "The unique identifier of the entry",
          },
          commentId: {
            type: "string",
            description: "The unique identifier of the comment to retrieve",
          },
          bodyFormat: {
            type: "string",
            enum: ["plain-text", "rich-text"],
            default: "plain-text",
            description: "Format for the comment body content",
          },
        },
        required: ["entryId", "commentId"],
      }),
    },
    DELETE_COMMENT: {
      name: "delete_comment",
      description: "Delete a specific comment from an entry.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            type: "string",
            description: "The unique identifier of the entry",
          },
          commentId: {
            type: "string",
            description: "The unique identifier of the comment to delete",
          },
        },
        required: ["entryId", "commentId"],
      }),
    },
    UPDATE_COMMENT: {
      name: "update_comment",
      description:
        "Update an existing comment on an entry. The handler will merge your updates with the existing comment data.",
      inputSchema: getSpaceEnvProperties({
        type: "object",
        properties: {
          entryId: {
            type: "string",
            description: "The unique identifier of the entry",
          },
          commentId: {
            type: "string",
            description: "The unique identifier of the comment to update",
          },
          body: {
            type: "string",
            description: "The updated content of the comment",
          },
          status: {
            type: "string",
            enum: ["active", "resolved"],
            description: "The updated status of the comment",
          },
          bodyFormat: {
            type: "string",
            enum: ["plain-text", "rich-text"],
            default: "plain-text",
            description: "Format for the comment body content",
          },
        },
        required: ["entryId", "commentId"],
      }),
    },
  }
}

// Export combined tools
export const getTools = () => {
  return {
    ...getEntryTools(),
    ...getAssetTools(),
    ...getContentTypeTools(),
    ...getSpaceEnvTools(),
    ...getBulkActionTools(),
    ...getAiActionTools(),
    ...getCommentTools(),
  }
}

```
Page 2/2FirstPrevNextLast