Skip to main content
Glama

Claude Query

query

Send a coding prompt to Claude CLI, optionally including files, resuming sessions, and capping cost.

Instructions

Execute a prompt via Claude Code CLI with optional file context and session resume. Claude is an AI coding agent that can generate, analyze, refactor, and explain code.

Capabilities: code generation and refactoring, code analysis and explanation, file understanding (text and images), multi-turn conversations via sessionId.

Cost: Default model is Sonnet (~$0.01-0.10/call). Use effort="low" for simple tasks, effort="high" + model="opus" for complex analysis. Set maxBudgetUsd to cap per-call cost (recommended for effort="max" or model="opus").

Tips:

  • Set workingDirectory to the target repo for project-aware responses.

  • Break complex tasks into focused prompts rather than one large request.

  • Resume multi-turn conversations with sessionId from a previous response's metadata.

  • Include relevant files via the files parameter for targeted context (text files inlined in prompt, images trigger allowed-tools mode).

  • Use noSessionPersistence=true for stateless one-shot calls.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
promptYesThe prompt to send to Claude
filesNoFile paths (text or images) relative to workingDirectory
modelNoModel alias or full Claude model name
sessionIdNoClaude session ID to resume with --resume
resetSessionNoClear stored session state before execution (use with sessionId to start fresh)
noSessionPersistenceNoDisable session persistence for ephemeral print calls
workingDirectoryNoWorking directory for file resolution and CLI execution
timeoutNoTimeout in milliseconds (default: 60000, image queries: 120000)
maxResponseLengthNoSoft limit on response length in words
maxBudgetUsdNoMaximum cost budget in USD for this call (passed to --max-budget-usd)
effortNoEffort level: low, medium, high, or max (passed to --effort)

Implementation Reference

  • Main entry point for the query tool handler. Routes to executeTextQuery or executeImageQuery based on whether image files are included.
    export async function executeQuery(input: QueryInput): Promise<QueryResult> {
      const { prompt, files = [], timeout, maxResponseLength, maxBudgetUsd, effort } = input;
      const model = resolveModel("query", input.model);
    
      const cwd = await resolveCwd(input.workingDirectory);
    
      if (files.length > MAX_FILES) {
        throw new Error(`Too many files: ${files.length} (max ${MAX_FILES})`);
      }
    
      const textFiles = files.filter((f) => !isImageFile(f));
      const imageFiles = files.filter((f) => isImageFile(f));
    
      if (imageFiles.length > 0) {
        return executeImageQuery({ prompt, textFiles, imageFiles, model, timeout, cwd, maxResponseLength, sessionId: input.sessionId, noSessionPersistence: input.noSessionPersistence, maxBudgetUsd, effort });
      }
    
      return executeTextQuery({ prompt, textFiles, model, timeout, cwd, maxResponseLength, sessionId: input.sessionId, noSessionPersistence: input.noSessionPersistence, maxBudgetUsd, effort });
    }
  • Text-only query execution: reads text files, assembles prompt, spawns Claude CLI, parses output, handles timeouts.
    async function executeTextQuery(input: BaseQueryInput): Promise<QueryResult> {
      const { prompt, textFiles, model, timeout, cwd, maxResponseLength, sessionId, noSessionPersistence, maxBudgetUsd, effort } = input;
    
      const fileContents = textFiles.length > 0 ? await readFiles(textFiles, cwd) : [];
      const fullPrompt = appendLengthLimit(assemblePrompt(prompt, fileContents), maxResponseLength);
      const useStdin = fullPrompt.length > STDIN_THRESHOLD || textFiles.length > 0;
      const effectiveTimeout = clampTimeout(timeout, 60_000);
    
      const args = buildClaudeArgs({
        model,
        fallbackModel: getFallbackModel(),
        maxBudgetUsd: resolveMaxBudget(maxBudgetUsd),
        effort: resolveEffort("query", effort),
        sessionId,
        noSessionPersistence,
        prompt: useStdin ? undefined : fullPrompt,
      });
    
      const result = await spawnClaude({ args, cwd, stdin: useStdin ? fullPrompt : undefined, timeout: effectiveTimeout });
    
      const filesIncluded = fileContents.filter((f) => !f.skipped).map((f) => f.path);
      const filesSkipped = fileContents.filter((f) => f.skipped).map((f) => `${f.path}: ${f.skipped}`);
    
      if (result.timedOut) {
        return {
          response: tryParsePartial(result.stdout, result.stderr, effectiveTimeout),
          model,
          filesIncluded,
          filesSkipped,
          imagesIncluded: [],
          timedOut: true,
          resolvedCwd: cwd,
        };
      }
    
      const parsed = parseClaudeOutput(result.stdout, result.stderr);
      checkAndThrow(result, parsed);
    
      return {
        response: parsed.response,
        model,
        sessionId: parsed.sessionId,
        totalCostUsd: parsed.totalCostUsd,
        usage: parsed.usage,
        filesIncluded,
        filesSkipped,
        imagesIncluded: [],
        timedOut: false,
        resolvedCwd: cwd,
      };
    }
  • Image-aware query execution: validates image files, assembles prompt with image references, spawns Claude CLI with allowedTools=['Read'], handles timeouts.
    async function executeImageQuery(input: ImageQueryInput): Promise<QueryResult> {
      const { prompt, textFiles, imageFiles, model, timeout, cwd, maxResponseLength, sessionId, noSessionPersistence, maxBudgetUsd, effort } = input;
    
      const imageResults = await Promise.all(
        imageFiles.map(async (img) => {
          try {
            const resolved = await resolveAndVerify(img, cwd);
            const size = await checkFileSize(resolved);
            if (size > MAX_IMAGE_FILE_SIZE) {
              return { skipped: `${img}: ${(size / 1024).toFixed(0)}KB exceeds ${(MAX_IMAGE_FILE_SIZE / 1024).toFixed(0)}KB limit` };
            }
            return { resolved, original: img };
          } catch (err) {
            return { skipped: `${img}: ${(err as Error).message}` };
          }
        }),
      );
    
      const validImages = imageResults.filter(
        (r): r is { resolved: string; original: string } => "resolved" in r,
      );
      const imageNames = validImages.map((r) => r.original);
      const skippedImages = imageResults
        .filter((r): r is { skipped: string } => "skipped" in r)
        .map((r) => r.skipped);
    
      const fileContents = textFiles.length > 0 ? await readFiles(textFiles, cwd) : [];
      const textPart = assemblePrompt(prompt, fileContents);
      const imagePart = imageNames.map((p) => `Read and analyze the image at: ${p}`).join("\n");
      const fullPrompt = appendLengthLimit(
        imageNames.length > 0 ? `${textPart}\n\n## Image Files\n\n${imagePart}` : textPart,
        maxResponseLength,
      );
      const effectiveTimeout = clampTimeout(timeout, IMAGE_QUERY_TIMEOUT);
    
      const args = buildClaudeArgs({
        model,
        fallbackModel: getFallbackModel(),
        maxBudgetUsd: resolveMaxBudget(maxBudgetUsd),
        effort: resolveEffort("query", effort),
        sessionId,
        noSessionPersistence,
        allowedTools: ["Read"],
      });
    
      const result = await spawnClaude({ args, cwd, stdin: fullPrompt, timeout: effectiveTimeout });
    
      const filesIncluded = fileContents.filter((f) => !f.skipped).map((f) => f.path);
      const filesSkipped = [
        ...fileContents.filter((f) => f.skipped).map((f) => `${f.path}: ${f.skipped}`),
        ...skippedImages,
      ];
    
      if (result.timedOut) {
        return {
          response: tryParsePartial(result.stdout, result.stderr, effectiveTimeout),
          model,
          filesIncluded,
          filesSkipped,
          imagesIncluded: imageNames,
          timedOut: true,
          resolvedCwd: cwd,
        };
      }
    
      const parsed = parseClaudeOutput(result.stdout, result.stderr);
      checkAndThrow(result, parsed);
    
      return {
        response: parsed.response,
        model,
        sessionId: parsed.sessionId,
        totalCostUsd: parsed.totalCostUsd,
        usage: parsed.usage,
        filesIncluded,
        filesSkipped,
        imagesIncluded: imageNames,
        timedOut: false,
        resolvedCwd: cwd,
      };
    }
  • Input type definition for the query tool, specifying all accepted parameters.
    export interface QueryInput {
      prompt: string;
      files?: string[];
      model?: string;
      sessionId?: string;
      noSessionPersistence?: boolean;
      workingDirectory?: string;
      timeout?: number;
      maxResponseLength?: number;
      maxBudgetUsd?: number;
      effort?: string;
    }
  • src/index.ts:40-141 (registration)
    Registration of the 'query' tool with McpServer, including its input schema (Zod definitions), description, annotations, and the handler callback that calls executeQuery and formats the response.
    server.registerTool(
      "query",
      {
        title: "Claude Query",
        description: queryDescription,
        inputSchema: {
          prompt: z.string().describe("The prompt to send to Claude"),
          files: z
            .array(z.string())
            .optional()
            .describe("File paths (text or images) relative to workingDirectory"),
          model: z.string().optional().describe("Model alias or full Claude model name"),
          sessionId: z
            .string()
            .optional()
            .describe("Claude session ID to resume with --resume"),
          resetSession: z
            .boolean()
            .optional()
            .describe("Clear stored session state before execution (use with sessionId to start fresh)"),
          noSessionPersistence: z
            .boolean()
            .optional()
            .describe("Disable session persistence for ephemeral print calls"),
          workingDirectory: z
            .string()
            .optional()
            .describe("Working directory for file resolution and CLI execution"),
          timeout: z
            .number()
            .optional()
            .describe("Timeout in milliseconds (default: 60000, image queries: 120000)"),
          maxResponseLength: z
            .number()
            .int()
            .positive()
            .optional()
            .describe("Soft limit on response length in words"),
          maxBudgetUsd: z
            .number()
            .positive()
            .optional()
            .describe("Maximum cost budget in USD for this call (passed to --max-budget-usd)"),
          effort: z
            .string()
            .optional()
            .describe("Effort level: low, medium, high, or max (passed to --effort)"),
        },
        annotations: queryAnnotations,
      },
      async (input, extra) => {
        const start = Date.now();
        const heartbeat = maybeStartHeartbeat(
          extra._meta as { progressToken?: string | number } | undefined,
          extra.sendNotification as ProgressNotificationSender,
        );
        try {
          if (input.resetSession && input.sessionId) {
            sessionStore.delete(input.sessionId);
          }
          const result = await executeQuery(
            input.resetSession ? { ...input, sessionId: undefined } : input,
          );
    
          const sessionId = result.sessionId ?? (input.resetSession ? undefined : input.sessionId);
          if (sessionId) {
            persist(sessionStore, sessionId, result);
          }
    
          const textMeta: string[] = [];
          if (result.filesIncluded.length > 0) textMeta.push(`Files included: ${result.filesIncluded.join(", ")}`);
          if (result.imagesIncluded.length > 0) textMeta.push(`Images included: ${result.imagesIncluded.join(", ")}`);
          if (result.filesSkipped.length > 0) textMeta.push(`Files skipped: ${result.filesSkipped.join(", ")}`);
          if (result.timedOut) textMeta.push("(timed out)");
    
          const text = textMeta.length > 0
            ? `${result.response}\n\n---\n${textMeta.join("\n")}`
            : result.response;
    
          return {
            content: [{ type: "text" as const, text }],
            _meta: buildMeta({
              durationMs: Date.now() - start,
              model: result.model,
              sessionId: result.sessionId,
              totalCostUsd: result.totalCostUsd,
              usage: result.usage,
              timedOut: result.timedOut,
            }),
          };
        } catch (e) {
          console.error("[query]", e);
          return {
            content: [{ type: "text" as const, text: `Error: ${getErrorMessage(e)}` }],
            isError: true,
            _meta: buildMeta({ durationMs: Date.now() - start }),
          };
        } finally {
          heartbeat.stop();
        }
      },
    );
Behavior4/5

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

Annotations indicate non-destructive and non-idempotent behavior with open-world hint. The description adds context: capabilities, cost structure, session management, and limitations like image query timeout. No contradiction with annotations; the tool's potential to generate/refactor code is noted but not claimed to modify persistent data.

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 fairly lengthy but front-loaded with the core purpose, followed by capabilities, cost, and tips. It is well-structured but could be slightly more concise without losing value.

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

Completeness5/5

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

Given the tool's complexity (11 parameters, no output schema), the description is comprehensive: covers purpose, usage, parameter semantics, cost, and practical tips. It adequately equips an agent to understand when and how to invoke the tool.

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

Parameters5/5

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

Schema description coverage is 100%, and the description adds valuable meaning beyond each parameter's schema: explains files as text/images, sessionId for resume, maxBudgetUsd as cost cap, effort levels, and workingDirectory for context. Tips further clarify parameter usage.

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 'Execute a prompt via Claude Code CLI' and lists capabilities like code generation, analysis, and multi-turn conversations. It distinguishes itself from sibling tools (listSessions, ping, search, structured) by focusing on prompt execution with optional file context and session resume.

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

Usage Guidelines4/5

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

The description provides extensive usage guidance: cost info, effort levels for different task complexities, tips for workingDirectory, breaking tasks, resuming sessions, including files, and stateless calls. However, it does not explicitly compare this tool to siblings (e.g., when to use search instead).

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/hampsterx/claude-mcp-bridge'

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