This documentation describes a mock MCP server. It does not file with the IRS and does not connect to any IRS system.
Tools reference
MOCK. Does not file with the IRS, connects to no IRS system, no network. Fake data only. Every response carries a
noticefield repeating this (omitted from examples below for brevity). Examples are real output from the built server (dist/index.js, run 2026-10-23 over stdio with fake data); timestamps and in-process sequence numbers will differ on your run. Long fields are trimmed with....
All tools return one JSON text block. Failures that the server handles itself set isError: true; argument-shape failures are rejected by the MCP layer before the tool runs (message MCP error -32602: Input validation error ...).
Shared input pieces:
formType: enum941 | 944 | 4868 | 7004 | 941-X. Anything else (940, 945, 1120-S, 8868) fails with-32602 Invalid enum value.data: JSON object with the form's fields (seeforms/andlist_supported_forms). Unknown keys are rejected. Optionaldata.headerblock, see the end of this file.
list_supported_forms
Purpose: discover forms, top-level field names and required flags, header fields. Read-only. Input: {}.
Output: notice, forms[] (formType, title, period, status, description, fields[{name, required}] for forms the mock models; planned and unavailable entries have status and note), headerFields[], headerNote.
Errors: none. Field names only (no types); use forms/ docs for meanings.
Real response (trimmed):
{ "forms": [
{"formType":"941","title":"Employer's Quarterly Federal Tax Return","period":"quarterly","status":"implemented (mock)","fields":[{"name":"filer","required":true}, "..."]},
"... 944, 4868, 7004, 941-X ...",
{"formType":"943","status":"planned","note":"Agricultural employers; not implemented"},
{"formType":"940","status":"not available to us","note":"Company is not authorized/able to file this form; intentionally not exposed"},
"... 945, 1120-S, 8868 likewise ..." ],
"headerFields":[{"name":"softwareId","required":true},{"name":"etin","required":true},"..."] }
validate_return
Purpose: check a return without storing or submitting anything. Input: formType, data.
Output: formType, valid (boolean), errors[], warnings[] (each {code, path, message, basis}), computed (recomputed totals/dates; absent on SCHEMA failure), xsd {status, errors, note}. xsd.status is not_available unless the user installed official schemas (then valid/invalid; XSD errors appear as code XSD, basis IRS XSD, and flip valid).
Error cases: SCHEMA errors (wrong type, missing required, unknown key, bad EIN/SSN format) come back with valid:false, isError not set. Note: the xsd.note reads "XML not generated (return failed input validation)" for ANY invalid result, including business-rule failures.
Example, valid 941 (input in the quickstart):
{"formType":"941","valid":true,"errors":[],"warnings":[],
"computed":{"line5a_ssTax":12400,"line5c_medicareTax":2900,"line5e_total":15300,"line6_totalBeforeAdj":25300,"line12_totalAfterCredits":25300,"line13_deposits":25300,"line14_balanceDue":0,"line15_overpayment":0},
"xsd":{"status":"not_available","errors":[],"note":"Official IRS MeF XSDs are not installed (distributed only via the IRS Secure Object Repository). XML was NOT schema-validated."}}
Example, same return with EIN 12-3456789 and month1: 8000:
{"valid":false,"errors":[
{"code":"F941-032","path":"monthlyLiability","message":"Monthly liability total (24866.67) must equal line 12 (25300.00)","basis":"illustrative"},
{"code":"R0000-148","path":"filer.ein","message":"Test transmissions require an EIN beginning with 00 (mock rule; ...)","basis":"illustrative"}],
"warnings":[{"code":"MOCK-W01","path":"filer.ein","message":"This is a mock-only prototype: use obviously fake test EINs (00-0000000), never real taxpayer identifiers","basis":"illustrative"}]}
Example, shape error (quarter: 7, wages: "a"): errors like {"code":"SCHEMA","path":"quarter","message":"Number must be less than or equal to 4"}, {"code":"SCHEMA","path":"wages","message":"Expected number, received string"}.
build_return_xml
Purpose: validate, then emit ILLUSTRATIVE MeF-style XML (hand-modeled names, namespace urn:terragroupe:mock-efile:illustrative, a comment says "NOT SCHEMA-VALID"). Read-only. Input: formType, data.
Output (success): xml (string), validation {valid, errors, warnings, computed}, xsd. Note xml is produced whenever input passes shape validation, even if business-rule errors exist; check validation.valid yourself. If shape validation fails: xml: null, validation, and isError: true.
Example (trimmed):
{"xml":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- ILLUSTRATIVE, NOT SCHEMA-VALID. ... Not an IRS MeF document. Do not transmit to the IRS. -->\n<Return xmlns=\"urn:terragroupe:mock-efile:illustrative\" returnVersion=\"mock-0.2\">\n <ReturnHeader>\n <ReturnTs>2026-10-23T20:05:04.257Z</ReturnTs>\n <ReturnType>941</ReturnType>\n <TaxYr>2025</TaxYr>\n <QuarterNum>1</QuarterNum>\n ...",
"validation":{"valid":true,"errors":[],"warnings":[], "computed":{ "...": "..." }},
"xsd":{"status":"not_available","errors":[],"note":"... XML was NOT schema-validated."}}
mock_submit_return
Purpose: submit to the in-memory mock MeF. Not idempotent: each call creates a new submission. Input: formType, data, optional forceReject (boolean test hook, adds X0000-005-01 only if there are no other errors).
Behavior: shape-invalid input is NOT submitted (submitted:false, reason, validation, isError:true). Anything else is submitted and receives status:"Received"; business-rule errors do not block submission, they make the later acknowledgement Rejected.
Output: submitted, submissionId (20 digits: fake EFIN 000000 + year + 3-digit day-of-year + 7-digit sequence), status:"Received", receivedAt, transmission {messageId, headerXml, manifestXml, container{description, entries[{name, contents, bytes}]}, issues}, next.
Example (trimmed; 941 fixture):
{"submitted":true,"submissionId":"00000020262960000001","status":"Received","receivedAt":"2026-10-23T20:05:04.266Z",
"transmission":{"messageId":"00000202629600000001",
"container":{"description":"Uncompressed container zip holding compressed submission zips (shape only; no file is created)",
"entries":[{"name":"00000020262960000001.zip","contents":["manifest/manifest.xml","xml/submission.xml"],"bytes":2483}]},
"issues":[]},
"next":"Call get_acknowledgement with this submissionId"}
Without a header, the mock uses ETIN 00000, test indicator T. Nothing leaves the process.
get_acknowledgement
Purpose: fetch the mock result. Read-only apart from advancing the (unused) polling counter. Input: submissionId (exactly 20 digits, else -32602), format json (default) | xml.
Output json: submissionId, status (Processing | Accepted | Rejected), kind (StatusRecord only for Processing; Acknowledgement for final), messageId, testIndicator (T/P), mock:true, ackTimestamp, errors[], warnings[]. Output xml: {xml} illustrative acknowledgement.
Errors: unknown ID returns {"error":"Unknown submissionId ..."} with isError:true (including IDs from a previous server process).
IMPORTANT: the server as started by dist/index.js uses pollsBeforeReady = 0, so the answer is always the final Accepted/Rejected on the first call. Processing is only reachable in unit tests that construct MockMef with that option. Agents should still handle it.
Examples (real):
{"submissionId":"00000020262960000001","status":"Accepted","kind":"Acknowledgement","messageId":"00000202629600000001","testIndicator":"T","mock":true,"ackTimestamp":"2026-10-23T20:05:04.269Z","errors":[],"warnings":[]}
Duplicate resubmission of the same accepted 941:
{"submissionId":"00000020262960000002","status":"Rejected","kind":"Acknowledgement","testIndicator":"T","mock":true,
"errors":[{"code":"R0000-902-01","path":"(return)","message":"Duplicate: an accepted return already exists (submission 00000020262960000001) (mock rule)","basis":"illustrative"}],"warnings":[]}
Forced rejection: errors:[{"code":"X0000-005-01","path":"(transmission)","message":"Forced rejection requested for testing (mock rule)"}].
XML form (real, accepted case):
<AcknowledgementList xmlns="urn:terragroupe:mock-efile:illustrative" mock="true">
<Acknowledgement>
<SubmissionId>00000020262960000001</SubmissionId>
<TestCd>T</TestCd>
<AcceptanceStatusTxt>Accepted</AcceptanceStatusTxt>
<StatusTs>2026-10-23T20:05:04.269Z</StatusTs>
A Rejected XML lists ValidationErrorList/ValidationError with RuleNum, SeverityCd (Reject), ErrorMessageTxt, XPathTxt, RuleBasisTxt; warnings use SeverityCd Alert.
Production-indicator case (real): EIN 12-0000002, header testIndicator:"production", Software ID 25880001 gave Rejected with R0000-904-03 (basis cited to Pub 4164) plus warnings HDR-W10, MOCK-W01.
The optional header block (inside data)
softwareId (8 digits, first two = tax year; 94x quarterly YY99NNNN, annual YY88NNNN), etin (5 digits), testIndicator (test default | production, simulated only), atsMode, transmitterIp, signatureOption (PINNumber | BinaryAttachment8453), pinEnteredBy (ERO | Taxpayer), practitionerPin, taxpayerPin (5 digits, not all zeros), ero {efin(6), firmName}, signer {name, title, dateSigned}, attachments[{description}], achDebitAgreement. Real example of failures: signatureOption: PINNumber, pinEnteredBy: ERO, practitionerPin: "00000" on a 941 gave R0000-183-01, HDR-031, R0000-185, HDR-030. All values must be fake.