Skip to main content
Glama

graphql_introspect

Discover GraphQL API schemas by running introspection queries to enumerate all types, fields, queries, and mutations, including hidden or undocumented elements.

Instructions

Run introspection query to enumerate all types, fields, and mutations. Sends the standard GraphQL introspection query (__schema) to discover the full API schema including hidden/undocumented fields, mutations, and types. Returns: {introspection_enabled, types: [{name, kind, fields: [str]}], mutations: [str], queries: [str]}. Side effects: Single POST request. Read-only.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesGraphQL endpoint URL, e.g. https://target/graphql or https://target/api
auth_headerNoAuthorization header value, e.g. 'Bearer abc123'
auth_cookieNoSession cookie for authenticated requests

Implementation Reference

  • Handler function for graphql_introspect. It constructs a GraphQL introspection query, executes it via curl, and processes the result.
    async ({ url, auth_header, auth_cookie }) => {
      requireTool("curl");
    
      const introspectionQuery = {
        query: `
                query IntrospectionQuery {
                    __schema {
                        queryType { name }
                        mutationType { name }
                        types {
                            name
                            kind
                            fields {
                                name
                                type {
                                    name
                                    kind
                                    ofType { name kind }
                                }
                                args {
                                    name
                                    type { name kind }
                                }
                            }
                        }
                    }
                }
            `,
      };
    
      const curlArgs: string[] = [
        "-sk",
        "-o",
        "-",
        "-w",
        "\n__META__%{http_code}",
        "-X",
        "POST",
        "-H",
        "Content-Type: application/json",
        "-d",
        JSON.stringify(introspectionQuery),
      ];
    
      if (auth_header) {
        curlArgs.push("-H", `Authorization: ${auth_header}`);
      }
      if (auth_cookie) {
        curlArgs.push("-b", auth_cookie);
      }
      curlArgs.push(url);
    
      const res = await runCmd("curl", curlArgs);
      let body = res.stdout;
      const metaMarker = body.lastIndexOf("__META__");
      let status = 0;
      if (metaMarker !== -1) {
        try {
          status = parseInt(body.slice(metaMarker + 8).trim(), 10) || 0;
        } catch {
          // ignore
        }
        body = body.slice(0, metaMarker);
      }
    
      // Parse the introspection result
      let data: Record<string, unknown>;
      try {
        data = JSON.parse(body);
      } catch {
        const result = {
          introspection_enabled: false,
          status,
          error: "Failed to parse response as JSON",
          response_snippet: body.slice(0, 1000),
          hint: "Introspection may be disabled. Try alternative endpoints: /graphql, /api/graphql, /v1/graphql",
        };
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }
    
      const schema = (
        (data.data as Record<string, unknown> | undefined) ?? {}
      ).__schema as Record<string, unknown> | undefined;
    
      if (!schema) {
        // Check if there's an error message
        const errors = (data.errors as Array<Record<string, unknown>> | undefined) ?? [];
        const result = {
          introspection_enabled: false,
          status,
          errors: errors
            .slice(0, 5)
            .map((e) => (e.message as string) || ""),
          response_snippet: body.slice(0, 1000),
          hint: "Introspection may be disabled. Try field suggestion brute-force instead.",
        };
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }
    
      // Extract types (filter out internal __ types)
      const typesList: Array<{
        name: string;
        kind: string;
        fields?: Array<{ name: string; type: string; args: string[] }>;
      }> = [];
    
      const rawTypes = (schema.types as Array<Record<string, unknown>>) ?? [];
      for (const t of rawTypes) {
        const typeName = t.name as string;
        if (typeName.startsWith("__")) continue;
    
        const typeInfo: {
          name: string;
          kind: string;
          fields?: Array<{ name: string; type: string; args: string[] }>;
        } = {
          name: typeName,
          kind: t.kind as string,
        };
    
        if (t.fields) {
          const rawFields = t.fields as Array<Record<string, unknown>>;
          typeInfo.fields = rawFields.map((f) => {
            const fType = f.type as Record<string, unknown> | undefined;
            const ofType = (fType?.ofType as Record<string, unknown> | undefined) ?? {};
            const typeName =
              (fType?.name as string | undefined) ||
              (ofType?.name as string | undefined) ||
              "";
            const rawArgs = (f.args as Array<Record<string, unknown>>) ?? [];
            return {
              name: f.name as string,
              type: typeName,
              args: rawArgs.map((a) => a.name as string),
            };
          });
        }
    
        typesList.push(typeInfo);
      }
    
      // Extract query and mutation names
      const queryTypeName =
        ((schema.queryType as Record<string, unknown> | undefined)?.name as string) ||
        "Query";
      const mutationTypeName =
        ((schema.mutationType as Record<string, unknown> | undefined)?.name as string) ||
        "Mutation";
    
      let queries: string[] = [];
      let mutations: string[] = [];
      for (const t of typesList) {
        if (t.name === queryTypeName && t.fields) {
          queries = t.fields.map((f) => f.name);
        } else if (t.name === mutationTypeName && t.fields) {
          mutations = t.fields.map((f) => f.name);
        }
      }
    
      const result = {
        introspection_enabled: true,
        status,
        type_count: typesList.length,
        types: typesList,
        queries,
        mutations,
        hint: `Found ${typesList.length} types, ${queries.length} queries, ${mutations.length} mutations. Look for sensitive fields (password, secret, token, admin, private).`,
      };
    
      return {
        content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
      };
    }
  • Registration of the graphql_introspect tool.
    export function register(server: McpServer): void {
      server.tool(
        "graphql_introspect",
        "Run introspection query to enumerate all types, fields, and mutations. Sends the standard GraphQL introspection query (__schema) to discover the full API schema including hidden/undocumented fields, mutations, and types. Returns: {introspection_enabled, types: [{name, kind, fields: [str]}], mutations: [str], queries: [str]}. Side effects: Single POST request. Read-only.",
        {
          url: z
            .string()
            .describe(
              "GraphQL endpoint URL, e.g. https://target/graphql or https://target/api"
            ),
          auth_header: z
            .string()
            .optional()
            .describe(
              "Authorization header value, e.g. 'Bearer abc123'"
            ),
          auth_cookie: z
            .string()
            .optional()
            .describe("Session cookie for authenticated requests"),
        },
        async ({ url, auth_header, auth_cookie }) => {
          requireTool("curl");
    
          const introspectionQuery = {
            query: `
                    query IntrospectionQuery {
                        __schema {
                            queryType { name }
                            mutationType { name }
                            types {
                                name
                                kind
                                fields {
                                    name
                                    type {
                                        name
                                        kind
                                        ofType { name kind }
                                    }
                                    args {
                                        name
                                        type { name kind }
                                    }
                                }
                            }
                        }
                    }
                `,
          };
    
          const curlArgs: string[] = [
            "-sk",
            "-o",
            "-",
            "-w",
            "\n__META__%{http_code}",
            "-X",
            "POST",
            "-H",
            "Content-Type: application/json",
            "-d",
            JSON.stringify(introspectionQuery),
          ];
    
          if (auth_header) {
            curlArgs.push("-H", `Authorization: ${auth_header}`);
          }
          if (auth_cookie) {
            curlArgs.push("-b", auth_cookie);
          }
          curlArgs.push(url);
    
          const res = await runCmd("curl", curlArgs);
          let body = res.stdout;
          const metaMarker = body.lastIndexOf("__META__");
          let status = 0;
          if (metaMarker !== -1) {
            try {
              status = parseInt(body.slice(metaMarker + 8).trim(), 10) || 0;
            } catch {
              // ignore
            }
            body = body.slice(0, metaMarker);
          }
    
          // Parse the introspection result
          let data: Record<string, unknown>;
          try {
            data = JSON.parse(body);
          } catch {
            const result = {
              introspection_enabled: false,
              status,
              error: "Failed to parse response as JSON",
              response_snippet: body.slice(0, 1000),
              hint: "Introspection may be disabled. Try alternative endpoints: /graphql, /api/graphql, /v1/graphql",
            };
            return {
              content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
            };
          }
    
          const schema = (
            (data.data as Record<string, unknown> | undefined) ?? {}
          ).__schema as Record<string, unknown> | undefined;
    
          if (!schema) {
            // Check if there's an error message
            const errors = (data.errors as Array<Record<string, unknown>> | undefined) ?? [];
            const result = {
              introspection_enabled: false,
              status,
              errors: errors
                .slice(0, 5)
                .map((e) => (e.message as string) || ""),
              response_snippet: body.slice(0, 1000),
              hint: "Introspection may be disabled. Try field suggestion brute-force instead.",
            };
            return {
              content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
            };
          }
    
          // Extract types (filter out internal __ types)
          const typesList: Array<{
            name: string;
            kind: string;
            fields?: Array<{ name: string; type: string; args: string[] }>;
          }> = [];
    
          const rawTypes = (schema.types as Array<Record<string, unknown>>) ?? [];
          for (const t of rawTypes) {
            const typeName = t.name as string;
            if (typeName.startsWith("__")) continue;
    
            const typeInfo: {
              name: string;
              kind: string;
              fields?: Array<{ name: string; type: string; args: string[] }>;
            } = {
              name: typeName,
              kind: t.kind as string,
            };
    
            if (t.fields) {
              const rawFields = t.fields as Array<Record<string, unknown>>;
              typeInfo.fields = rawFields.map((f) => {
                const fType = f.type as Record<string, unknown> | undefined;
                const ofType = (fType?.ofType as Record<string, unknown> | undefined) ?? {};
                const typeName =
                  (fType?.name as string | undefined) ||
                  (ofType?.name as string | undefined) ||
                  "";
                const rawArgs = (f.args as Array<Record<string, unknown>>) ?? [];
                return {
                  name: f.name as string,
                  type: typeName,
                  args: rawArgs.map((a) => a.name as string),
                };
              });
            }
    
            typesList.push(typeInfo);
          }
    
          // Extract query and mutation names
          const queryTypeName =
            ((schema.queryType as Record<string, unknown> | undefined)?.name as string) ||
            "Query";
          const mutationTypeName =
            ((schema.mutationType as Record<string, unknown> | undefined)?.name as string) ||
            "Mutation";
    
          let queries: string[] = [];
          let mutations: string[] = [];
          for (const t of typesList) {
            if (t.name === queryTypeName && t.fields) {
              queries = t.fields.map((f) => f.name);
            } else if (t.name === mutationTypeName && t.fields) {
              mutations = t.fields.map((f) => f.name);
            }
          }
    
          const result = {
            introspection_enabled: true,
            status,
            type_count: typesList.length,
            types: typesList,
            queries,
            mutations,
            hint: `Found ${typesList.length} types, ${queries.length} queries, ${mutations.length} mutations. Look for sensitive fields (password, secret, token, admin, private).`,
          };
    
          return {
            content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
          };
        }
      );

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/operantlabs/operant-mcp'

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