Skip to main content
Glama
ivo-toby

Contentful GraphQL MCP Server

smart_search

Search across multiple content types using cached metadata to find content without manually chaining GraphQL calls.

Instructions

Perform intelligent search across multiple content types using cached metadata. This tool automatically searches all text fields in your content types for the given query term. Much faster and easier than manually chaining multiple GraphQL calls.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesThe search term to look for across content types
contentTypesNoOptional: Limit search to specific content types (default: search all)
limitNoMaximum number of results per content type (default: 5)
spaceIdNoOptional override for the space ID (defaults to SPACE_ID environment variable)
environmentIdNoOptional override for the environment ID (defaults to ENVIRONMENT_ID environment variable or 'master')

Implementation Reference

  • Main handler function for the 'smart_search' tool. It searches across multiple content types using cached schemas, building dynamic GraphQL queries for text fields containing the search term.
    smartSearch: async (args: SmartSearchArgs): Promise<ToolResponse> => {
      try {
        if (!isCacheAvailable()) {
          return {
            content: [
              {
                type: "text",
                text: "Smart search requires cached metadata. Please wait for the cache to load or use individual GraphQL queries.",
              },
            ],
            isError: true,
          }
        }
    
        const spaceId = args.spaceId || process.env.SPACE_ID
        const environmentId = args.environmentId || process.env.ENVIRONMENT_ID || "master"
        const accessToken = process.env.CONTENTFUL_DELIVERY_ACCESS_TOKEN
        const limit = args.limit || 5
    
        if (!spaceId || !accessToken) {
          return {
            content: [
              {
                type: "text",
                text: "Space ID and CDA token are required for smart search",
              },
            ],
            isError: true,
          }
        }
    
        const contentTypes = getCachedContentTypes()
        if (!contentTypes) {
          return {
            content: [{ type: "text", text: "No content types available in cache" }],
            isError: true,
          }
        }
    
        // Filter content types if specified
        const targetContentTypes = args.contentTypes
          ? contentTypes.filter((ct) => args.contentTypes!.includes(ct.name))
          : contentTypes
    
        const searchPromises = targetContentTypes.map(async (contentType) => {
          try {
            const schema = getCachedContentTypeSchema(contentType.name)
            if (!schema) return null
    
            // Find searchable text fields
            const textFields = schema.fields.filter((field: any) => isSearchableTextField(field.type))
            if (textFields.length === 0) return null
    
            // Build search query with OR conditions across text fields
            const searchConditions = textFields
              .map((field: any) => `{ ${field.name}_contains: $searchTerm }`)
              .join(", ")
    
            const query = `
              query SearchIn${contentType.name}($searchTerm: String!) {
                ${contentType.queryName}(where: { OR: [${searchConditions}] }, limit: ${limit}) {
                  items {
                    sys { id }
                    ${textFields.map((field: any) => field.name).join("\n                  ")}
                  }
                }
              }
            `
    
            const result = await graphqlHandlers.executeQuery({
              query,
              variables: { searchTerm: args.query },
              spaceId,
              environmentId,
            })
    
            if (!result.isError) {
              const data = JSON.parse(result.content[0].text)
              const items = data.data?.[contentType.queryName]?.items || []
              if (items.length > 0) {
                return {
                  contentType: contentType.name,
                  items: items.map((item: any) => ({
                    id: item.sys.id,
                    ...Object.fromEntries(
                      textFields.map((field: any) => [field.name, item[field.name]]),
                    ),
                  })),
                }
              }
            }
            return null
          } catch (error) {
            console.error(`Search error for ${contentType.name}:`, error)
            return null
          }
        })
    
        const results = await Promise.all(searchPromises)
        const validResults = results.filter(Boolean)
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  query: args.query,
                  results: validResults,
                  totalContentTypesSearched: targetContentTypes.length,
                  contentTypesWithResults: validResults.length,
                },
                null,
                2,
              ),
            },
          ],
        }
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error in smart search: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        }
      }
    },
  • Tool schema definition for 'smart_search', including input schema with query, optional contentTypes, limit, and environment overrides.
    SMART_SEARCH: {
      name: "smart_search",
      description:
        "Perform intelligent search across multiple content types using cached metadata. This tool automatically searches all text fields in your content types for the given query term. Much faster and easier than manually chaining multiple GraphQL calls.",
      inputSchema: getOptionalEnvProperties({
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "The search term to look for across content types",
          },
          contentTypes: {
            type: "array",
            items: { type: "string" },
            description: "Optional: Limit search to specific content types (default: search all)",
          },
          limit: {
            type: "number",
            description: "Maximum number of results per content type (default: 5)",
            default: 5,
          },
        },
        required: ["query"],
      }),
    },
  • TypeScript interface defining the input arguments for the smartSearch handler.
    export interface SmartSearchArgs {
      query: string
      contentTypes?: string[] // Optional filter to specific content types
      limit?: number // Limit per content type (default: 5)
      spaceId?: string // Optional override for environment variable
      environmentId?: string // Optional override for environment variable
    }
  • src/index.ts:126-126 (registration)
    Registration of the 'smart_search' tool name to the graphqlHandlers.smartSearch function in the handler map.
    smart_search: graphqlHandlers.smartSearch,
  • src/index.ts:43-44 (registration)
    Conditional inclusion of the SMART_SEARCH tool in the staticTools object used for MCP capabilities.
    if (allStaticTools.SMART_SEARCH)
      staticTools.SMART_SEARCH = allStaticTools.SMART_SEARCH
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'using cached metadata' and 'much faster' which provides some behavioral context about performance characteristics. However, it doesn't disclose important behavioral traits like whether this is a read-only operation, what permissions might be required, rate limits, error handling, or what the output format looks like (especially problematic since there's no output schema).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately concise with three sentences that each add value: stating the purpose, explaining the scope, and providing comparative context. It's front-loaded with the core functionality. While efficient, it could be slightly more structured by explicitly separating purpose from benefits.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a 5-parameter search tool with no annotations and no output schema, the description is incomplete. It doesn't explain what the tool returns (critical for a search tool), doesn't mention authentication requirements, and provides minimal behavioral context. The description should do more to compensate for the lack of structured metadata, especially regarding output format and operational constraints.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 5 parameters thoroughly. The description doesn't add any meaningful parameter semantics beyond what's in the schema - it mentions 'search term' and 'content types' but these are already covered. With complete schema coverage, the baseline is 3 even without additional param info in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Perform intelligent search across multiple content types using cached metadata' and 'automatically searches all text fields in your content types for the given query term.' It specifies the verb (search), resource (content types), and mechanism (cached metadata). However, it doesn't explicitly differentiate from sibling tools like 'build_search_query' or 'graphql_query' beyond mentioning it's faster than manual GraphQL calls.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides some usage context: 'Much faster and easier than manually chaining multiple GraphQL calls' implies this tool should be used for cross-content-type searches instead of manual GraphQL queries. However, it doesn't explicitly state when to use this vs. alternatives like 'build_search_query' or 'graphql_query', nor does it mention any exclusions or prerequisites for usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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