structured-extract-mcp
Allows using NVIDIA NIM as an OpenAI-compatible provider for structured extraction, sending chat-completions requests to a configured model and validating JSON outputs against Pydantic schemas.
Allows using Ollama's OpenAI-compatible endpoint as a provider for local structured extraction, sending chat-completions requests to a configured model and validating JSON outputs against Pydantic schemas.
Allows using OpenAI's chat-completions API as a provider for structured extraction, sending prompts and JSON schemas, then validating returned JSON against Pydantic models with configurable response format and retry on validation errors.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@structured-extract-mcpextract an invoice record from this text: INV-2026-1042 total $182.52"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
structured-extract
Extract a typed record from unstructured text, check it against a Pydantic model, and try again with the validation error if it does not match.
Source: https://github.com/AlonsoAlviraa/structured-extract-mcp
This repository is a small public example of two things: a provider interface around structured LLM output, and a minimal MCP server. It is meant to be read in one sitting.
Author: Alonso Alvira Ballano. License: MIT.
What it does
You give it a schema name and a piece of text (an invoice email, a job posting, a support ticket). It asks a provider for JSON, validates that JSON with Pydantic, and returns the model. If validation fails, it sends the error back to the provider and tries again, up to a fixed number of attempts. If the attempts run out, it raises. It does not fill in an empty object and call that success.
The default provider is offline. It does not call a model and it does not need an API key. A second provider speaks the OpenAI chat-completions API, so the same client can point at OpenAI, NVIDIA NIM, Ollama, or another compatible server.
Related MCP server: genpark-structured-extract-schema-validator-skill
60-second quickstart
Requires Python 3.11+ and uv. Commands assume the repository root.
uv sync
uv run extract --list-schemas
uv run extract --schema invoice examples/invoice.txt
uv run extract --schema job_posting examples/job_posting.txt
uv run extract --schema support_ticket examples/support_ticket.txt--list-schemas prints:
invoice
job_posting
support_ticketextract --schema invoice examples/invoice.txt prints the validated object. Logs go to stderr. The default level is warning, so a successful run writes only JSON to stdout. Excerpt (the file has three line items):
{
"vendor": "Northwind Supply Co.",
"invoice_number": "INV-2026-1042",
"issued_on": "2026-03-12",
"currency": "USD",
"total": "182.52",
"payment_terms": "Net 30"
}Money is a decimal string ("182.52"), not a JSON number.
The same command with examples/job_posting.txt as if it were an invoice exits 1 and prints the reason. The offline provider does not guess missing fields.
Without uv:
python -m venv .venv
source .venv/bin/activate
pip install -e .
extract --schema invoice examples/invoice.txtUsing a real model
The offline provider stays selected until you opt in. A missing key does not fall back to it.
export STRUCTURED_EXTRACT_PROVIDER=openai
export OPENAI_API_KEY=sk-...
export OPENAI_MODEL=gpt-4o-mini
# Optional. Include the /v1 prefix your server expects.
export OPENAI_BASE_URL=https://api.openai.com/v1
uv run extract --schema invoice examples/invoice.txtVariable | Default | Meaning |
|
|
|
|
| Base URL. The client posts to |
| unset | Required when the provider is |
|
| Model name sent in the request |
|
|
|
|
| Seconds for one request |
|
| Validation attempts, including the first |
|
| Standard library level. The CLI uses warning, or debug with |
json_schema sends the Pydantic schema as OpenAI strict structured output (response_format.type = json_schema, strict: true). Some local servers only accept {"type": "json_object"} or reject response_format entirely. Set STRUCTURED_EXTRACT_RESPONSE_FORMAT to match the server. The client does not downgrade on its own: if the server returns an error, that error is raised.
Ollama's OpenAI-compatible endpoint is typically:
export STRUCTURED_EXTRACT_PROVIDER=openai
export OPENAI_BASE_URL=http://127.0.0.1:11434/v1
export OPENAI_API_KEY=ollama
export OPENAI_MODEL=llama3.1
export STRUCTURED_EXTRACT_RESPONSE_FORMAT=json_objectCLI
extract --schema invoice examples/invoice.txt
extract --schema invoice - # stdin
extract --list-schemas
extract --provider openai --timeout 20 --max-attempts 2 --schema invoice note.txt
extract --verbose --schema invoice examples/invoice.txtExit codes: 0 validated JSON on stdout, 2 bad arguments or configuration (unknown schema, missing file, missing API key), 1 the provider failed or validation never passed.
MCP server
The server uses the official mcp Python SDK (MCPServer in mcp 2.x) and stdio. It exposes two tools:
list_schemas— schema namesextract(schema, text)— validated JSON text, or a tool error
Stdout is the protocol. The process logs to stderr.
uv run structured-extract-mcpThat process waits for an MCP client. Cursor (~/.cursor/mcp.json) and Claude Desktop (claude_desktop_config.json) can start it:
{
"mcpServers": {
"structured-extract": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/this/repo", "run", "structured-extract-mcp"],
"env": {
"STRUCTURED_EXTRACT_PROVIDER": "fake"
}
}
}
}Point the same block at a model by setting STRUCTURED_EXTRACT_PROVIDER to openai and adding OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL. Paths must be absolute. uv --directory is there so the server can be started from another working directory.
Architecture
flowchart TD
cli[CLI extract]
mcp[MCP stdio tools]
ext[Extractor]
reg[Schema registry]
prov{Provider}
fake[FakeProvider]
http[OpenAI-compatible HTTP]
val{Pydantic validation}
out[JSON on stdout or tool result]
fail[ExtractionFailedError]
cli --> ext
mcp --> ext
ext --> reg
ext --> prov
prov --> fake
prov --> http
ext --> val
val -->|valid| out
val -->|invalid, attempts left| ext
val -->|attempts exhausted| failA provider returns text. The extractor is the only place that decides whether that text is a successful record. Timeouts and HTTP errors skip the retry loop: there is no new validation error to show the model, and repeating the same request would spend the timeout again.
Design decisions
One method on the provider. complete(messages, schema_name, json_schema, timeout) -> str. The OpenAI client, the offline parser, and the test double (ScriptedProvider) all implement it. Tests never open a socket.
Retries are only for validation. Invalid JSON, extra fields, a string where an integer is required, a money value of "182.5", a salary range that is upside down: those go back to the model as Validation error: plus the Pydantic messages, with the bad reply as the previous assistant turn. The cap is max_attempts (default 3).
Strict models. extra="forbid" and strict=True, so "10" is not accepted as the integer 10. The JSON schema sent upstream also sets additionalProperties: false and marks every property required, which is what OpenAI strict mode asks for.
Money is text. Invoice amounts are strings with two fractional digits. JSON numbers are binary floats. The models do not check that line items sum to the total.
The fake provider is a parser for the files in examples/. Those files are emails with labels (Vendor:, Line items:, and so on). If a label is missing it raises UnrecognizedInputError. It is not used when the OpenAI provider fails.
httpx, not the OpenAI SDK. The request is a small JSON body. Any server that implements POST /chat/completions can sit behind OPENAI_BASE_URL. The API key is stripped from HTTP error text if the server echoes it.
One fence is stripped. If the whole reply is a markdown JSON fence, the fence is removed before validation. Surrounding prose is not scraped for a JSON object. If the inner text is still invalid, the attempt counts as a failure.
Logs. Info logs record the schema, the attempt, and the length of the input. They do not record the input. Validation failures are warnings. The CLI stays quiet unless something fails or you pass --verbose.
Layout
src/structured_extract/
cli.py argparse entry point
config.py environment variables
exceptions.py typed errors
extractor.py validation and retry
mcp_server.py stdio MCP server
providers.py fake, scripted, and HTTP providers
schemas.py invoice, job posting, support ticket
examples/ sample emails the fake provider can parse
tests/ pytest, no network
.github/workflows/ci.ymlPublic import surface: extract, get_schema, available_schemas, and the exception types.
Development
uv sync --extra dev
uv run ruff check .
uv run pytestCI runs those two commands on Python 3.11 and 3.12.
Limitations
The offline provider only understands the labeled layout in
examples/. Other text fails. That is intentional.HTTP 429, HTTP 5xx, connection errors, and timeouts are not retried.
There is no check that line-item totals add up to the invoice total.
The strict JSON schema helper covers these models. It is not a general converter for every keyword OpenAI strict mode rejects (
allOf, numeric constraints on some servers, and so on).temperatureis always0. A server that rejects that field needs a code change.The MCP server is stdio only. There is no authentication and no container image.
Nothing here has been measured against a model benchmark. The tests check the retry loop, the HTTP request shape, and the offline parser.
Available Tools
2 toolsextractB
Extract validated JSON for schema from text.
Uses STRUCTURED_EXTRACT_PROVIDER (default: fake). The fake provider only understands the labeled examples in this repository. Failures raise a tool error instead of returning placeholder JSON.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| schema | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does disclose real behavior: the provider dependency (STRUCTURED_EXTRACT_PROVIDER, default fake), the fake provider's limitation to repo examples, and that failures raise a tool error rather than returning placeholder JSON. However, it says nothing about the shape of a successful result or any cost/latency/auth characteristics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Short and front-loaded: the capability sentence comes first, followed by the provider caveat and error behavior. No filler, though the provider detail is somewhat implementation-flavored.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-required-param tool with no output schema, the description covers error behavior and provider caveats but omits the successful return shape and the valid values for 'schema'. An agent could call it, but not confidently about the output.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it does assign roles (JSON validated against 'schema', sourced from 'text'). It still leaves ambiguity about whether 'schema' is an inline JSON string or a schema name from list_schemas, and gives no format detail for either parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
It states a specific verb+resource: 'Extract validated JSON for schema from text'. That is clear and distinguishable from the sibling list_schemas, which enumerates rather than extracts, though the description never names the sibling to make the contrast explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use or when-not-to-use guidance, and no mention of list_schemas as the way to obtain a valid schema value. Usage is only implied by the parameter pairing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_schemasA
Return the names of the extraction schemas this server can fill.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It does disclose the return shape (names only, not full schema definitions), which is genuinely useful, but says nothing about read-only safety, auth requirements, or whether the list is static. For a zero-argument listing tool this is thin but adequate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with zero waste, front-loading the verb and the exact payload returned.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a trivial zero-param read tool with no output schema, the description covers what is returned and implies the discovery role. Only the relationship to 'extract' and the absence of any safety signal keep it from being fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes no parameters, so per the baseline there is nothing for the description to disambiguate. The description correctly signals a no-input call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: it returns the names of the extraction schemas the server can fill. The scope ('names' only) is clear, and it is implicitly distinguishable from the sibling 'extract', which fills a schema rather than enumerating them. It stops short of explicitly naming that sibling or the relationship.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is a discovery step preceding 'extract', but the description never says when to call it or that it is a prerequisite for extraction. No exclusions or alternatives are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.1.0- First observed
extract - First observed
list_schemas
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: list_schemas discovers available schemas and extract performs the extraction. There is no overlap or ambiguity between them.
Both names are lowercase snake_case, but one follows verb_noun (list_schemas) while the other is a bare verb (extract). This is a minor deviation within an otherwise consistent convention.
Only 2 tools makes the surface quite thin, though each one earns its place for a focused extraction service. It sits at the borderline of being under-scoped.
The core workflow (discover schemas, extract data) is covered, but there is no way to inspect a schema's fields or validate text without extracting, leaving users partly blind to the expected structure.
Maintenance
Related MCP Connectors
PDFs, images and messy text to schema-guaranteed JSON. try_parse runs a real extraction, no key.
Turn messy text into strict JSON schemas agents can trust (invoice, receipt, contact, resume).
Document extraction against a JSON Schema with a calibrated confidence per field; async job status.
Free, no-key MCP server: token estimates, JSON Schema conversion, MCP configs, tool search.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceHosted MCP server for structured knowledge extraction and entity enrichment, allowing users to author JSON schemas, enrich entities using multiple LLM providers, and benchmark model performance.MIT
- FlicenseNot gradedqualityBmaintenanceEnables agents to deterministically extract structured entities from input payloads and validate or coerce them against JSON schemas, returning a standardized response dossier with execution telemetry. It runs as a zero-dependency MCP server that plugs into Claude Desktop, Cursor, and other MCP clients.8-
- FlicenseNot gradedqualityBmaintenanceEnables deterministic extraction and validation of structured JSON data from inputs using a defined schema. It plugs into MCP-compatible clients like Claude Desktop, Cursor, and Windsurf to return standardized, schema-validated results.7-
- AlicenseNot gradedqualityBmaintenanceEnables agents to ask typed questions about text or JSON and receive structured answers, with tools for single requests, batch processing, model listing, and offline validation.11 npmMIT