Skip to main content
Glama
InterLogue

InterLogue

InterLogue

Brief it the way an editor briefs a reporter. It interviews the subject and returns a published piece with every quote cited to a timestamp in the transcript.

InterLogue is an MCP server. Inside a host such as Claude, the host writes the piece and InterLogue is the fact-checker: nothing is stored as a piece until every quoted span resolves, verbatim, to a subject turn at the cited timestamp. A product with no host model calls write_piece instead and the server writes the piece, then puts it through the same check. The spine is:

brief -> approve_contact -> interview (place_call + fetch_transcript, or run_interview) -> draft_piece -> [the host writes] -> check_citations
                                                                                          or -> write_piece (the server writes, same check)

The output leads with the story. Pull quotes come second, each with its timestamp. The per-question answers come third, as a secondary view. Every piece ends with a line that says whether a recorded call stands behind it.

The one-sentence brief

The brief tool needs three things: the person's name, their phone number, and one sentence in the user's words about who they are and what the piece is about.

Interview Marisol Teague, +1-555-555-0142, founder of Ridgeline Provisions, about switching her order entry to Tallyhook, for Tallyhook's marketing team.

Role, company, client, product, topic, angle and genre are inferred from the sentence or defaulted, and the result names every choice. Four genres share the same seven story beats: a subject at another company than the client gets the customer case study plan; a subject whose company is the client gets the founder story plan; a sentence that says podcast or episode gets the podcast segment plan (a hook question first, a closing question that invites a story); one that says testimonial gets the testimonial plan (five beats, ending on a recommendation). An explicit genre field overrides all of that, and question_templates lists what each genre asks. A phone number, email address or street address inside the sentence is stripped before anything is inferred, so it never becomes the angle.

Related MCP server: clearon-source-audit

The three rules

  1. Output leads with the story. A bulleted question-and-answer summary is meeting minutes, not a piece. The checker fails a piece that is not prose first.

  2. No cold outreach. A human approves the specific person and number before any dial. The agent states it is an AI and asks permission to record at the top of the call. A subject who declines to be recorded stops the interview: nothing is stored.

  3. No agenda steering. The user sets an angle, which shapes which questions are asked and what is emphasized. It never shapes what the subject is portrayed as having said. Every claim and quote links to a timestamp in the transcript.

The tools as a host sees them

Tool

Inputs

What it does

brief

subject_name, subject_phone, about; optional genre, subject_role, subject_company, client_company, client_product, topic, angle, content_needed

Stores the brief and builds the question plan from the sentence, in one of four genres, and says how the genre was chosen. Nobody is contacted.

approve_contact

brief_id, approved_by, statement; optional subject_name, phone, consent_basis, confirm

Over stdio: records that a named human approved contacting this brief's subject at this brief's number. Over HTTP: records nothing and returns a one-time approval link for a person to click; only the click counts. A name or number that differs from the brief refuses. See "What the approval record is" below.

run_interview

brief_id; fixture or turns

Text-only interview from the fictional fixture or from turns supplied inline. Refuses without an approval on file and refuses a transcript whose subject declined to be recorded.

place_call

brief_id; optional confirm_dial (ignored)

Phone path, step 1. Refused by the dial gate without the approval record. Places one outbound call through the ElevenLabs agent over Twilio. Present only when the ElevenLabs variables are set.

fetch_transcript

brief_id; optional wait_secs

Phone path, step 2. Waits for the call to end, fetches and normalizes the transcript with real timestamps, runs the consent check, stores it append-only. A refusal to be recorded stores nothing and marks the call consent_failed. Answers at once for a call the post-call webhook already stored. Present only with the ElevenLabs variables.

draft_piece

brief_id

Returns the reporter's packet: brief, question plan, writing contract, ranked verbatim quote candidates with timestamps, and the full transcript, with contact details masked.

check_citations

brief_id + markdown, or piece_id

The fact-checker and, with write_piece, the only path by which a piece is stored. Validates section order, every quoted span and its (MM:SS), numbers outside quotes, quotes in the dek or byline, repeated frames. On a pass it appends the per-question view, masks contact details, and stores the piece. With piece_id it re-checks a stored piece.

write_piece

brief_id; optional writer (model or deterministic)

The server writes the piece from the stored transcript, with the configured Claude model when ANTHROPIC_API_KEY is set (model from INTERLOGUE_WRITER_MODEL, default claude-sonnet-5) or the deterministic reporter otherwise, submits it to the same check as check_citations, retries once with the failing spans fed back, and stores only a clean pass.

generate_piece

brief_id

The deterministic fallback writer for the no-host run. Registered only when INTERLOGUE_ALLOW_FALLBACK=1.

status

brief_id

Where a brief sits in the spine and what to call next. Names a withdrawn brief as withdrawn. With a transcript on file it also names the planned beats the call never reached and the questions to send the subject by email.

withdraw_subject

brief_id, or subject_name + phone; confirm, confirm_token, optional requested_by

Replaces every record of a subject with tombstones at their request, after a preview. Returns the external references the operator must delete by hand.

ledger

optional brief_id

Cost and duration per brief and in total from the call records (credits and USD as ElevenLabs reported them), pieces per brief, a per-ten-minutes figure, and what one stored interview costs against the two hours of a marketer's time it replaces (dollars with INTERLOGUE_MARKETER_HOURLY_USD). Metadata only: no transcript is opened, no name or number printed.

quote_check

brief_id, optional piece_id; to relay an answer: subject_response, disputed_quotes, note, relayed_by

The subject sees exactly the words a stored piece attributes to them, each with its timestamp, and confirms or disputes them before anything is published. Hosted: a signed one-time page, delivered through the operator's channel when one is configured. Stdio: the message to send, and the answer relayed by a person. Recorded per piece, shown by status, withdrawn with the subject's other records.

derive_assets

brief_id, optional piece_id

A LinkedIn post, a short post under 280 characters and an email teaser from a stored piece, built only from the piece's own sentences and pull quotes, every quote re-verified against the transcript and kept with its timestamp. Nothing stored.

discover_contacts

query

Stub: "not in this build". Automated contact discovery is not part of InterLogue.

question_templates

optional genre

The four genres and the questions each asks, with placeholders.

download_recording

transcript_id

Stub: "not in this build". The timestamped transcript is the record.

Every tool returns readable text: a heading line, the facts, then a Next: line naming the tool to call next. A refusal sets isError. Phone numbers are never echoed beyond their last four digits.

A judge can try it two ways

Requires Node 20 or newer. No env file, no API keys.

Path 1: one command, no Claude needed (the fallback writer)

git clone https://github.com/aminehamlouchi/interlogue.git interlogue && cd interlogue && npm install && npm run spine

npm run spine builds the server, starts it over stdio, and drives the tools in order with the fictional fixture using the deterministic reporter as the writer. It prints the generated piece and the citation check, and exits non-zero if any quote fails to resolve. It also proves the gate: it calls run_interview before approve_contact and confirms the refusal.

npm test

Runs the test suite: the dial gate, the append-only store and its tombstones, consent detection and the consent gate, the redaction of contact details, the fallback writer, the host-piece checker with tamper tests, withdrawal, the server-side writer behind the citation gate (with a fake writer and a canned piece carrying a quote the subject never said), the hosted entry (bearer token, session isolation, health check, the approval link, the post-call webhook with signed and tampered payloads, tenants), the four genres, the ledger, and the partner sidecar. Nothing in the suite reads a real transcript unless INTERLOGUE_REAL_DATA_DIR names a directory that holds one, nothing in the suite can reach the phone path, and no test calls a model: the Anthropic writer runs against a fake client.

Path 3: the hosted flow, one command, no keys

npm run demo:hosted

Starts the hosted entry in-process on a loopback port with a token and an approval secret minted for the run, then drives it the way a partner's product would, with the sidecar example over plain JSON-RPC: brief, an approval link issued and nothing recorded, the gate refusing before the click, the link opened and the button pressed (by the script here; by the person the operator sent it to in a deployment), the fixture interview, write_piece with the deterministic reporter, the subject's quote_check (the one-time page opened and "These are my words" pressed by the script; by the subject in a deployment), derive_assets with the LinkedIn post printed, status and ledger. It prints the piece and exits non-zero if any step refuses. No call is placed, no model is called, no key is used. This is the flow to show a partner first.

Path 2: inside Claude, where Claude writes the piece

Claude Desktop: after npm install && npm run build in the cloned folder, quit Claude Desktop fully (it rewrites its config from memory while running) and run:

npm run register-desktop

It backs up the config, adds or refreshes only the interlogue entry (with the env-file flag when .env exists), and leaves every other entry alone. Or add the entry by hand:

{
  "mcpServers": {
    "interlogue": {
      "command": "node",
      "args": ["/absolute/path/to/interlogue/dist/src/index.js"]
    }
  }
}

Claude Code: the repo ships a .mcp.json, so opening the folder registers the interlogue server after npm run build.

Paste the one-sentence brief above into Claude and add: "I approve contacting her at that number. Use the founder-case-study fixture instead of calling, then write the piece and show it to me." Claude calls brief, approve_contact, run_interview, draft_piece, writes the piece, and check_citations stores it or returns every failing span with the closest transcript turn.

From a shell, without a host:

npm run tool -- brief '{"subject_name":"Marisol Teague","subject_phone":"+1-555-555-0142","about":"founder of Ridgeline Provisions, about switching her order entry to Tallyhook, for Tallyhook'"'"'s marketing team"}'
npm run tool -- approve_contact '{"brief_id":"<brief_id>","approved_by":"Your Name","statement":"I approve contacting this person at this number."}'
npm run tool -- run_interview '{"brief_id":"<brief_id>","fixture":"founder-case-study"}'
npm run tool -- draft_piece '{"brief_id":"<brief_id>"}'
npm run tool -- check_citations '{"brief_id":"<brief_id>"}' --markdown-file my-piece.md

The writing contract

draft_piece hands the writer a contract and check_citations enforces the parts it can enforce mechanically:

  • Order: H1 headline, story as prose, then ## Pull quotes. The per-question view is appended by the checker from the transcript.

  • Every quoted span is verbatim from a subject turn and is followed by its (MM:SS). A span without a timestamp, or a timestamp without a span, fails. One ellipsis is allowed inside a single turn, in order, with fragments of at least three words.

  • No quotes in the dek or the byline: they would sit above the fold with no timestamp.

  • Connective prose may frame and sequence but may not assert a fact about the subject outside a cited quote. A number outside a quote fails.

  • The angle chooses emphasis, never words. More than one sentence starting with "Asked", or "said:" more than once, fails.

  • Three to seven pull quotes. Plain, reported voice.

  • A thin interview must say so in the headline; draft_piece grades the interview and the packet says when that clause applies.

Hosted deploy on your own keys

src/remote.ts serves the same tools over the MCP Streamable HTTP transport, for a partner who wants to run InterLogue on their own infrastructure with their own ElevenLabs account.

  • Endpoint: POST, GET and DELETE on /mcp. Every request must carry Authorization: Bearer <INTERLOGUE_TOKEN>. A missing or wrong token, or a server with no token set, is refused with 401 before the request body is read.

  • /healthz answers GET without a token with liveness only: { ok, version, token_configured, sessions }. With a tenant's bearer token it adds that tenant's own tools, phone_tools, webhook_configured and approval_links. No subject data, no list of tenants.

  • /approve/<token>: the approval page. Over HTTP approve_contact records nothing and returns this link for a person to open; the page shows the subject's name, the masked number, the client and the approver with one button, "I approve this call". The click records the approval and the dial gate accepts only such a record. The token is an HMAC keyed with INTERLOGUE_APPROVAL_SECRET (never the bearer token, so a caller holding the token cannot mint a link; without the secret no link is issued); the URL carries only ids, an expiry and a nonce, nothing about the person; it expires after 24 hours (INTERLOGUE_APPROVAL_TTL_SECS, capped at a week), works once, is replaced by a newer link for the same brief, and a tampered, stale or superseded link records nothing. The signature is checked before anything is read from disk. INTERLOGUE_PUBLIC_URL sets the host in the link. With INTERLOGUE_APPROVAL_DELIVERY_URL set, the link is posted there (signed with INTERLOGUE_APPROVAL_DELIVERY_SECRET, last four digits of the number only) instead of being returned to the caller, which then learns only that it was sent: the operator's own channel is what hands the link to a person, and the calling program never holds it.

  • /webhooks/elevenlabs: the ElevenLabs post-call webhook, verified with ELEVENLABS_WEBHOOK_SECRET against the raw body (ElevenLabs-Signature: t=<seconds>,v0=<HMAC-SHA256 over "<timestamp>.<body>">, 30 minute tolerance). A verified transcript is stored through the same path as fetch_transcript, consent check included; polling stays as the fallback.

  • Several operators on one server: INTERLOGUE_TOKENS="id:token,id2:token2" with per-tenant INTERLOGUE_<ID>_* variables for the data root, the public URL, the ElevenLabs keys and webhook secret, the Anthropic key and writer model, the approval secret and the link lifetime. Each tenant's sessions, data and keys are its own; data roots must be distinct and must not nest, or the server refuses to start; the webhook is /webhooks/elevenlabs/<id>. INTEGRATION.md lists the variables.

  • The server-side writer: with ANTHROPIC_API_KEY set, write_piece writes with Claude (INTERLOGUE_WRITER_MODEL, default claude-sonnet-5); without it, with the deterministic reporter. Either way the piece goes through check_citations's check and is stored only on a clean pass.

  • One data directory per MCP session under INTERLOGUE_DATA_ROOT (default data/sessions). Two clients of the same deployment never see each other's briefs. A session's directory stays on disk when the session ends, so withdrawal can still find it.

  • The phone tools (place_call, fetch_transcript) are registered only when ELEVENLABS_API_KEY, ELEVENLABS_AGENT_ID and ELEVENLABS_PHONE_NUMBER_ID are all set. Without them the deployment is text-only and cannot dial.

  • PORT defaults to 3000. INTERLOGUE_ALLOW_FALLBACK=1 adds the deterministic writer.

Run it locally:

npm run build && INTERLOGUE_TOKEN=$(openssl rand -hex 24) npm run start:remote

Docker (Node 20, npm ci, npm run build, runs as a non-root user, copies no .env):

docker build -t interlogue . && docker run -p 3000:3000 -e INTERLOGUE_TOKEN=<secret> -v interlogue-data:/data interlogue

render.yaml is a Render blueprint for the same image. Every secret in it is declared unset (sync: false) and is entered in the Render dashboard; autoDeploy is off. Nothing in the repo deploys anything. The Dockerfile was written and read, not built, during the run that added it; the first build is on whoever deploys it.

A client connects with the SDK's StreamableHTTPClientTransport and a requestInit.headers.authorization of Bearer <token>, or with plain JSON-RPC over HTTP as examples/sidecar/sidecar.ts does. INTEGRATION.md has the request and response shapes, the approval and webhook flows, and the tenant variables; OPERATIONS.md has what to set, what to watch, how to rotate a secret and how to handle a withdrawal.

Withdrawal

The consent script promises a subject can ask afterwards to have their interview thrown out. withdraw_subject honours that:

  1. Called with a brief_id, or with a subject_name and phone that match exactly one approval record (two matches refuse as ambiguous, none refuse), it previews what would be removed and returns a confirm_token. Nothing is removed.

  2. Called again with confirm: true and that token, it replaces the brief, the approval, the call record, the transcript and every piece with a tombstone that holds only the id, the time and the reason "withdrawn". A tombstone is never overwritten by a later save, and every loader treats it as withdrawn: the dial gate refuses, status reports it, draft_piece has nothing to draft from.

  3. Two audit lines go to data/audit/withdrawals.jsonl, the first before any record is touched. They carry ids and external references only: no name, number or text.

  4. The result names what the operator must delete by hand: the ElevenLabs conversation id (audio and their transcript copy) and the Twilio call SID (the call log). The tool never calls either service.

Withdrawal is the one sanctioned exception to the append-only transcript rule, and it removes rather than edits.

Redaction of contact details

The stored transcript is verbatim. Everything derived from it for display is masked: a phone number becomes [phone number removed], an email address [email removed], a street address [address removed]. That covers the reporter's packet (quote candidates and the full transcript), the per-question view of a piece, the story and pull quotes of a stored piece, the citation report, the opening lines fetch_transcript shows, and the hints in a failing check. The matchers are written for speech: "five five five, five five five, oh one four two" and "marisol at ridgeline dot com" count; year ranges, money and business figures do not.

The citation check runs against the verbatim turn first. A quote that wholly contains a contact detail resolves and is stored masked. A quote that cuts through one fails. A quote copied from the masked packet, mask token included, resolves against the masked rendering of the turn. After a clean check the piece is masked and a leak check confirms no cited contact detail survives; if one did, the check is marked failed and nothing is stored.

Where data lives

Everything at runtime goes under data/, which is gitignored, so subject names and numbers never enter git. The hosted entry uses one such tree per session under INTERLOGUE_DATA_ROOT; the stdio server uses INTERLOGUE_DATA_DIR or data/.

data/briefs/<brief_id>.json          name, phone, role, company, client, topic, angle, question plan
data/approvals/<brief_id>.json       who approved, when, their statement verbatim; earlier approvals under "replaced"
data/transcripts/<brief_id>.json     append-only; turns with time_in_call_secs and MM:SS; consent evidence
data/pieces/<brief_id>_pc_<id>.json  story, pull quotes, per-question view, citation check, markdown (contact details masked)
data/calls/<brief_id>.json           conversation id, call SID, timing, cost, last four digits only
data/audit/withdrawals.jsonl         withdrawal events: ids and external references only

A withdrawn record is { "id", "withdrawn_at", "reason": "withdrawn" } at the same path. The fixture under fixtures/ is fictional and committed; every name, company and number in it is invented, and the 555-01XX range is reserved for fiction.

The quote check: the subject's word on their words

A piece attributes quotes to a person. quote_check shows that person those quotes, each with the minute in the call it was said, and records one answer per piece: confirmed, or disputed with the quote numbers and a note of up to 500 characters. Hosted, the page at /quotes/<token> is signed with the approval secret, verified before any disk is read, answers once, expires with the approval lifetime, and shows no phone number; with a delivery channel the link goes to the operator's endpoint as a quote_check payload and the calling program never holds it. Over stdio the tool returns the message to send and a person records the answer with relayed_by. A disputed piece blocks derive_assets; the fix is a new piece from the same transcript without the disputed quotes. The page and the message both tell the subject that the whole interview can be withdrawn on request, which is what withdraw_subject does. Nothing publishes on its own either way: the check is a record for the human review, not a gate the server enforces.

Security: what the approval record is and is not

The approval record is the only thing that unlocks a dial, so it is worth being exact about what it proves.

It is: a file under data/approvals/ that says a named person (approved_by) approved contacting this brief's subject at this brief's number, with the statement they made stored verbatim, and the time. place_call and run_interview both call the same assertDialApproved in src/gate/dialGate.ts, which requires that file to exist, not be a tombstone, and name the brief's exact subject and number. A name or number that differs from the brief is refused at approve_contact, so the record can only ever cover the person on the brief. Replacing an approval keeps the earlier ones inside the record.

It is not, over stdio: an identity check, a login, or proof that a human was in the loop. There is no user authentication. approved_by and statement are whatever the host passes, and the tool descriptions tell the host that a user request which names the person and asks to contact them already counts as the approval. In a Claude session the human is the one typing that request; in any other host the operator has to make sure that is still true.

Over HTTP the hosted entry supplies the human-only surface: approve_contact records nothing and returns a signed one-time link; a person opens it, sees the name, the masked number, the client and the approver, and clicks "I approve this call"; the record that click makes (approved_via: "click") is the only kind the dial gate accepts there. Be exact about what that proves: that whoever held the link pressed the button. The calling program receives the link from approve_contact in order to hand it to a person, so it could press the button itself; the server cannot tell a browser from a program. The link is signed with a secret the caller does not hold, so it cannot mint links, but it can use the one it was given. The human step is therefore the operator's delivery of the link to a person and nobody else. A deployment that sets INTERLOGUE_APPROVAL_DELIVERY_URL closes that gap: the server posts the link to the operator's own channel and the calling program never receives it, so the click can only come from someone that channel reached.

Other facts a partner should rely on, each verified by a test:

  • Credentials are read from process.env only, never from a file by this code, never printed, and the ElevenLabs base URL is fixed so no environment value can redirect a request carrying the key.

  • Nothing reaches placeOutboundCall except place_call, after the gate. The test suite deletes every ELEVENLABS_* variable before the tools load and fails on any network call.

  • Transcripts are append-only. A piece is regenerated from its transcript, never the other way round. Withdrawal replaces, it never edits.

  • A subject's explicit refusal to be recorded stores nothing. A missing AI disclosure or recording ask is stored with a notice; the disclosure and the ask themselves live in the ElevenLabs agent's configuration, outside this repository.

  • The elision rule allows one ellipsis inside a single turn with fragments in order; the checker cannot see what was dropped between them. A reviewer should read elided quotes against the transcript.

  • A transcript can come from the fixture or from turns the host supplies, and the check confirms quotes against whatever was stored. The footer of every piece names its source so a reader knows whether a recorded call stands behind it.

The phone path

The phone path places a real call through an ElevenLabs agent over the native Twilio integration. It is two tools, because a live call outlasts one MCP tool call:

  1. place_call with brief_id. Refused unless the dial gate finds the approval for the brief's exact name and number. It triggers the outbound call, passing six dynamic variables built from the brief (subject_name, subject_role, client_name, genre, angle, question_plan), records the conversation id under data/calls/, and returns.

  2. fetch_transcript with brief_id. Waits for the call to end, fetches the conversation, normalizes it to one timestamped turn per entry, runs the consent check on the real opening, and stores the transcript append-only. A refusal stores nothing. A voicemail greeting is NO ANSWER. Each call waits up to about three minutes and answers STILL IN PROGRESS if the interview is still running; a ten-minute interview needs about four calls. If a host cancels a wait early, the next call for that brief stays under that limit.

Credentials for a local stdio run go in app/.env and are loaded by Node's own flag:

ELEVENLABS_API_KEY=...
ELEVENLABS_AGENT_ID=...
ELEVENLABS_PHONE_NUMBER_ID=...
npm run start:phone

That runs node --env-file=.env dist/src/index.js. For Claude Desktop, use the same two arguments with absolute paths. The agent itself is configured in ElevenLabs: its first message must state that it is an AI and ask permission to record, its prompt reads the six dynamic variables, and the Twilio number is imported under Phone Numbers. Endpoints used: POST /v1/convai/twilio/outbound-call and GET /v1/convai/conversations/{id}, authenticated with the xi-api-key header.

Layout

src/index.ts                  MCP server entry, stdio
src/remote.ts                 the same tools over Streamable HTTP, bearer token, one data dir per session, tenants, approval page, webhook
src/hosted.ts                 hosted-mode context and the signed approval token
src/writer/                   the server-side writers: interface, deterministic reporter, Anthropic API writer
src/phone/ingest.ts           one path from a finished conversation to a stored transcript (polling and webhook)
src/phone/webhook.ts          ElevenLabs post-call webhook signature and payload
examples/sidecar/sidecar.ts   a partner's sidecar over plain JSON-RPC, no SDK
src/tools/*.ts                one tool per file
src/gate/dialGate.ts          the only unlock for a dial
src/store/fileStore.ts        JSON persistence under data/, tombstones, audit lines
src/withdraw.ts               withdrawal: plan, preview token, execute
src/consent.ts                AI disclosure, recording ask, refusal and voicemail detection
src/generate/packet.ts        the reporter's packet for the host writer
src/generate/contract.ts      the writing contract
src/generate/markdownPiece.ts the host-piece checker and assembler
src/generate/citations.ts     the citation rule and re-check
src/generate/redact.ts        contact-detail matchers and masking
src/generate/maskPiece.ts     masking a checked piece, leak check
src/generate/reporter.ts      the deterministic fallback writer
src/questionBank.ts           question banks and angle weighting
src/phone/elevenlabs.ts       ElevenLabs REST client (outbound call, conversation fetch)
src/phone/normalize.ts        brief -> dynamic variables; conversation -> timestamped turns
fixtures/                     the fictional text-only fixture
scripts/run-spine.ts          end-to-end fallback runner over stdio
scripts/demo-hosted.ts        the hosted flow in one command over loopback HTTP, no keys
scripts/call-tool.ts          generic one-tool client over stdio
scripts/register-desktop.ts   writes the Claude Desktop config entry
test/                         node:test suite
Dockerfile, render.yaml       the hosted entry's image and blueprint
CHANGELOG.md, INTEGRATION.md  history, and the contract for another product
OPERATIONS.md, SECURITY.md    running the hosted entry; what is enforced and what is asked for

Available Tools

12 tools
approve_contactRecord human approval to contact the subjectA

A human records that InterLogue may contact exactly this person at exactly this number for this brief. This is the no-cold-outreach gate: run_interview refuses to proceed without a matching record. The name and number must match the brief. Re-approving with identical values returns the existing record; different values are refused. Next step: run_interview.

Inputs: phone (required), confirm (required), brief_id (required), statement (required), +3 more.

briefBrief the reporterA

Start here. Brief InterLogue the way an editor briefs a reporter: who the subject is, which client and product the case study is for, the topic, and the angle. The angle decides which questions get asked and what the piece emphasizes; it never changes what the subject is portrayed as having said. Saves the brief and returns a brief_id plus the question plan. Nobody is contacted at this step. Next step: approve_contact.

Inputs: angle (required), topic (required), subject_name (required), subject_role (required), +6 more.

check_citationsFact-check a piece against its transcriptA

Validates that every quoted span resolves to a subject turn at the cited timestamp. With piece_id it re-checks a stored piece. With brief_id and markdown it checks a host-written piece: section order (story, then pull quotes), every quote verbatim and timestamped, no numbers outside quotes, no repeated frames. A clean pass persists the piece as a draft for human review and returns its piece_id. A fail returns every failing span with the closest matching turn so you can fix and resubmit; nothing is persisted on a fail.

Inputs: brief_id, markdown, piece_id.

discover_contactsDiscover contacts (not in this build)A

Cut-list stub. Automated contact discovery is not implemented and will not find, suggest or look up anyone. Explains what to do instead.

Inputs: query (required).

download_recordingDownload recording (not in this build)A

Cut-list stub. No audio is fetched or stored by this build; the timestamped transcript is the record. Any transcript_id gets the same answer.

Inputs: transcript_id (required).

draft_pieceGet the reporter's packet to write the pieceA

Returns everything needed to write the published piece from the transcript: the brief and angle, the question plan, a strict writing contract, ranked verbatim quote candidates each with its timestamp, and the full timestamped transcript. You (the host) write the story first, then pull quotes, in markdown, and submit it to check_citations. Every quoted span must be verbatim from a subject turn and followed by its (MM:SS). Nothing is published until the check passes.

Inputs: brief_id (required).

fetch_transcriptFetch the call transcript (phone path, step 2 of 2)A

Step 2 of the phone path. Waits for the placed call to end (up to about three minutes per call), then fetches the transcript from ElevenLabs, normalizes it to timestamped turns, runs the consent check (AI disclosure, permission to record, the subject's yes), and stores it against the brief. If the call is still running it answers STILL IN PROGRESS: call it again, as many times as needed, until it answers TRANSCRIPT STORED. A ten-minute interview needs about four calls. Then: draft_piece.

Inputs: brief_id (required), wait_secs.

generate_pieceFallback writer: generate the piece deterministicallyA

FALLBACK writer, used when no host is writing (for example npm run spine). Inside Claude, prefer draft_piece then check_citations, where the host writes the prose. This tool assembles the customer case study deterministically from the saved transcript: the story first, pull quotes second, per-question answers third. Every quote is a verbatim span of a subject turn and carries its transcript timestamp; the piece is saved only if the citation check passes. Regenerate any time; the transcript is never edited to fit a piece. Next step: check_citations.

Inputs: brief_id (required).

place_callPlace the interview call (phone path, step 1 of 2)A

Places one real outbound call to the brief's subject through the ElevenLabs agent over Twilio. Refused unless a human approval for this exact person and number is on file. The agent opens by stating it is an AI and asking permission to record. Returns at once with the conversation id; call fetch_transcript to wait for the transcript. Requires ELEVENLABS_API_KEY, ELEVENLABS_AGENT_ID and ELEVENLABS_PHONE_NUMBER_ID in the server's environment.

Inputs: brief_id (required), confirm_dial (required).

question_templatesQuestion templates by genre (not in this build)A

Cut-list stub. There is one genre, customer case study, and its questions come from the brief tool's angle-driven plan. Any genre passed here gets the same answer.

Inputs: genre (required).

run_interviewRun the interview (text-only in this build)A

Runs the interview step of the spine on text: either a named fixture transcript or inline turns. Refuses unless a human approval for this brief's exact person and number is on file (the same gate a real dial would need). Checks that the agent disclosed it is an AI and asked permission to record at the top of the call, then saves the transcript. Transcripts are append-only. No phone call is placed in this build. Next step: generate_piece.

Inputs: brief_id (required), turns, fixture.

statusWhere is this brief in the spine?A

Reports which of brief, human approval, transcript and generated pieces exist for a brief_id, and names the next tool to call. Spine: brief -> approve_contact -> run_interview -> generate_piece -> check_citations.

Inputs: brief_id (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 12 tools

    Disambiguation4/5

    Tools mostly map to distinct steps in a linear interview spine, but there is a potential confusion between the simulated text path (run_interview) and the real phone path (place_call + fetch_transcript), and between the fallback writer (generate_piece) and the preferred writer flow (draft_piece + check_citations). Fortunately, the descriptions explicitly clarify these boundaries, which mitigates the overlap.

    Naming Consistency4/5

    The names are mostly snake_case verb_noun (e.g., approve_contact, run_interview, fetch_transcript, check_citations). A few names are nouns or noun phrases (brief, question_templates, status), which is a minor deviation but still readable and consistent with the domain.

    Tool Count3/5

    With 12 tools, the count is not unreasonable, but three stubs (question_templates, download_recording, discover_contacts) are explicitly cut-list placeholders that add no real functionality and dilute the set. The remaining tools feel appropriately scoped for the interview workflow.

    Completeness4/5

    The surface covers the full spine from brief to approval to interview to piece generation and citation checking, with status reporting. However, the stubs represent unimplemented features, and there is no tool to update or delete briefs or transcripts, though the workflow may not require them.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      C
      maintenance
      Enables revision-bound source audits with exact article fingerprinting, claim-to-source mapping, quotation verification, and immutable JSON evidence reports for prepublication review.
      9
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables writers to research and verify news stories, extract facts with their sources attached, mine SEO keywords, receive a drafting brief, and audit their finished draft for weak spots and AI-sounding phrasing. It then assembles the publishing pack, NewsArticle and FAQPage schema, and SEO validation report around the text — without writing the prose itself.
      19
      57 PyPI
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables assistants to read and write filmmaking projects: import Final Draft, WriterDuet or Fountain scripts, OCR PDFs and scans, edit scenes and elements, search the script, and create or update storyboard shots with panel cutting and reference images. Connects over remote Streamable HTTP with OAuth sign-in and 22 tools, with versioned script history and confirmation before deletions.
      MIT