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
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | GraphQL endpoint URL, e.g. https://target/graphql or https://target/api | |
| auth_header | No | Authorization header value, e.g. 'Bearer abc123' | |
| auth_cookie | No | Session cookie for authenticated requests |
Implementation Reference
- src/tools/graphql.ts:33-209 (handler)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) }], }; } - src/tools/graphql.ts:12-210 (registration)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) }], }; } );