Skip to main content
Glama
ivo-toby

Contentful GraphQL MCP Server

smart_search

Search across all text fields in your Contentful content types with one query. Uses cached metadata for fast results.

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

  • The smartSearch handler function that performs the actual smart search logic. It validates cache availability, fetches content types from cache, optionally filters by content type, then for each content type builds a GraphQL query searching across all String text fields using _contains with OR conditions. Results are collected and returned as JSON with query term, per-content-type matches, and stats.
    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,
        }
      }
    },
  • SmartSearchArgs interface defining the input arguments for the smart search handler: query (string), contentTypes (optional array of strings to filter), limit (optional number per content type), spaceId and environmentId (optional overrides).
    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
    }
  • SMART_SEARCH tool registration definition in the tools schema. Defines the tool name 'smart_search', description, and inputSchema with properties: query (required string), contentTypes (optional array of strings), limit (optional number, default 5), plus optional spaceId/environmentId 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"],
      }),
    },
  • src/index.ts:43-44 (registration)
    Registration of SMART_SEARCH from the static tools definition into the MCP server's tool list via getAllTools(), conditionally including it if the definition exists.
    if (allStaticTools.SMART_SEARCH)
      staticTools.SMART_SEARCH = allStaticTools.SMART_SEARCH
  • src/index.ts:126-128 (registration)
    Handler mapping in getHandler() that maps the string 'smart_search' to graphqlHandlers.smartSearch, which is called when the MCP server receives a CallToolRequest for smart_search.
      smart_search: graphqlHandlers.smartSearch,
      build_search_query: graphqlHandlers.buildSearchQuery,
    }
Behavior3/5

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

The description discloses key behavioral traits: it uses cached metadata and searches all text fields automatically. However, with no annotations, it lacks details on whether results are read-only, authorization requirements, rate limits, or response format. Some transparency but significant gaps remain.

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

Conciseness5/5

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

The description is two concise sentences, front-loaded with the core action. Every sentence adds value: first defines purpose, second adds key features and benefits. No wasted words.

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

Completeness3/5

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

The description provides the essential purpose and a key detail (cached metadata), but lacks information about output structure, pagination, error behavior, or limitations. For a search tool with no output schema, more contextual completeness would be expected.

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 baseline is 3. The tool description adds no extra parameter meaning beyond what the schema already provides. It mentions searching all text fields, but that is not parameter-specific.

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

Purpose5/5

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

The description clearly states the tool performs intelligent search across multiple content types using cached metadata and automatically searches all text fields. It distinguishes from manually chaining GraphQL calls, making its purpose specific and unambiguous.

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 implies usage when cross-content-type search is needed by touting speed and ease over GraphQL chaining, but it does not explicitly state when not to use it or mention sibling alternatives like build_search_query or graphql_query. Guidance is implied rather than explicit.

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