introspect_actions
List all mutations and their arguments available in a Gadget app to understand write operations on a read-only server.
Instructions
List all actions (mutations) available in this Gadget app, including their arguments. Useful for understanding what write operations are available, even though this server is read-only.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/tools.ts:335-372 (handler)Handler for the 'introspect_actions' tool. Executes a GraphQL introspection query to fetch the mutation type's fields (actions) from the schema, then maps each field to an object with name, description, and arguments. Returns the list of actions as JSON.
case "introspect_actions": { const data = await gql(` query { __schema { mutationType { fields { name description args { name description type { name kind ofType { name kind ofType { name kind } } } } } } } } `); if (!data.__schema.mutationType) { return { content: [{ type: "text", text: "No actions (mutations) found in this schema." }] }; } const actions = (data.__schema.mutationType.fields as any[]).map((f) => ({ name: f.name, description: f.description ?? "", args: (f.args ?? []).map((a: any) => ({ name: a.name, type: typeString(a.type), description: a.description ?? "", })), })); return { content: [{ type: "text", text: JSON.stringify(actions, null, 2) }] }; } - src/tools.ts:539-544 (schema)Input schema definition for the 'introspect_actions' tool. It has no required parameters and an empty properties object, as it simply lists all available mutations from the schema.
{ name: "introspect_actions", description: "List all actions (mutations) available in this Gadget app, including their arguments. Useful for understanding what write operations are available, even though this server is read-only.", inputSchema: { type: "object", properties: {} }, }, - src/tools.ts:117-452 (registration)The main handleTool function dispatches tool calls via a switch statement. The case 'introspect_actions' at line 335 is the registration/dispatch point. The TOOL_DEFINITIONS array (line 454-564) is exported and used in index.ts (line 48) for the ListTools request handler.
// ── Tool handler ────────────────────────────────────────────────────────────── export async function handleTool(name: string, args: Record<string, any>): Promise<{ content: { type: string; text: string }[]; isError?: boolean; }> { try { switch (name) { case "list_models": { const data = await gql(` query { __schema { queryType { fields { name description type { name kind } } } } } `); const fields: any[] = data.__schema.queryType.fields; const models = fields .filter((f) => f.name && !f.name.startsWith("__") && f.type?.kind === "OBJECT") .map((f) => ({ name: f.name, description: f.description ?? "" })); return { content: [{ type: "text", text: JSON.stringify(models, null, 2) }] }; } case "introspect_model": { const { model } = args as { model: string }; const typeName = model.charAt(0).toUpperCase() + model.slice(1); const data = await gql(` query IntrospectModel($name: String!) { __type(name: $name) { name fields { name description type { name kind ofType { name kind } } } } } `, { name: typeName }); if (!data.__type) { return { content: [{ type: "text", text: `No type found for "${typeName}". Try list_models to see available model names, then adjust casing.` }], }; } return { content: [{ type: "text", text: JSON.stringify(data.__type, null, 2) }] }; } case "query_records": { const { model, fields, filter, sort, limit = 10, after } = args as { model: string; fields: string; filter?: unknown; sort?: unknown; limit?: number; after?: string; }; const first = Math.min(limit, 50); const resolved = await resolveListField(model); if (!resolved) { return { content: [{ type: "text", text: `No connection field found for model "${model}". Use list_models to browse available models.` }], isError: true, }; } const { fieldName, filterArgType, sortArgType } = resolved; const varParts: string[] = ["$first: Int"]; const argParts: string[] = ["first: $first"]; const varsVal: Record<string, unknown> = { first }; if (after) { varParts.push("$after: String"); argParts.push("after: $after"); varsVal.after = after; } if (filter !== undefined && filterArgType) { varParts.push(`$filter: ${filterArgType}`); argParts.push("filter: $filter"); varsVal.filter = filter; } if (sort !== undefined && sortArgType) { varParts.push(`$sort: ${sortArgType}`); argParts.push("sort: $sort"); varsVal.sort = sort; } const query = ` query QueryRecords(${varParts.join(", ")}) { ${fieldName}(${argParts.join(", ")}) { edges { node { ${fields} } } pageInfo { hasNextPage endCursor } } } `; const data = await gql(query, varsVal); const connection = data[fieldName]; const records = connection.edges.map((e: any) => e.node); return { content: [{ type: "text", text: JSON.stringify({ records, hasMore: connection.pageInfo.hasNextPage, endCursor: connection.pageInfo.endCursor ?? null, }, null, 2), }], }; } case "count_records": { const { model, filter } = args as { model: string; filter?: unknown }; const resolved = await resolveListField(model); if (!resolved) { return { content: [{ type: "text", text: `No connection field found for model "${model}". Use list_models to browse available models.` }], isError: true, }; } const { fieldName, filterArgType } = resolved; const varParts: string[] = []; const argParts: string[] = []; const varsVal: Record<string, unknown> = {}; if (filter !== undefined && filterArgType) { varParts.push(`$filter: ${filterArgType}`); argParts.push("filter: $filter"); varsVal.filter = filter; } const varsDef = varParts.length ? `(${varParts.join(", ")})` : ""; const argsClause = argParts.length ? `(${argParts.join(", ")})` : ""; const query = ` query CountRecords${varsDef} { ${fieldName}${argsClause} { count } } `; const data = await gql(query, Object.keys(varsVal).length ? varsVal : undefined); const count = data[fieldName].count; return { content: [{ type: "text", text: JSON.stringify({ model, count }, null, 2) }], }; } case "get_record": { const { model, id, fields } = args as { model: string; id: string; fields: string }; const query = ` query GetRecord($id: GadgetID!) { ${model}(id: $id) { ${fields} } } `; const data = await gql(query, { id }); return { content: [{ type: "text", text: JSON.stringify(data[model], null, 2) }] }; } case "introspect_filters": { const { model } = args as { model: string }; const resolved = await resolveListField(model); if (!resolved?.filterArgType) { return { content: [{ type: "text", text: `No filter type found for model "${model}". Use list_models to verify the model name.` }], isError: true, }; } // Extract base type name from e.g. "[ShopifyOrderFilter!]" → "ShopifyOrderFilter" const filterTypeName = resolved.filterArgType.replace(/[\[\]!]/g, ""); const data = await gql(` query IntrospectFilters($name: String!) { __type(name: $name) { name inputFields { name description type { name kind ofType { name kind ofType { name kind } } } } } } `, { name: filterTypeName }); if (!data.__type) { return { content: [{ type: "text", text: `Filter type "${filterTypeName}" not found in schema.` }], isError: true, }; } return { content: [{ type: "text", text: JSON.stringify(data.__type, null, 2) }] }; } case "introspect_actions": { const data = await gql(` query { __schema { mutationType { fields { name description args { name description type { name kind ofType { name kind ofType { name kind } } } } } } } } `); if (!data.__schema.mutationType) { return { content: [{ type: "text", text: "No actions (mutations) found in this schema." }] }; } const actions = (data.__schema.mutationType.fields as any[]).map((f) => ({ name: f.name, description: f.description ?? "", args: (f.args ?? []).map((a: any) => ({ name: a.name, type: typeString(a.type), description: a.description ?? "", })), })); return { content: [{ type: "text", text: JSON.stringify(actions, null, 2) }] }; } case "get_schema_overview": { const data = await gql(` query { __schema { queryType { fields { name type { kind name ofType { kind name } } } } types { name kind description fields { name description type { name kind ofType { name kind ofType { name kind } } } } } } } `); // Identify model names from Connection return types in query fields const modelNames = new Set<string>(); for (const f of data.__schema.queryType.fields as any[]) { let t = f.type; if (t?.kind === "NON_NULL") t = t.ofType; if (t?.name?.endsWith("Connection")) { modelNames.add(t.name.replace(/Connection$/, "")); } } const models = (data.__schema.types as any[]) .filter((t) => modelNames.has(t.name) && t.kind === "OBJECT") .map((t) => ({ name: t.name, description: t.description ?? "", fields: (t.fields ?? []).map((f: any) => ({ name: f.name, type: typeString(f.type), description: f.description ?? "", })), })) .sort((a, b) => a.name.localeCompare(b.name)); return { content: [{ type: "text", text: JSON.stringify(models, null, 2) }] }; } case "run_graphql": { const { query, variables } = args as { query: string; variables?: Record<string, unknown> }; const trimmed = query.trim().toLowerCase(); if (trimmed.startsWith("mutation")) { return { content: [{ type: "text", text: "Mutations are not allowed — this server is read-only." }], isError: true, }; } const data = await gql(query, variables); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true }; } } catch (err: any) { return { content: [{ type: "text", text: `Error: ${err?.message ?? String(err)}` }], isError: true, }; } }