Skip to main content
Glama
index.ts7.54 kB
#!/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 { graphqlHandlers, fetchGraphQLSchema, setGraphQLSchema, loadContentfulMetadata, } from "./handlers/graphql-handlers.js" import { getTools } from "./types/tools.js" import { validateEnvironment } from "./utils/validation.js" import { StreamableHttpServer } from "./transports/streamable-http.js" // Validate environment variables validateEnvironment() // Create AI Action tool context // Function to get all tools including dynamic AI Action tools export function getAllTools() { // Get all static tools const allStaticTools = getTools() // Filter tools based on token availability const staticTools: Record<string, unknown> = {} if (allStaticTools.GRAPHQL_QUERY) staticTools.GRAPHQL_QUERY = allStaticTools.GRAPHQL_QUERY if (allStaticTools.GRAPHQL_LIST_CONTENT_TYPES) staticTools.GRAPHQL_LIST_CONTENT_TYPES = allStaticTools.GRAPHQL_LIST_CONTENT_TYPES if (allStaticTools.GRAPHQL_GET_CONTENT_TYPE_SCHEMA) staticTools.GRAPHQL_GET_CONTENT_TYPE_SCHEMA = allStaticTools.GRAPHQL_GET_CONTENT_TYPE_SCHEMA if (allStaticTools.GRAPHQL_GET_EXAMPLE) staticTools.GRAPHQL_GET_EXAMPLE = allStaticTools.GRAPHQL_GET_EXAMPLE if (allStaticTools.SMART_SEARCH) staticTools.SMART_SEARCH = allStaticTools.SMART_SEARCH if (allStaticTools.BUILD_SEARCH_QUERY) staticTools.BUILD_SEARCH_QUERY = allStaticTools.BUILD_SEARCH_QUERY return staticTools } // Create MCP server const server = new Server( { name: "contentful-graphql-mcp-server", version: "0.0.1", }, { 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 const result = await handlePrompt(name, args) // Add tools to the prompt result // Use Object.values to convert from object to array // @ts-ignore - SDK expects a specific tool format result.tools = Object.values(getAllTools()) return { messages: result.messages, tools: result.tools, } }) // 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 || {}) 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 { const cdaOnlyHandlers = { // Only GraphQL operations are allowed with just a CDA token graphql_query: graphqlHandlers.executeQuery, graphql_list_content_types: graphqlHandlers.listContentTypes, graphql_get_content_type_schema: graphqlHandlers.getContentTypeSchema, graphql_get_example: graphqlHandlers.getExample, smart_search: graphqlHandlers.smartSearch, build_search_query: graphqlHandlers.buildSearchQuery, } return cdaOnlyHandlers[name as keyof typeof cdaOnlyHandlers] } // Function to fetch GraphQL schema async function loadGraphQLSchema() { try { const spaceId = process.env.SPACE_ID const environmentId = process.env.ENVIRONMENT_ID || "master" // GraphQL REQUIRES a CDA token - Management tokens won't work for GraphQL const cdaToken = process.env.CONTENTFUL_DELIVERY_ACCESS_TOKEN // Check if we have the minimum required parameters if (!spaceId || !cdaToken) { console.error("Unable to fetch GraphQL schema: Space ID or CDA access token not provided") return } console.error( `Fetching GraphQL schema for space ${spaceId}, environment ${environmentId} using CDA token...`, ) const schema = await fetchGraphQLSchema(spaceId, environmentId, cdaToken) if (schema) { setGraphQLSchema(schema) console.error("GraphQL schema loaded successfully") } else { console.error("Failed to load GraphQL schema") } } catch (error) { console.error("Error loading GraphQL schema:", error) } } // Function to load Contentful metadata into cache async function loadContentfulCache() { try { const spaceId = process.env.SPACE_ID const environmentId = process.env.ENVIRONMENT_ID || "master" const cdaToken = process.env.CONTENTFUL_DELIVERY_ACCESS_TOKEN if (!spaceId || !cdaToken) { console.error("Unable to load Contentful metadata: Space ID or CDA access token not provided") return } await loadContentfulMetadata(spaceId, environmentId, cdaToken) } catch (error) { console.error("Error loading Contentful metadata:", 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 GraphQL schema const loadPromises = [] loadPromises.push(loadGraphQLSchema()) loadPromises.push(loadContentfulCache()) // Wait for all resources to load await Promise.all(loadPromises) 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 GraphQL MCP Server running with StreamableHTTP on port ${httpPort}`, ) // 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 GraphQL MCP Server running on stdio`, ) } // Set up periodic refresh of GraphQL schema (every 5 minutes) setInterval( () => { const promises = [ loadGraphQLSchema().catch((error) => console.error("Error refreshing GraphQL schema:", error)), loadContentfulCache().catch((error) => console.error("Error refreshing Contentful cache:", error)) ] Promise.all(promises) }, 5 * 60 * 1000, ) } runServer().catch((error) => { console.error("Fatal error running server:", error) process.exit(1) })

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ivo-toby/contentful-mcp-graphql'

If you have feedback or need assistance with the MCP directory API, please join our Discord server