Skip to main content
Glama

submit_diff_review

Submit a git diff for code review to a chosen provider, with optional base ref and prompt. Auto-detects diff range from working tree or branch.

Instructions

Send a code-review job: the MCP server runs git diff inside repo_path and forwards the diff to the chosen provider. The diff content never appears in the LLM's output tokens, only the short tool call does. When base is omitted, auto-detects: dirty working tree -> diff against HEAD; clean tree with main/master/origin-HEAD found -> ${detected}...HEAD; otherwise falls back to diff against HEAD. Pass base explicitly (e.g. "main", a tag, or a SHA) to force a ${base}...HEAD PR-style range. Optional prompt is prepended above the diff so reviewers can scope the review. Default capability is "review" - override if the provider advertises a different tag.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
provider_npubYes
capabilityNoCapability tag advertised by the reviewer. Override if not "review".review
repo_pathNoPath to the git repo. Absolute or relative to the MCP server's working directory..
baseNoOptional base ref (branch, tag, SHA). When set, diffs ${base}...HEAD. When omitted, auto-detects working-tree vs main/master/origin-HEAD.
promptNoOptional instructions prepended above the diff (e.g. "focus on auth flow").
kind_offsetNo
timeout_secsNo
max_price_lamportsNo

Implementation Reference

  • The `submit_diff_review` tool handler: computes a git diff (via computeGitDiff), composes the prompt + diff as the job input payload, then delegates to executeSubmitAndPay for the full submit/pay/await flow.
    defineTool({
      name: 'submit_diff_review',
      description:
        'Send a code-review job: the MCP server runs `git diff` inside repo_path and forwards ' +
        "the diff to the chosen provider. The diff content never appears in the LLM's output " +
        'tokens, only the short tool call does. ' +
        'When base is omitted, auto-detects: dirty working tree -> diff against HEAD; ' +
        'clean tree with main/master/origin-HEAD found -> ${detected}...HEAD; otherwise ' +
        'falls back to diff against HEAD. Pass base explicitly (e.g. "main", a tag, or a SHA) ' +
        'to force a `${base}...HEAD` PR-style range. ' +
        'Optional `prompt` is prepended above the diff so reviewers can scope the review. ' +
        'Default capability is "review" - override if the provider advertises a different tag.',
      schema: SubmitDiffReviewSchema,
      async handler(ctx, input) {
        ctx.toolRateLimiter.check();
        checkLen('provider_npub', input.provider_npub, MAX_NPUB_LEN);
    
        let diffResult;
        try {
          diffResult = await computeGitDiff(input.repo_path, input.base);
        } catch (e) {
          return errorResult(e instanceof Error ? e.message : String(e));
        }
    
        // Compose payload: optional prompt then a fenced diff block with the
        // resolved range so the provider knows what was actually compared.
        const promptBlock = input.prompt.trim().length > 0 ? `${input.prompt.trim()}\n\n` : '';
        const payload = `${promptBlock}--- git diff (${diffResult.describedRange}) ---\n${diffResult.diff}`;
        if (payload.length > MAX_INPUT_LEN) {
          return errorResult(
            `Combined prompt + diff is ${payload.length} chars (max ${MAX_INPUT_LEN}). ` +
              `Shorten the prompt or pass a narrower base.`,
          );
        }
    
        const agent = ctx.active();
        return executeSubmitAndPay(ctx, agent, {
          input: payload,
          providerNpub: input.provider_npub,
          providerPubkey: decodeNpub(input.provider_npub),
          capability: input.capability,
          dTag: toDTag(input.capability),
          kindOffset: input.kind_offset,
          timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1000,
          maxPriceLamports: input.max_price_lamports,
        });
      },
  • Zod schema for submit_diff_review: validates provider_npub, capability, repo_path, base (optional), prompt, kind_offset, timeout_secs, and max_price_lamports.
    const SubmitDiffReviewSchema = z.object({
      provider_npub: z.string(),
      capability: z
        .string()
        .min(1)
        .max(64)
        .default('review')
        .describe('Capability tag advertised by the reviewer. Override if not "review".'),
      repo_path: z
        .string()
        .min(1)
        .max(4096)
        .default('.')
        .describe("Path to the git repo. Absolute or relative to the MCP server's working directory."),
      base: z
        .string()
        .max(200)
        .optional()
        .describe(
          'Optional base ref (branch, tag, SHA). When set, diffs ${base}...HEAD. When ' +
            'omitted, auto-detects working-tree vs main/master/origin-HEAD.',
        ),
      prompt: z
        .string()
        .max(MAX_INPUT_LEN)
        .default('')
        .describe('Optional instructions prepended above the diff (e.g. "focus on auth flow").'),
      kind_offset: z.number().int().min(0).max(999).default(DEFAULT_KIND_OFFSET),
      timeout_secs: z.number().int().min(1).max(600).default(300),
      max_price_lamports: z.number().int().optional(),
    });
  • All tools aggregated in server.ts - customerTools array (which includes submit_diff_review) is spread into the allTools registry and registered with the MCP server.
    const allTools: ToolDefinition[] = [
      ...discoveryTools,
      ...customerTools,
      ...walletTools,
      ...dashboardTools,
      ...agentTools,
      ...feedbackContactsTools,
      ...policiesTools,
    ];
    
    // fail at startup if any tool name is duplicated or empty. A silent overwrite in
    // `toolMap` would cause the LLM to see two tools with the same name but only one being
    // callable.
    const toolMap = new Map<string, ToolDefinition>();
  • The computeGitDiff helper function: handles base detection (auto-detecting main/master/origin-HEAD), runs git diff, and returns the diff text with a described range string.
    export async function computeGitDiff(repoPath: string, base?: string): Promise<GitDiffResult> {
      if (repoPath.length > MAX_INPUT_PATH_LEN) {
        throw new Error(`repo_path too long: ${repoPath.length} chars (max ${MAX_INPUT_PATH_LEN}).`);
      }
      const absRepo = isAbsolute(repoPath) ? repoPath : resolvePath(process.cwd(), repoPath);
      await assertGitRepo(absRepo);
    
      let args: string[];
      let describedRange: string;
      if (base) {
        args = ['diff', `${base}...HEAD`];
        describedRange = `${base}...HEAD`;
      } else if (await isDirty(absRepo)) {
        args = ['diff', 'HEAD'];
        describedRange = 'HEAD (working tree, uncommitted changes)';
      } else {
        const detected = await detectDefaultBase(absRepo);
        if (detected) {
          args = ['diff', `${detected}...HEAD`];
          describedRange = `${detected}...HEAD`;
        } else {
          args = ['diff', 'HEAD'];
          describedRange = 'HEAD (no main/master detected)';
        }
      }
    
      const diff = await execGit(absRepo, args);
      if (diff.trim().length === 0) {
        throw new Error(
          `No changes in range ${describedRange}. Nothing to review - commit work, ` +
            `pass an explicit "base", or check the repo path.`,
        );
      }
      if (diff.length > MAX_INPUT_LEN) {
        throw new Error(
          `Diff for range ${describedRange} is ${diff.length} chars (max ${MAX_INPUT_LEN}). ` +
            `Pass a narrower "base" or split the review.`,
        );
      }
      return { diff, describedRange };
    }
  • The executeSubmitAndPay shared helper function used by submit_diff_review (and submit_and_pay_job, submit_and_pay_job_from_file) to handle the complete submit->pay->await flow.
    async function executeSubmitAndPay(
      ctx: AgentContext,
      agent: AgentInstance,
      params: SubmitAndPayParams,
    ): Promise<ToolResult> {
      // Pre-ping: refuse to submit to an unreachable provider. The 30s pong cache
      // in PingService means that if the caller just ran search_agents, this is a
      // free in-memory lookup. A hard error here avoids wasting a 120-300s timeout
      // and, for paid jobs, avoids publishing a NIP-90 request that nobody will ever
      // service.
      const ping = await agent.client.ping.pingAgent(params.providerPubkey, PRE_PING_TIMEOUT_MS);
      if (!ping.online) {
        return errorResult(
          `Provider ${params.providerNpub} is offline. ` +
            `Run search_agents to find currently-online providers.`,
        );
      }
    
      // resolve expected Solana recipient from the provider's capability card
      // BEFORE submitting the job. If the provider is unknown on-network, fail fast.
      const providers = await agent.client.discovery.fetchAgents(agent.network);
      const provider = providers.find((a) => a.npub === params.providerNpub);
    
      // if the provider is not in the current discovery snapshot, refuse
      // to submit. Previously we fell through with `expectedRecipient = undefined`,
      // which silently disabled the recipient-match check inside `validatePaymentRequest`
      // and let a malicious actor redirect funds. Free providers without Solana payment
      // are still allowed - the no-wallet path in makePaymentFeedbackHandler handles them.
      if (!provider) {
        return errorResult(
          `Provider ${params.providerNpub} not found on ${agent.network}. ` +
            `Refresh discovery (e.g. search_agents) or verify the npub is correct.`,
        );
      }
      const expectedRecipient = providerSolanaAddress(provider, params.dTag);
      if (agent.solanaKeypair && !expectedRecipient) {
        // Customer has a wallet (intends to pay), but the provider advertised no Solana
        // recipient for this capability. We cannot verify where funds would go - refuse.
        return errorResult(
          `Provider "${params.providerNpub}" has no Solana payment address for ` +
            `capability "${params.capability}". Cannot verify payment recipient - refusing ` +
            `to proceed. Ask the provider to publish a capability card with a payment address.`,
        );
      }
    
      const buyerWallet = agent.solanaKeypair?.publicKey;
      if (buyerWallet && expectedRecipient && buyerWallet === expectedRecipient) {
        return errorResult(
          `Cannot buy from yourself - your agent's Solana wallet (${buyerWallet}) ` +
            `matches the provider's payment address. Use a different agent or provider.`,
        );
      }
    
      const submittedAt = Date.now();
      const jobId = await agent.client.marketplace.submitJobRequest(agent.identity, {
        input: params.input,
        capability: params.dTag,
        providerPubkey: params.providerPubkey,
        kindOffset: params.kindOffset,
      });
    
      let paymentSig: string | undefined;
      let paidAmountSubunits: bigint | undefined;
      let paidAssetKey: string | undefined;
      let paymentWarnings: string[] = [];
      try {
        const result = await awaitJobResult<string>(
          agent,
          {} as never,
          ({ resolve, reject }) => {
            const payHandler = makePaymentFeedbackHandler({
              ctx,
              agent,
              jobId,
              providerPubkey: params.providerPubkey,
              expectedRecipient,
              maxPriceLamports: params.maxPriceLamports,
              resolveNoWallet: resolve,
              resolveResult: resolve,
              rejectPayment: reject,
              onPaid: (sig, warnings, amount, assetKey) => {
                paymentSig = sig;
                paidAmountSubunits = amount;
                paidAssetKey = assetKey;
                paymentWarnings = warnings;
                for (const line of warnings) {
                  logger.warn({ event: 'session_spend_threshold', agent: agent.name }, line);
                }
              },
            });
            return {
              jobEventId: jobId,
              providerPubkey: params.providerPubkey,
              customerPublicKey: agent.identity.publicKey,
              callbacks: {
                onResult(content: string) {
                  const kind = isLikelyBase64(content) ? ('binary' as const) : ('text' as const);
                  const sanitized = sanitizeUntrusted(content, kind);
                  payHandler.onResultReceived(`Job completed.\n\n${sanitized.text}`);
                },
                onFeedback: payHandler.onFeedback,
                onError(error: string) {
                  reject(new Error(`Job error: ${error}`));
                },
              },
              timeoutMs: params.timeoutMs,
              customerSecretKey: agent.identity.secretKey,
            };
          },
          params.timeoutMs + 5_000,
        );
    
        await recordJobOutcome(agent, {
          jobEventId: jobId,
          capability: params.dTag,
          providerPubkey: params.providerPubkey,
          providerName: clipProviderName(provider.name),
          paidAmountSubunits: paidAmountSubunits?.toString(),
          assetKey: paidAssetKey,
          status: 'completed',
          submittedAt,
          completedAt: Date.now(),
          resultPreview: result.slice(0, RESULT_PREVIEW_MAX_LEN),
          paymentSig,
        });
        const warningBlock = paymentWarnings.length > 0 ? `${paymentWarnings.join('\n')}\n` : '';
        const tip = buildJobCompletionTip(jobId, params.providerNpub);
        return textResult(`${warningBlock}event_id=${jobId}\n${result}${tip}`);
      } catch (e) {
        const msg = e instanceof Error ? e.message : String(e);
        await recordJobOutcome(agent, {
          jobEventId: jobId,
          capability: params.dTag,
          providerPubkey: params.providerPubkey,
          providerName: clipProviderName(provider.name),
          paidAmountSubunits: paidAmountSubunits?.toString(),
          assetKey: paidAssetKey,
          status: classifyJobFailure(msg),
          submittedAt,
          completedAt: Date.now(),
          paymentSig,
        });
        const paid = paymentSig
          ? ` Payment already sent (sig=${paymentSig}) - use get_job_result with event_id="${jobId}" to retrieve once ready.`
          : '';
        const warningBlock = paymentWarnings.length > 0 ? `${paymentWarnings.join('\n')}\n` : '';
        return errorResult(`${warningBlock}Job ${jobId} failed: ${msg}.${paid}`);
      }
    }
Behavior4/5

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

With no annotations, the description explains that diff content never appears in LLM output, auto-detection logic for base, and prompt prepending. Lacks details on side effects, authentication, or rate limits, but offers substantial insight.

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?

A single paragraph that front-loads the main action and efficiently packs details, though slightly long. No wasted sentences, but less structured than ideal.

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

Completeness2/5

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

With 8 parameters, no annotations, and no output schema, the description fails to explain three parameters (kind_offset, timeout_secs, max_price_lamports) and omits return value or post-call flow (e.g., job ID retrieval). Incomplete for an agent to use confidently.

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 coverage is 50%; description adds meaning for base (auto-detection) and prompt (prepended), but no explanation for kind_offset, timeout_secs, or max_price_lamports. Partially compensates but leaves gaps.

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 sends a code-review job by running git diff and forwarding to a provider, with specific verb and resource. It distinguishes itself from sibling tools by focusing on code review, though not explicitly contrasting.

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?

Provides guidance on base parameter behavior (auto-detection), capability override, and prompt usage. However, no explicit comparison to sibling tools like submit_and_pay_job, leaving some ambiguity about when to choose this tool.

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/elisymlabs/elisym'

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