AI agents and MCP

Give an agent the API as a tool, through a function-calling definition or the hosted Silvertext MCP server, so it can check a draft and apply the fixes.

The API was shaped for agents as much as for apps: one submit, one poll, one report whose findings carry fixes, and JSON everywhere. Two ways in.

As a tool in your agent

Define two tools, submit_check and read_check, and let the model chain them. The submit tool takes the text and options; the read tool takes the id and returns the summary or the findings. Keep polling on your side (the tool waits for the check) so the model sees one round trip.

tools.ts
import { Silvertext } from './silvertext';

const st = new Silvertext();

export const tools = [
  {
    name: 'check_writing',
    description:
      'Check a document for plagiarism, grammar and spelling, citation problems and AI-written content. Returns scores and a list of findings, each with a suggested fix.',
    input_schema: {
      type: 'object',
      properties: {
        text: { type: 'string', description: 'The document text.' },
        checks: {
          type: 'array',
          items: {
            type: 'string',
            enum: ['plagiarism', 'grammarSpelling', 'citations', 'aiContent', 'style'],
          },
          description: 'Which checks to run. Defaults to all.',
        },
        citationStyle: { type: 'string', enum: ['APA', 'MLA', 'Chicago'] },
      },
      required: ['text'],
    },
  },
];

export async function runTool(name: string, input: any) {
  if (name !== 'check_writing') throw new Error(`unknown tool ${name}`);
  const all = ['plagiarism', 'grammarSpelling', 'citations', 'aiContent', 'style'];
  const options = Object.fromEntries(
    all.map((k) => [k, !input.checks || input.checks.includes(k)]),
  );
  const { id } = await st.createCheck({
    text: input.text,
    options,
    citationStyle: input.citationStyle,
  });
  const summary = await st.waitForCheck(id);
  if (summary.status !== 'done')
    return { status: summary.status, reason: summary.rejection?.message };
  const { report } = await st.getCheck<{ report: any }>(id, { include: ['issues', 'sources'] });
  return {
    scores: summary.scores,
    findings: report.issues.map((i: any) => ({
      category: i.category,
      severity: i.severity,
      text: i.original?.text,
      message: i.message,
      fix: i.fix?.replacement ?? i.rephrase?.text,
      source: i.sourceUrl,
    })),
    reportUrl: `https://app.silvertext.com/reports/${id}`,
  };
}

Trim the tool result to what the model needs. The full report with spans and matches runs to hundreds of kilobytes on a long document; the findings list above is a few kilobytes and is what an editing agent acts on.

Through the MCP server

The same checks are tools on a hosted MCP server at https://api.silvertext.com/mcp, so Claude, Cursor, VS Code and any other MCP client can check a draft with no code on your side. Streamable HTTP, stateless; the API key travels as a header on the connection, never inside a tool call.

claude mcp add --scope user --transport http silvertext https://api.silvertext.com/mcp \
  --header "Authorization: Bearer $SILVERTEXT_API_KEY"

Four tools, brand-prefixed so they never collide with another server's:

ToolWhat it doesCost
silvertext_submit_checkSubmits text or a base64 file with the same options and citationStyle as the REST body, waits up to wait_seconds (0 to 55, default 45) for the result, and answers a one-line verdict plus the scores and issues with fixes (detail: "full" for spans, matches and sources). A check still running comes back with its check_id. Takes an idempotency_key, so a retried call never starts a second check.150 credits per 250 words
silvertext_get_checkStatus and progress while a check runs; the report once it is done, trimmed to scores and issues unless detail or include asks for more.free
silvertext_list_checksThe 50 most recent checks with status and scores.free
silvertext_get_usageCredits remaining (plan period plus purchased pool) and where to top up.free

Every answer carries credits_remaining; a refusal comes back as a tool error in the REST error code's words (quota_exceeded says how many credits are left and where to buy more, rate_limited says when to retry), so the agent can relay it. The key's scopes apply exactly as on REST: a checks:read key can read and list but a submit answers insufficient_scope.

Reading the result as an agent

  • scores.plagiarism above a threshold you choose means the draft needs sources or rewriting; the sources list has the URLs and citation metadata to cite them.
  • scores.aiContent is a likelihood, not a verdict. Show it with its sentence-level findings rather than acting on the number alone.
  • Findings carry offsets into the submitted text. Apply fixes from the end of the document backwards, or hand the model the text plus the findings and let it rewrite.
  • A rejected check has a rejection.code worth surfacing (too_long, unsupported_language); an agent should not retry those.

On this page