Skip to content

MCPcast API Reference

promptise mcpcast turns an existing API (OpenAPI 3.x or Swagger 2) into a curated, safe, agent-ready MCP server, emitted as an editable project: mcpcast.plan.yaml (the source of truth), an installable <name>_mcp/ package with its own tests (regenerated from the plan), a server.py launcher, a README and the packaging scaffold. The pipeline is parse → classify (risk) → curate (LLM, optional) → review (human) → emit (code) → eval (Agent Readiness Score). Every public symbol of promptise.mcpcast is documented here; for the walkthrough, safety profiles, auth modes and the generated server's runtime contract see the MCPcast guide.

from promptise.mcpcast import SafetyProfile, mcpcast, write_project

plan = mcpcast("openapi.yaml", profile=SafetyProfile.STANDARD)
write_project(plan, "myapi-mcp")

Entry points

mcpcast() is the deterministic path (no model, no network beyond fetching a spec URL). curate() designs the surface with a model, with every post-condition enforced in code. write_project() emits the project. evaluate() scores how well a real agent can drive the result.

mcpcast

promptise.mcpcast.mcpcast(source, *, profile=SafetyProfile.READ_ONLY, base_url=None, auth=AuthMode.PASSTHROUGH, approval=None, name=None, max_tools=None)

The deterministic pipeline: load → extract → classify → plan.

No model, no network beyond fetching source when it is a URL.

Parameters:

Name Type Description Default
source str | Path | Mapping[str, Any]

OpenAPI spec as a file path, URL, JSON/YAML text, or dict.

required
profile SafetyProfile

Safety profile (default read-only).

READ_ONLY
base_url str | None

Override the API base URL declared by the spec.

None
auth AuthMode

How the generated server authenticates against the API.

PASSTHROUGH
approval ApprovalMode | None

Who approves gated calls (defaults per auth).

None
name str | None

Server slug (defaults to the spec title).

None
max_tools int | None

Optional tool budget (reads kept first).

None

Returns:

Type Description
MCPcastPlan

A validated :class:MCPcastPlan.

curate

promptise.mcpcast.curate.curate(operations, *, model='openai:gpt-5-mini', max_tools=25, profile=SafetyProfile.READ_ONLY, base_url=None, auth=AuthMode.PASSTHROUGH, approval=None, name='api', description='', spec_source=None, max_attempts=3, complete=None) async

Design the tool surface with a model, enforcing every post-condition in code.

Parameters:

Name Type Description Default
operations Iterable[Operation]

Parsed operations.

required
model Any

Model id or LangChain chat model used for curation.

'openai:gpt-5-mini'
max_tools int

Tool budget.

25
profile SafetyProfile

Safety profile applied after curation.

READ_ONLY
base_url str | None

API base URL recorded on the plan (defaults to the spec's).

None
auth AuthMode

How the generated server authenticates against the API.

PASSTHROUGH
approval ApprovalMode | None

Who approves gated calls (defaults per auth).

None
name str

Server slug recorded on the plan.

'api'
description str

One-line API description (becomes server instructions).

''
spec_source str | None

Where the spec came from (recorded on the plan).

None
max_attempts int

Proposals rejected for violations are retried with the violations as feedback, up to this many times. On the last attempt a description that still names a tool the server will not have loses the sentence that names it (see :func:apply_curation) instead of failing the run.

3
complete Completer | None

Override the completion function (tests inject a script).

None

Raises:

Type Description
MCPcastError

If no valid proposal is obtained within max_attempts, or the plan cannot be built at all (no base URL, a base_url with a credential, a spec credential passthrough cannot relay) — settled before any model call.

write_project

promptise.mcpcast.write_project(plan, out_dir, *, write_plan=True, force=False)

Write the project for plan into out_dir.

Derived files — server.py, the package, tests/conftest.py, tests/test_tools.py and README.md — are always regenerated from the plan. Under <pkg>/tools/ ownership is decided per file by the generated header (:func:_is_generated): a generated module the plan no longer has is removed; a file without the header — hand-written, or a generated module copied to another name and edited — is yours and stays, and if the plan would now write a module at its path the whole run is refused before anything is written, unless force. Scaffold files (:data:SCAFFOLD_ONCE: pyproject.toml, Dockerfile, .env.example, .gitignore) are written only when absent.

api.name fixes the package (<name>_mcp/) and the command the scaffold packages; a plan whose name changed after the first write would leave the old package on disk while pyproject.toml and the Dockerfile still ship it, so that too is refused unless force.

Pass write_plan=False when regenerating from an edited mcpcast.plan.yaml that already lives in out_dir, so hand-written comments in it survive. Returns the written paths.

Parameters:

Name Type Description Default
plan MCPcastPlan

The plan to render.

required
out_dir str | Path

Project directory (created when missing).

required
write_plan bool

Also write mcpcast.plan.yaml.

True
force bool

Write anyway — into a non-empty directory that holds no mcpcast.plan.yaml, over a tools/ module that is not ours, beside a package generated under a previous api.name. Regenerating over an existing mcpcast project (one with a plan file) never needs it.

False

Raises:

Type Description
MCPcastError

If out_dir is a non-empty directory without a plan file, a rendered tools/ module would replace a file that is not ours, or a package generated under another api.name is present — each unless force — or if a rendered file would not compile.

evaluate

promptise.mcpcast.evaluate(plan, build_server, *, model='openai:gpt-5-mini', tasks=DEFAULT_EVAL_TASKS, operations=None, live_reads=True, headers=None, complete=None, max_agent_iterations=8) async

Run the Agent Readiness evaluation.

Parameters:

Name Type Description Default
plan MCPcastPlan

The plan the server was generated from.

required
build_server Any

The generated module's build_server factory.

required
model Any

Model for the driving agent (and task generation).

'openai:gpt-5-mini'
tasks int | Sequence[EvalTask]

Number of tasks to generate, or an explicit task list.

DEFAULT_EVAL_TASKS
operations Sequence[Operation] | None

Parsed operations, used to derive mock responses from the spec's response schemas.

None
live_reads bool

Let the routes of read tools reach the real API. Whether a request goes live is decided by the tool's risk class, not its HTTP method: a GET the classifier escalated to destructive is mocked like any write. The transport expects every route where the generated server sends it — MCPCAST_BASE_URL when set (:func:base_url_override), else the operation's own server, else the plan's base URL — and a request it has no route for is answered with :data:NO_MOCK_STATUS, never a made-up success; the report names such calls.

True
headers dict[str, str] | None

MCP request headers for the in-process client (defaults from MCPCAST_EVAL_HEADERS; else a placeholder bearer token for passthrough auth, or the evaluation key for api-key). For api-key and env-token servers an evaluation-only identity is merged into the credential variables for the run (see :func:_eval_credentials) — real keys keep working.

None
complete Completer | None

Override the task-generation completion (tests).

None
max_agent_iterations int

Agent loop cap per task.

8

Raises:

Type Description
MCPcastError

When there is nothing to evaluate (no tasks, tasks that name unknown tools), or when no task could run at all — every attempt crashed before the agent answered (a rejected model credential, a server that fails to build) — since a score for a run that never happened would be a measurement of nothing.


Plan schema

The validated, versioned plan that round-trips to mcpcast.plan.yaml. MCPcastPlan enforces the invariants (unique tool names, each operation in at most one tool, never both kept and dropped, profile allows every tool's risk, gated risks carry requires_approval=True).

MCPcastPlan

promptise.mcpcast.MCPcastPlan

Bases: BaseModel

The complete, validated tool plan for one API.

gated_tools property

Tools that demand human approval.

kept_operations property

Every operation id some tool exposes.

tool_names property

Tool names in plan order.

from_document(data) classmethod

Validate an already parsed plan document (a mapping).

This is what :meth:from_yaml ends in, and what the CLI uses for a plan that :func:~promptise.mcpcast.parse.load_spec fetched from a URL or read from a file.

Raises:

Type Description
MCPcastError

If data is not a mapping or fails validation.

from_yaml(text) classmethod

Parse and validate a plan from YAML text.

Raises:

Type Description
MCPcastError

If the document is not a plan or fails validation.

load(path) classmethod

Load a plan from path.

save(path)

Write the plan to path and return it.

to_yaml()

Serialise to the mcpcast.plan.yaml text form.

tool(name)

Look up a tool by name (raises KeyError if absent).

ToolPlan

promptise.mcpcast.ToolPlan

Bases: BaseModel

One generated MCP tool.

operations property

Operation ids this tool covers, in dispatch order.

visible_params property

Parameters the agent sees (everything not hidden).

RoutePlan

promptise.mcpcast.RoutePlan

Bases: BaseModel

One upstream HTTP operation a tool can execute.

A tool with several routes dispatches to the first route whose required parameters were all supplied (in plan order), so a collapsed tool such as find_customer(id | email) can serve both GET /customers/{id} and GET /customers?email=.

base_url = None class-attribute instance-attribute

Operation-level server override (only when it differs from api.base_url).

path_params property

Placeholders that appear in the path template, in order.

required_params property

Parameter names this route requires, in declaration order.

wire(name)

The wire name for tool parameter name.

RouteParam

promptise.mcpcast.RouteParam

Bases: BaseModel

Where one parameter of a route travels on the wire.

wire_name = None class-attribute instance-attribute

The name sent to the API when it differs from the tool parameter name (e.g. a body property that collides with a path parameter is exposed as body_<name> and sent as <name>).

ParamPlan

promptise.mcpcast.ParamPlan

Bases: BaseModel

The agent-facing shape of one tool parameter.

hidden = False class-attribute instance-attribute

Hidden parameters are not exposed to the agent; default is sent on every call instead (the "param diet").

DroppedOp

promptise.mcpcast.DroppedOp

Bases: BaseModel

An operation deliberately left out of the tool surface.

ApiPlan

promptise.mcpcast.ApiPlan

Bases: BaseModel

The upstream API and how the generated server talks to it.

approval_mode property

Effective approver: explicit setting, else pending for api-key auth (clients are identified) and elicitation otherwise.

credential_is_authorization_header property

True when the credential is the Authorization header (what passthrough relays).

credential_location = 'header' class-attribute instance-attribute

Where the generated server presents the upstream credential under env-token and api-key: a request header or a query parameter. Set from the spec's effective security scheme (an apiKey scheme names its location); HTTP bearer/basic, OAuth 2 and OpenID Connect all travel in the Authorization header, the default.

credential_name = 'Authorization' class-attribute instance-attribute

The header or query parameter that carries the credential (Authorization, X-API-Key, api_key…). passthrough can only relay the caller's Authorization header, so a plan that names anything else under that mode is refused at build time.

RiskClass

promptise.mcpcast.RiskClass

Bases: str, Enum

What calling a tool can do to the world.

The classes form a severity ladder used by safety profiles and by the "never downgrade" post-condition on LLM curation:

READ (0) < WRITE (1) < DESTRUCTIVE (2) == FINANCIAL (2)

DESTRUCTIVE and FINANCIAL share the top severity: both are excluded by the standard profile and both require approval under full. Reclassifying one as the other is therefore neither an escalation nor a downgrade.

severity property

Position on the severity ladder (higher is more dangerous).

at_least(other)

True if this class is at least as severe as other.

escalate()

Return the class one level up the ladder (top classes stay put).

SafetyProfile

promptise.mcpcast.SafetyProfile

Bases: str, Enum

How much of the API an agent is allowed to reach.

============== ======== ======================= ============================== Profile read write destructive / financial ============== ======== ======================= ============================== read-only exposed not generated not generated standard exposed requires_approval not generated full exposed requires_approval exposed, requires_approval ============== ======== ======================= ==============================

allows(risk)

Whether a tool of class risk may be generated under this profile.

exclusion_reason(risk)

Human-readable reason recorded when risk is excluded by this profile.

requires_approval(risk)

Whether a generated tool of class risk must be approval-gated.

AuthMode

promptise.mcpcast.AuthMode

Bases: str, Enum

How the generated server authenticates against the upstream API.

API_KEY = 'api-key' class-attribute instance-attribute

MCP clients present an API key; each key maps to a tenant whose upstream credential is configured on the server.

ENV_TOKEN = 'env-token' class-attribute instance-attribute

Present one upstream credential from MCPCAST_UPSTREAM_TOKEN on every call — the standard pattern for a personal server launched over stdio by Claude Desktop, Claude Code or Cursor.

NONE = 'none' class-attribute instance-attribute

No credentials. Local/dev only — the server refuses to bind to a non-loopback address.

PASSTHROUGH = 'passthrough' class-attribute instance-attribute

Forward the MCP caller's Authorization header to the API (HTTP/SSE deployments where every caller brings their own token).

ApprovalMode

promptise.mcpcast.ApprovalMode

Bases: str, Enum

Who approves approval-gated tool calls.

ELICITATION = 'elicitation' class-attribute instance-attribute

Ask the human behind the calling MCP client (confirm-your-own-action).

PENDING = 'pending' class-attribute instance-attribute

Park the call in a pending store until a different human holding the approver role decides (independent four-eyes review).

MCPcastError

promptise.mcpcast.MCPcastError

Bases: Exception

Raised for an invalid spec, plan, or curation result.

valid_tool_name

promptise.mcpcast.valid_tool_name(name)

Why name cannot be a tool name, or None if it can.

RESERVED_TOOL_NAMES

Tool names that would break or hijack the generated server (approvals_list, approvals_decide, server, upstream, str, …); valid_tool_name() rejects them alongside Python keywords.

RESERVED_CREDENTIAL_HEADERS

Headers the HTTP client owns (cookie, host, content-length, transfer-encoding); ApiPlan.credential_name may not name one. ApiPlan.credential_location / credential_name record where the generated server presents the upstream credential, set by the planner from the spec's security scheme (default: the Authorization header).

scrub_text

promptise.mcpcast.schema.scrub_text(value)

value with control characters deleted and lone surrogates replaced.

Spec and model text ends up in the --review tables, the wizard, tool descriptions and the README; an \x1b[2K in a summary would blank the row a reviewer is looking at and an OSC 52 sequence would write the clipboard. Every C0/C1 control character except \n and \t is deleted. A lone UTF-16 surrogate ("\ud800", which JSON and YAML both accept) is replaced so the string can be written as UTF-8.

Parameters:

Name Type Description Default
value str

Any string from a spec, a model response or a plan file.

required

Returns:

Type Description
str

The cleaned string (value itself when nothing had to change).

is_plan_document

promptise.mcpcast.is_plan_document(data)

True if a parsed YAML/JSON document looks like an :class:MCPcastPlan rather than an OpenAPI spec.

render_validation_errors

promptise.mcpcast.schema.render_validation_errors(exc)

pydantic's error listing (title, location, message) without input_value.

A plan refused for carrying a credential in a base_url must not have that credential echoed back by the refusal — the message goes to a terminal, a CI log, a bug report. The location and the message already say which value is wrong; the plan author has the file.

is_python_keyword

promptise.mcpcast.schema.is_python_keyword(name)

True if name is a Python keyword, hard or soft, on any supported version.

SOFT_KEYWORDS

promptise.mcpcast.schema.SOFT_KEYWORDS = frozenset({'_', 'case', 'match', 'type'}) module-attribute

Soft keywords the generator treats as reserved on every Python version.

:func:keyword.issoftkeyword answers for the running interpreter only (type is soft from 3.12), so a plan generated on 3.10 would name a tool type while 3.12 named it type_op. A fixed set keeps generated names identical across the supported versions.


Parsing

Load an OpenAPI document (file path, URL, inline JSON/YAML text, or dict) and flatten it into Operation records with dereferenced schemas, parameter wire locations, OAuth scopes, deprecation flags and success response schemas.

load_spec

promptise.mcpcast.load_spec(source, *, cancelled=None)

Load an OpenAPI document from a URL, file path, raw JSON/YAML text, or dict.

Whatever the source, the result has been through :func:check_document (node budget, nesting depth, info/paths shape) and :func:scrub_strings (control characters and lone surrogates removed from every string), so nothing downstream has to defend against them.

Parameters:

Name Type Description Default
source str | Path | Mapping[str, Any]

A URL, a file path (~ is expanded), an inline JSON/YAML document, or an already-parsed mapping.

required
cancelled Callable[[], bool] | None

Polled while a URL is downloading; returning True aborts the download (the wizard wires its interrupt here).

None

Raises:

Type Description
MCPcastError

If the source cannot be read or parsed, if the download exceeds MCPCAST_FETCH_SECONDS or is cancelled, or if the document is nested too deeply or expands past MCPCAST_MAX_SPEC_NODES.

check_document

promptise.mcpcast.check_document(document, *, hint)

Refuse whole-document defects before anything walks or copies document.

Checks, in order: the node budget (MCPCAST_MAX_SPEC_NODES), the nesting depth, and that info and paths are mappings when present. Anything past this point may assume those hold. :func:load_spec runs it on every document; code that parses a document itself (the wizard's local API detection) calls it, then :func:scrub_strings, before using the result.

Parameters:

Name Type Description Default
document dict[str, Any]

The parsed document.

required
hint str

What to call it in an error (file, URL or <inline>).

required

Returns:

Type Description
dict[str, Any]

document itself.

Raises:

Type Description
MCPcastError

Naming hint and the defect.

scrub_strings

promptise.mcpcast.scrub_strings(document)

document with every string — keys and values, at any depth — passed through :func:~promptise.mcpcast.schema.scrub_text.

Spec text reaches the terminal (the --review tables, the wizard), tool descriptions and the README, so control characters other than newline and tab are deleted at this boundary: an \x1b[2K in a summary, path, parameter name or media-type key would otherwise erase the review row a human is about to approve. Lone UTF-16 surrogates ("\ud800", which json.loads and YAML escapes happily produce) are replaced so every string can be written as UTF-8. :func:load_spec applies it to every document it returns; callers that parse a document themselves apply it after :func:check_document.

Parameters:

Name Type Description Default
document Any

A parsed JSON/YAML value.

required

Returns:

Type Description
Any

A cleaned copy (scalars other than strings are returned as they are).

public_url

promptise.mcpcast.public_url(url)

url with its userinfo, query string and fragment removed.

A spec URL may carry a credential (https://user:token@host/openapi.json or ?api_key=…) so that the download is authenticated — httpx sends userinfo as HTTP Basic auth. Nothing derived from that URL may keep the secret: not the plan's base_url, not the generated config.py or README.md, not a log line. Everything that records, prints or joins a spec URL goes through this function first. Strings that are not http(s):// URLs (file paths, inline documents) are returned unchanged.

Parameters:

Name Type Description Default
url str

The spec source as the user typed it.

required

Returns:

Type Description
str

scheme://host[:port]/path for a URL; url itself otherwise.

expanded_nodes

promptise.mcpcast.expanded_nodes(document, *, limit=None)

Count the nodes of document as extraction would visit them, stopping past limit.

Every mapping value and list item counts, and a subtree reached through several YAML aliases counts once per visit — that is what copying it (surrogate scrubbing, $ref inlining) would cost. The walk is iterative, so depth cannot overflow the stack, and it stops as soon as the count passes limit, so a 1 KB alias bomb costs at most limit steps and no memory.

Parameters:

Name Type Description Default
document Any

A parsed JSON/YAML value.

required
limit int | None

Stop counting past this many nodes (default MCPCAST_MAX_SPEC_NODES, see :data:MAX_DOCUMENT_NODES).

None

Returns:

Type Description
int

The node count, or a value above limit when the document expands

int

past it.

MAX_DOCUMENT_NODES

promptise.mcpcast.parse.MAX_DOCUMENT_NODES = 2000000 module-attribute

Default ceiling on the number of nodes a parsed document may expand to.

A 20 MiB document parses to a few million nodes at most; a 1 KB YAML alias bomb (a: &a [x], b: &b [*a, *a], …) expands to billions. The count is taken after parsing, on the object graph, so it also catches aliases that share one Python object many times over — every visit counts, so a shared subtree costs what copying it would. Override with MCPCAST_MAX_SPEC_NODES.

is_url

promptise.mcpcast.is_url(source)

True if source is an http(s):// URL string (the scheme is case-insensitive).

extract_operations

promptise.mcpcast.extract_operations(spec, *, base_url=None, spec_url=None)

Extract every operation from spec, in document order.

Parameters:

Name Type Description Default
spec Mapping[str, Any]

A parsed OpenAPI 3.x or Swagger 2.x document.

required
base_url str | None

Override the base URL declared by the spec.

None
spec_url str | None

Where the spec was fetched from, used to resolve a relative servers[].url.

None

A defect inside one operation (parameters that is not a list of mappings, a requestBody.content that is not a mapping, a schema that is a string, …) never fails the whole spec: that operation is returned with :attr:Operation.unsupported_body naming the defect and the planner drops it with that reason, exactly like an unresolvable $ref. Scalars that merely have the wrong type are coerced (operationId: 5 → "5"; tags that are not a list are ignored).

Raises:

Type Description
MCPcastError

If the document has no paths, or info or paths is not a mapping — defects of the whole document.

Operation

promptise.mcpcast.Operation

Bases: BaseModel

One HTTP operation extracted from the spec.

base_url = '' class-attribute instance-attribute

Effective base URL for this operation (operation/path-level servers win over the document's).

doc_base_url = '' class-attribute instance-attribute

The document-level base URL, which becomes the plan's api.base_url.

security_scheme = None class-attribute instance-attribute

How the operation authenticates: the first alternative of its effective security requirement (operation-level overrides document-level) that the generated server can present, else the first one that resolves at all; None when the operation declares no security or names schemes the document does not define.

signal_text property

The text risk words are matched against: id, path and summary.

The free-form description is deliberately excluded — prose that merely mentions billing or deletion must not reclassify an ordinary write.

text property

Everything worth pattern-matching: id, path, summary, description.

unsupported_body = None class-attribute instance-attribute

Set when the request body uses a media type the generated server cannot send (multipart/form-data, text/plain, …), when a $ref in the operation's parameters or body cannot be resolved (external or dangling), or when the operation is malformed (parameters that is not a list of mappings, a requestBody.content or schema that is a string, …); such operations are dropped with this reason rather than emitted as tools that can never work.

param(name)

The parameter called name, or None.

ParamSpec

promptise.mcpcast.ParamSpec

Bases: BaseModel

One input of an operation, with its wire location.

wire property

The name sent to the API (wire_name when set, else name).

wire_name = None class-attribute instance-attribute

Name on the wire when it differs from name (set when a spec uses the same name in two locations, e.g. {username} in the path and username in the body — the body one is exposed as body_username).

SecurityScheme

promptise.mcpcast.SecurityScheme

Bases: BaseModel

One components.securitySchemes entry (Swagger 2: securityDefinitions), resolved.

What the planner needs to know about how an operation authenticates: where the credential travels. An apiKey scheme names a header, query parameter or cookie; HTTP bearer/basic, OAuth 2 and OpenID Connect all arrive in the Authorization header.

credential property

(location, name) the generated server must present the credential as.

None when it cannot: an API key in a cookie, mutual TLS, or an unknown scheme type. Every Authorization-borne scheme maps to ("header", "Authorization").

key instance-attribute

Its name in the components map — what security requirements refer to.

location = None class-attribute instance-attribute

For apiKey: where the key travels.

name = None class-attribute instance-attribute

For apiKey: the header or query parameter name.

scheme = None class-attribute instance-attribute

For http: the scheme, lower-cased (bearer, basic, digest…).

type instance-attribute

apiKey, http, oauth2, openIdConnect, mutualTLS or Swagger 2's basic (case-normalised).

describe()

an API key in header 'X-API-Key', HTTP bearer… for messages.

api_name_from_spec

promptise.mcpcast.api_name_from_spec(spec, source=None)

A slug for the API, from info.title.

An untitled spec is named after its source file or URL; an untitled inline document (the text itself, or the <inline> label recorded for it) has no source to name it after and becomes api — the same name whether it reaches the wizard, the CLI or :func:~promptise.mcpcast.mcpcast.

spec_base_url

promptise.mcpcast.spec_base_url(spec, override=None, *, spec_url=None)

The API base URL: override, else servers[0].url, else Swagger 2 host.

A relative server URL (/api/v3, allowed by OpenAPI and common in specs served by the API itself) is resolved against spec_url when the document was fetched from one; otherwise it is returned as-is and the planner asks for --base-url.

spec_title

promptise.mcpcast.spec_title(spec)

The spec's info.title, stripped (empty if absent or if info is not a mapping).

spec_description

promptise.mcpcast.spec_description(spec)

The spec's info.description, stripped (empty if absent or if info is not a mapping).

spec_summary_line

promptise.mcpcast.spec_summary_line(spec)

One line for the plan's api.description (and the server's instructions): the title, plus the first line of the description when it adds something ("Bookshelf API: Books, notes and search.").


Classification

Deterministic, ordered risk rules over the operation id, path and summary (method → destructive verbs → money words → POST led by a query verb with no mutating verb → write), then one escalation step per signal: an OAuth scope containing admin/root/superuser (a write: scope does not escalate), an admin/internal/sudo/impersonate path segment, deprecated: true, or a GET that names a destructive verb. The free-form description is never matched. The curator may escalate a class, never relax it.

classify

promptise.mcpcast.classify.classify(op)

Classify op and explain the decision.

classify_operation

promptise.mcpcast.classify_operation(op)

The :class:RiskClass for op (see :func:classify for the reasoning).

risk_floor

promptise.mcpcast.risk_floor(*, operation_id, method, path)

The lowest class :func:classify could assign to an operation with this wire mapping.

A plan file records a tool's method, path and operation id but not the summary, scopes or deprecated flag the classifier also read, so a plan cannot be re-classified exactly — but it can be bounded. Every rule that raises the class needs only tokens the mapping carries (the method, a destructive or money word in the id or path, an admin-style path segment), and text the plan lacks can only add tokens, so the class is never lower than what those tokens give. The one rule that lowers it — a POST led by a query verb (search, validate…) is a read — may have been satisfied by a summary the plan does not carry, so a POST whose id and path mention no mutating verb is floored at read rather than write. :class:MCPcastPlan refuses a tool declared below this floor: a DELETE can never be edited into a read.

Parameters:

Name Type Description Default
operation_id str

The route's operation id (already sanitised).

required
method str

The HTTP method.

required
path str

The path template.

required

Returns:

Type Description
RiskClass

The floor; the deterministic classification of the same operation

RiskClass

with its summary, scopes and deprecation is always at least this.

Classification

promptise.mcpcast.Classification dataclass

The outcome of classifying one operation, with its reasoning.


Planning

The --no-curate path: one tool per operation, filtered by the safety profile, with everything not generated recorded in plan.dropped with a reason.

build_plan

promptise.mcpcast.build_plan(operations, *, profile=SafetyProfile.READ_ONLY, base_url=None, auth=AuthMode.PASSTHROUGH, approval=None, name='api', description='', spec_source=None, max_tools=None, classifications=None)

Map operations 1:1 onto tools, filtered by profile.

Parameters:

Name Type Description Default
operations Iterable[Operation]

Parsed operations (see :func:~promptise.mcpcast.parse.extract_operations).

required
profile SafetyProfile

Safety profile deciding which risk classes are generated.

READ_ONLY
base_url str | None

API base URL; defaults to the one declared by the spec.

None
auth AuthMode

How the generated server authenticates against the API.

PASSTHROUGH
approval ApprovalMode | None

Who approves gated calls (defaults per auth).

None
name str

Slug used for the server name.

'api'
description str

One-line API description (becomes server instructions).

''
spec_source str | None

Where the spec came from (recorded in the plan).

None
max_tools int | None

Optional budget; reads are kept first, then spec order. Everything over budget is dropped with an explicit reason.

None
classifications dict[str, Classification] | None

Precomputed classifications keyed by operation id (computed with :func:~promptise.mcpcast.classify.classify if omitted).

None

Returns:

Type Description
MCPcastPlan

A validated :class:MCPcastPlan.

Raises:

Type Description
MCPcastError

If the plan cannot be built — no base URL, a base_url that carries a credential, or auth is passthrough while the spec authenticates with something other than the Authorization header (an API key in a custom header or a query parameter), which passthrough cannot relay.

route_base_url

promptise.mcpcast.plan.route_base_url(op, base_url)

The host a route must carry, or None when the plan's base_url serves it.

Only an operation that declares its own servers (a base that differs from the document's) is served elsewhere. When the document had no servers block and its base was resolved from the spec URL's origin, every operation shares that base — it must not be frozen into the routes, or the plan's base URL (edited in the wizard, or MCPCAST_BASE_URL at run time) would silently stop applying to them.

derive_tool_name

promptise.mcpcast.derive_tool_name(operation_id, taken=None)

getPetById → get_pet_by_id (unique within taken, if given).

The result is always a valid tool name: an operation whose id is a Python keyword (import), a soft keyword (match, type) or a name the generated server reserves (list, server, approvals_list…) is suffixed — list_op, type_op, server_op — never dropped.

example_value

promptise.mcpcast.example_value(schema, name='value')

A plausible example for one JSON Schema (used for tool examples and mocks).

The spec's own example, default or first examples entry is used when it fits the declared type — coerced across an obvious slip (example: 2019 on a string becomes "2019", "42" on an integer becomes 42), dropped when it cannot fit (a non-finite number, a value outside the enum). Otherwise the example is synthesised from the type, format and parameter name.

resolve_base_url

promptise.mcpcast.resolve_base_url(operations, override=None)

The plan's API base URL: override, else the document-level server, else the first operation's own base.

unmappable_reason

promptise.mcpcast.unmappable_reason(op, *, base_url=None, credential=None)

Why op cannot become a tool, or None if it can.

Only the wire mapping is judged here — an unsupported request body, a $ref the parser could not resolve, a path placeholder without its parameter, an own server URL that carries a credential, a required header or cookie parameter the generated server would never send, or a security scheme it cannot satisfy; the operation id never disqualifies an operation, since :func:derive_tool_name always produces a valid tool name.

Parameters:

Name Type Description Default
op Operation

The operation.

required
base_url str | None

The plan's API base URL (an own server URL equal to it is not a route-level override).

None
credential CredentialSlot | None

The slot the plan's server presents its credential in (see :func:credential_slot); when given, an operation whose scheme needs another slot, or one no server can present (an API key in a cookie), is unmappable, and a required header parameter with the slot's name counts as sent.

None

credential_slot

promptise.mcpcast.credential_slot(operations)

The one slot the plan's server presents its credential in, from the spec's schemes.

Every operation records the security scheme it resolves to (:attr:~promptise.mcpcast.parse.Operation.security_scheme); the slot the most operations need wins (ties go to spec order — the document-level scheme, which most operations inherit, normally is the majority), so a mixed API loses the minority rather than the whole surface. The Authorization header when no operation declares a presentable scheme. Operations that need a different slot are dropped by :func:unmappable_reason with that reason.

CredentialSlot

promptise.mcpcast.CredentialSlot

Bases: NamedTuple

Where the generated server presents the upstream credential.

is_authorization_header property

True for the Authorization header — the only slot passthrough can relay.

location instance-attribute

header or query.

name instance-attribute

The header or query parameter name.

describe()

header 'X-API-Key' / query parameter 'api_key' for messages.

matches(other)

Same slot (header names compare case-insensitively, query names exactly).

example_mismatch

promptise.mcpcast.example_mismatch(value, schema)

Why value does not fit schema, or None if it plausibly does.

A shallow structural check — enough to catch a model inventing a shape the API never declared (items: [{sku, quantity}] against a schema of {sku, qty}), or a spec example of the wrong JSON type (example: 2019 on a string), without re-implementing JSON Schema. Used to repair an example rather than to reject anything: an example is a hint to the agent, and a wrong one is worth replacing, not worth failing a run.

make_example

promptise.mcpcast.make_example(params, *, prefer=None)

One worked example covering every required, visible parameter.

For a collapsed tool nothing may be required on the tool itself; pass the first route's required parameters as prefer so the example still shows a call that works.


Curation

The model proposes a budgeted, collapsed, renamed, LLM-described tool surface; check_postconditions() rejects any proposal that exceeds the budget, references unknown operations, downgrades risk, hides a required parameter without a default, or keeps a deprecated operation. Violations are fed back to the model up to max_attempts times, then curate() raises MCPcastError — there is no silent fallback.

CurationResult

promptise.mcpcast.CurationResult

Bases: BaseModel

The model's complete proposal.

CuratedTool

promptise.mcpcast.CuratedTool

Bases: BaseModel

One tool as proposed by the model.

CuratedParam

promptise.mcpcast.CuratedParam

Bases: BaseModel

The model's adjustments to one parameter.

CurationViolation

promptise.mcpcast.CurationViolation

Bases: MCPcastError

A curation proposal broke one or more post-conditions.

apply_curation

promptise.mcpcast.apply_curation(result, operations, classifications, *, profile, max_tools, base_url=None, auth=AuthMode.PASSTHROUGH, approval=None, name='api', description='', spec_source=None, spec_operations=None, repair_references=False)

Turn an accepted proposal into a validated :class:MCPcastPlan.

A tool or parameter description that names a tool the plan does not expose — one the profile excluded, an operation that was dropped or merged, any snake_case form of an operation id in the spec that is not a tool — is a violation. With repair_references (curate() sets it on its last attempt) the sentences naming such a tool are removed instead; a description left empty falls back to the spec's.

Parameters:

Name Type Description Default
spec_operations Iterable[Operation] | None

Every operation in the spec, including any not passed as operations (unmappable ones); their would-be tool names count as references to tools that do not exist. Defaults to operations.

None
repair_references bool

Strip dangling tool references instead of rejecting the proposal.

False

Raises:

Type Description
CurationViolation

If any post-condition fails.

MCPcastError

If auth is passthrough and the spec's credential is not the Authorization header (see :func:~promptise.mcpcast.plan.build_plan).

check_postconditions

promptise.mcpcast.check_postconditions(result, operations, classifications, *, max_tools)

Every post-condition violation in result (empty means it passed).

Completer

Completer is the type of the complete= hook accepted by curate(), generate_tasks() and evaluate(): an async (system_prompt, user_prompt) -> str callable. Tests inject a scripted one; production code always goes through build_agent().

render_curation_prompt

promptise.mcpcast.render_curation_prompt(operations, classifications, *, max_tools, profile, api_name='api', api_description='')

The user prompt: the operation catalogue plus the budget.


Code generation

write_project() emits a real project: the <name>_mcp/ package (config, HTTP client, approval gate, build_server(), one tools module per resource), the server.py launcher (exposes build_server, a module-level server and main(argv); runs without installing), tests/ (a pytest suite through the full pipeline), README.md, and — written once — pyproject.toml, Dockerfile, .env.example and .gitignore (SCAFFOLD_ONCE). The package depends only on promptise and httpx. render_project() returns every file as {path: text}; load_generated_server() imports a written project through its launcher, dropping any previously imported copy of the package first.

render_project

promptise.mcpcast.render_project(plan)

Every generated file for plan, keyed by path relative to the project root.

Includes both the derived files (rewritten on every regeneration) and the scaffold files in :data:SCAFFOLD_ONCE (written only when absent).

load_generated_server

promptise.mcpcast.load_generated_server(server_path)

Import a generated project's server.py launcher as a module.

The module exposes build_server() (used by the Agent Readiness evaluation and by tests), main(argv) (the command line with the auth-mode bind guard) and server, which is built on first access rather than at import — importing never reads credentials or client keys. Any previously imported copy of the project's package is dropped first, so several projects with the same name can be loaded in one process (tests, --eval after regeneration).

SCAFFOLD_ONCE

promptise.mcpcast.SCAFFOLD_ONCE = frozenset({'pyproject.toml', 'Dockerfile', '.env.example', '.gitignore'}) module-attribute

Project files written when absent and never overwritten — they are yours after the first write (add a dependency, change the base image).

package_name

promptise.mcpcast.package_name(api_name)

The import package for an API name: bookshelf → bookshelf_mcp.

tool_group

promptise.mcpcast.tool_group(tool)

The tools module a tool lives in: its first tag, else the resource in its path.

/tickets/{id}/close → tickets; /admin/purge → admin; a versioned prefix (/v1/orders) is skipped; nothing usable → api. A name that would break from . import <group> — a keyword, a soft keyword, annotations, one of the package's own modules — gets a _tools suffix, and names are cut at 40 characters so the module's file name stays sane.

describe_written

promptise.mcpcast.describe_written(written, out_dir)

"bookshelf_mcp/ (7 modules), tests/ (2), server.py, README.md, pyproject.toml…".

A one-line account of a :func:write_project result for CLI and wizard summaries — grouped by area rather than a flat list of file names. The paths are the ones :func:write_project returned, so they sit under out_dir whether it was given relative (petstore-mcp) or absolute — in either spelling; a path from elsewhere is shown as it is.

render_readme

promptise.mcpcast.render_readme(plan)

Render README.md for plan.


Agent Readiness

evaluate() generates tasks (one expected tool each), drives the generated server in-process with a real build_agent() through TestClient, and scores the run: score = 0.6 × task success + 0.4 × correct-tool-selected-first, graded A (≥ 0.9), B (≥ 0.75), C (≥ 0.6), D (≥ 0.4), else F. Reads may reach the live API; writes, destructive and financial calls hit spec-derived mocks behind an auto-approver, so an evaluation never changes real data.

EvalReport

promptise.mcpcast.EvalReport

Bases: BaseModel

The Agent Readiness Score with its evidence.

never_used = Field(default_factory=list) class-attribute instance-attribute

Tools some task targeted that the agent never called.

not_covered = Field(default_factory=list) class-attribute instance-attribute

Tools no task targeted — nothing is known about them; raise the task count.

render_markdown()

The human-readable report.

render_summary()

A compact terminal summary.

EvalTask

promptise.mcpcast.EvalTask

Bases: BaseModel

One user request and the tool it is designed to exercise.

TaskResult

promptise.mcpcast.TaskResult

Bases: BaseModel

What happened when the agent attempted one task.

mocked = Field(default_factory=list) class-attribute instance-attribute

The mocked replies the agent received during this task — values it may have reused, which exist nowhere in the real API.

ToolCall

promptise.mcpcast.ToolCall

Bases: BaseModel

One tool invocation observed during a task.

details_status = None class-attribute instance-attribute

Upstream HTTP status when the tool reported one.

ConfusedPair

promptise.mcpcast.ConfusedPair

Bases: BaseModel

The agent chose chosen first when expected was the target.

CallRecorder

promptise.mcpcast.CallRecorder

Records every tool call the agent makes, grouped by task id.

all_calls property

Every recorded call across tasks.

begin(task_id)

Attribute subsequent calls to task_id.

calls_for(task_id)

The calls recorded for task_id, in order.

record(call)

Record one call under the current task.

DEFAULT_EVAL_TASKS

promptise.mcpcast.readiness.DEFAULT_EVAL_TASKS = 20 module-attribute

How many tasks an evaluation generates when no number is given (the CLI and wizard default).

generate_tasks

promptise.mcpcast.generate_tasks(plan, *, model='openai:gpt-5-mini', count=20, complete=None) async

Generate up to count tasks from the plan's tool set.

Tasks naming a tool that does not exist are discarded; ids are made unique. Raises :class:MCPcastError if the model produced no usable task.

tools_from_server

promptise.mcpcast.tools_from_server(server, *, recorder=None, headers=None) async

LangChain tools that call server in-process through TestClient.

Each call runs the full server pipeline (validation, guards, middleware, approval gate, handler). Structured errors are returned as text so the agent can react, and are recorded with their error code.

mock_transport

promptise.mcpcast.mock_transport(plan, *, operations=None, live_reads=True, base_url=None)

An httpx transport: live GET/HEAD (optional), spec-derived mocks otherwise.

Whether a request goes live is decided by the tool's risk class, not the HTTP method: only routes of read tools reach the real API (when live_reads is on); every other route — including a GET the classifier escalated — is answered by a mock. Mock responses are built from each operation's success response schema when operations are given, and echo the request otherwise. Nothing that changes data ever reaches the real API during an evaluation.

Each route is expected under exactly the base the generated server uses for it — base_url when given, else the operation's own server, else the plan's api.base_url (the resolution order of the generated upstream.py). A request that matches no route is not answered with a success: it gets :data:NO_MOCK_STATUS and is recorded on the transport's unmatched list, which :func:score reports.

Parameters:

Name Type Description Default
plan MCPcastPlan

The plan the server was generated from.

required
operations Sequence[Operation] | None

Parsed operations, for the response schemas the mocks are built from.

None
live_reads bool

Let the routes of read tools reach the real API.

True
base_url str | None

The MCPCAST_BASE_URL override in force, as the generated config.py reads it (:func:base_url_override); :func:evaluate passes the environment's value.

None

EvalTransport

promptise.mcpcast.readiness.EvalTransport

Bases: AsyncBaseTransport

The evaluation's split transport: live reads, spec-derived mocks, nothing invented.

Built by :func:mock_transport; the attribute :attr:unmatched is what :func:score needs to report requests that reached no route.

mocked = [] instance-attribute

Every reply the transport made up, in order.

unmatched = [] instance-attribute

"METHOD /path" of every request no route in the plan matched, in order.

aclose() async

Close the real transport behind the live reads.

handle_async_request(request) async

Route request: live for a read tool's route, a mock for every other route.

A request that matches no route is answered with :data:NO_MOCK_STATUS and a body saying so — never with an invented success.

base_url_override

promptise.mcpcast.readiness.base_url_override()

MCPCAST_BASE_URL exactly as the generated config.py reads it (None when unset).

The generated server sends every request to MCPCAST_BASE_URL when it is set — replacing the plan's base URL and any operation-level server — so the evaluation transport must expect the same paths, or a live read would never be recognised as one.

NO_MOCK_STATUS

promptise.mcpcast.readiness.NO_MOCK_STATUS = 502 module-attribute

HTTP status the evaluation transport answers a request it has no route for.

Not a 2xx — a fake success would grade the agent on nothing — and not a 404, which the score reads as "the example identifier does not exist upstream".

credential_slot (readiness)

promptise.mcpcast.readiness.credential_slot(plan)

Where the generated server presents the upstream credential, for a hint.

"Authorization header", "X-API-Key header" or "api_key query parameter" — from the plan's credential_location/credential_name; under passthrough always the Authorization header, which is the only thing that mode relays.

score

promptise.mcpcast.score(plan, results, *, unmatched=())

Compute the report from task results (pure; no model, no network).

Parameters:

Name Type Description Default
plan MCPcastPlan

The plan the server was generated from.

required
results Sequence[TaskResult]

One :class:TaskResult per task the agent attempted.

required
unmatched Sequence[str]

"METHOD /path" of the upstream requests the evaluation transport had no route for (:attr:EvalTransport.unmatched); each is a call that was neither live nor mocked and is reported as such rather than folded into the grade silently.

()

grade_for

promptise.mcpcast.grade_for(score)

Letter grade for a score in [0, 1].

write_eval

promptise.mcpcast.write_eval(report, tasks, out_dir)

Write eval/tasks.yaml and eval/report.md under out_dir.


Guided setup

promptise.mcpcast.wizard is the terminal wizard behind promptise mcpcast with no arguments (see Guided Setup). run_wizard() opens it; the plain helpers it is built from carry no UI state and are importable on their own.

run_wizard

promptise.mcpcast.wizard.run_wizard(spec=None, *, base_url=None, out_dir=None, cwd=None, model=None, eval_tasks=None, force=False)

Open the guided setup in the current terminal and return what it wrote.

Parameters:

Name Type Description Default
spec str | None

Pre-fill the OpenAPI source.

None
base_url str | None

Pre-fill the API base URL override.

None
out_dir str | PathLike[str] | None

Pre-fill the output folder (default <name>-mcp).

None
cwd str | PathLike[str] | None

Directory relative paths resolve against.

None
model str | None

Pre-fill the curation model (None: :data:DEFAULT_MODEL).

None
eval_tasks int | None

Pre-fill the Agent Readiness task count (None: :data:~promptise.mcpcast.readiness.DEFAULT_EVAL_TASKS).

None
force bool

Pre-fill the overwrite switch.

False

Returns:

Name Type Description
The WizardResult | None
WizardResult | None

was quit with Ctrl+Q after writing — or None when nothing was written.

MCPcastWizard

promptise.mcpcast.wizard.MCPcastWizard

Bases: App[WizardResult | None]

The guided setup.

Parameters:

Name Type Description Default
spec str | None

Pre-fill the OpenAPI source (promptise mcpcast SPEC --interactive).

None
base_url str | None

Pre-fill the API base URL override.

None
out_dir str | PathLike[str] | None

Pre-fill the output folder (--out); default <name>-mcp.

None
cwd str | PathLike[str] | None

Directory relative paths resolve against (default: the process cwd).

None
completer Completer | None

Override the model completion used by curation and the evaluation (tests inject a script; production leaves it None).

None
fetch Fetcher | None

Override the HTTP fetch used by local API detection (tests).

None
auto_detect bool

Look for a running local API when the spec step opens empty (off in tests).

True
model str | None

Pre-fill the curation model (--model); None is :data:DEFAULT_MODEL.

None
eval_tasks int | None

Pre-fill the Agent Readiness task count (--eval-tasks); None is the wizard's default.

None
force bool

Pre-fill the overwrite switch (--force).

False

WizardResult

promptise.mcpcast.wizard.WizardResult dataclass

What the wizard wrote — the last write, which is what is on disk.

command instance-attribute

The equivalent non-interactive command (see :func:equivalent_command).

eval_requested = False class-attribute instance-attribute

The evaluation was switched on; with report still None it did not complete (the wizard was quit while it ran, or it failed).

report = None class-attribute instance-attribute

The Agent Readiness report, when the evaluation was run.

WizardSettings

promptise.mcpcast.wizard.WizardSettings dataclass

Everything the wizard collects — one field per promptise mcpcast flag.

base_url = '' class-attribute instance-attribute

The effective API base URL; base_url_override says whether the user changed it.

default_out_dir property

The output folder when none was typed: <name>-mcp, the CLI's --out default.

effective_budget property

The budget passed to the planner.

max_tools = None class-attribute instance-attribute

None means the CLI default: 25 under curation, unlimited offline.

ParsedSpec

promptise.mcpcast.wizard.ParsedSpec dataclass

An OpenAPI document loaded and classified, ready to be planned.

base_url instance-attribute

The effective API base URL (override, else the spec's, else the fetch origin).

declared_base_url = '' class-attribute instance-attribute

The base URL the document itself declares (servers[0], the Swagger 2 host, or the fetch origin); empty when it declares no usable one. A typed base URL that differs from it is an override (--base-url).

label instance-attribute

What the plan records as spec_source ("<inline>" for pasted text).

risk_counts property

How many operations fall in each risk class (all four keys present).

source instance-attribute

Where the document came from, as the user typed it — minus credentials (see :func:public_source): a path, a URL or inline text.

load(source, *, base_url=None, document=None, cancelled=None) classmethod

Load, extract and classify source.

Parameters:

Name Type Description Default
source str

File path, URL or inline text. A URL may carry credentials (userinfo, query string); they are used for the fetch and stripped from everything that is recorded.

required
base_url str | None

Override the API base URL declared by the document. Applied when the operations are extracted, exactly as the CLI's --base-url is.

None
document dict[str, Any] | None

The already-loaded document (a detected local API), so source is only recorded, not fetched again. It goes through the same checks and the same scrub as a fetched one (:func:~promptise.mcpcast.parse.check_document, :func:~promptise.mcpcast.parse.scrub_strings): the node and depth caps hold, and no control character survives.

None
cancelled Callable[[], bool] | None

Polled after every chunk while a URL downloads (:func:~promptise.mcpcast.parse.load_spec); True abandons the download. The wizard passes its load's cancellation flag, so Ctrl+Q — or a newer load — stops a slow server's transfer within one chunk instead of waiting for it to finish.

None

Raises:

Type Description
MCPcastError

when the document cannot be loaded or the download was cancelled, when it breaks a document cap, or when it is an mcpcast plan rather than an OpenAPI document.

summary()

"Bookshelf API v1.0 — 9 operations: 5 read · 2 write · 2 destructive".

Terminal-safe: the title and version are spec text (see :func:_console_safe).

with_base_url(base_url)

This document re-extracted with base_url as the override (None = as declared).

The CLI applies --base-url when it extracts the operations; doing the same when the wizard's base URL field changes keeps the written project identical to what the equivalent command produces. No fetch: the loaded document is reused as it is — it was checked and scrubbed when it was loaded.

preview_profile

promptise.mcpcast.wizard.preview_profile(parsed, profile)

Count what :func:~promptise.mcpcast.plan.build_plan would keep under profile.

Runs the deterministic planner, so the numbers are exact for the offline path and an upper bound for curation (which may merge or drop tools).

detect_local_apis

promptise.mcpcast.wizard.detect_local_apis(*, ports=PROBE_PORTS, paths=PROBE_PATHS, timeout=0.4, fetch=None)

The candidates :func:probe_local_apis finds, with its default budget and caps.

Parameters:

Name Type Description Default
ports Sequence[int]

Ports to probe (see :data:PROBE_PORTS).

PROBE_PORTS
paths Sequence[str]

Paths to try on each port (see :data:PROBE_PATHS).

PROBE_PATHS
timeout float

Connect timeout per request, in seconds.

0.4
fetch Fetcher | None

Override the HTTP fetch (tests inject a stub).

None

Returns:

Type Description
list[Candidate]

Candidates in port order.

review_warnings

promptise.mcpcast.wizard.review_warnings(plan)

Things a reviewer should look at first — computed from the plan, not guessed.

  • A description that names a tool the plan does not expose. The classic misread: the model writes "to remove it use delete_book" for an operation the safety profile excluded, and the agent goes looking for a tool that does not exist.
  • Parameters hidden from the agent (sent as fixed defaults), which your users may need to set.
  • A tool whose parameter schemas are too large to advertise in full (see :func:~promptise.mcpcast.emit.trimmed_schemas).

Returns:

Type Description
list[str]

One line per finding, "<tool>: <what to check>"; empty when there

list[str]

is nothing to flag.

promptise.mcpcast.wizard.recommended_auth(usage)

The auth mode that fits usage (see :class:Usage).

equivalent_command

promptise.mcpcast.wizard.equivalent_command(s)

The non-interactive promptise mcpcast command that reproduces s.

Only flags that differ from the CLI defaults are included, so the line stays short; it is shown on the last step and printed after the wizard exits. Arguments are quoted for the current platform's shell (see :func:quote_argument).

quote_argument

promptise.mcpcast.wizard.quote_argument(part, *, windows=None)

Quote one command-line argument for the shell the user will paste it into.

POSIX shells get :func:shlex.quote. On Windows (os.name == "nt" unless windows says otherwise) single quotes mean nothing to cmd.exe, so an argument that needs quoting is wrapped in double quotes — which both cmd.exe and PowerShell accept — with embedded quotes backslash-escaped.

probe_local_apis

promptise.mcpcast.wizard.probe_local_apis(*, ports=PROBE_PORTS, paths=PROBE_PATHS, timeout=0.4, read_timeout=2.0, budget=DETECT_BUDGET, max_bytes=DETECT_MAX_BYTES, fetch=None, cancelled=None)

Look for OpenAPI documents served by APIs running on this machine.

Probes http://127.0.0.1:<port><path> for every port and path (first hit per port wins) and keeps the ones that parse as an OpenAPI document with at least one operation. Loopback only, GET only, redirects not followed, bodies parsed strictly (never fetched or read as a path), capped at max_bytes and held to the node budget and nesting depth :func:~promptise.mcpcast.parse.load_spec enforces, every string scrubbed of terminal control characters (see :func:_parse_document); whatever a probed service answers, a malformed or hostile body is skipped, never raised. Probing stops once budget seconds have passed — the ports not reached are reported, not silently dropped.

Parameters:

Name Type Description Default
ports Sequence[int]

Ports to probe (see :data:PROBE_PORTS).

PROBE_PORTS
paths Sequence[str]

Paths to try on each port (see :data:PROBE_PATHS).

PROBE_PATHS
timeout float

Connect timeout per request, in seconds.

0.4
read_timeout float

Timeout per socket read, in seconds.

2.0
budget float

Total seconds for the whole probe (see :data:DETECT_BUDGET).

DETECT_BUDGET
max_bytes int

Largest body read (see :data:DETECT_MAX_BYTES).

DETECT_MAX_BYTES
fetch Fetcher | None

Override the HTTP fetch (tests inject a stub).

None
cancelled Callable[[], bool] | None

Polled between probes and, with the built-in fetch, after every chunk of a body; True stops the probe early.

None

Returns:

Name Type Description
The Detection

public_source

promptise.mcpcast.wizard.public_source(source)

source with nothing secret in it: a URL loses its userinfo, query and fragment.

Credentials in a spec URL (https://user:token@host/… or ?api_key=…) are used for the fetch only. What the wizard shows, what the plan records as spec_source and what the equivalent command repeats is this form. Paths and inline documents are returned unchanged. Never raises, whatever the string looks like.

Candidate, Detection, ProfilePreview, Usage

promptise.mcpcast.wizard.Candidate dataclass

A running local API that serves an OpenAPI document.

document = field(default_factory=dict, compare=False, repr=False) class-attribute instance-attribute

The document as fetched, so picking a candidate needs no second request.

promptise.mcpcast.wizard.Detection dataclass

What :func:probe_local_apis found, and what it did not get to.

candidates instance-attribute

Running local APIs serving an OpenAPI document, in port order.

elapsed = 0.0 class-attribute instance-attribute

Seconds the probe took.

skipped_ports = () class-attribute instance-attribute

Ports not (fully) probed because the time budget ran out or the probe was cancelled.

skipped_line()

"Stopped after 10 s: ports 8888, 9000 were not checked." or "".

promptise.mcpcast.wizard.ProfilePreview dataclass

What a safety profile would generate from a parsed spec.

excluded instance-attribute

Operations not exposed (by the profile, deprecated, or unmappable).

gated instance-attribute

Tools that require human approval.

line()

A one-line description for a menu row.

promptise.mcpcast.wizard.Usage

Bases: str, Enum

How the generated server will be used — the question that decides the auth mode.

OPEN = 'open' class-attribute instance-attribute

The API needs no credentials (open, or local development only).

PERSONAL = 'personal' class-attribute instance-attribute

A desktop client (Claude Desktop, Claude Code, Cursor) launches it over stdio.

SHARED = 'shared' class-attribute instance-attribute

A shared HTTP deployment where every caller brings their own token.

TENANTS = 'tenants' class-attribute instance-attribute

A multi-tenant product: MCP clients present API keys, one per tenant.

Constants

DEFAULT_MODEL (the curation model when none is chosen), STEP_NAMES (the seven steps in order), PROBE_PORTS and PROBE_PATHS (the loopback ports and document paths local API detection looks at).


Source files

File Purpose
src/promptise/mcpcast/__init__.py Package exports and the deterministic mcpcast() entry point (load → extract → classify → plan)
src/promptise/mcpcast/schema.py The plan schema: MCPcastPlan and its Pydantic models, the RiskClass / SafetyProfile / AuthMode / ApprovalMode enums, plan invariants, YAML round-trip, MCPcastError
src/promptise/mcpcast/parse.py OpenAPI 3.x / Swagger 2 loading (load_spec, is_url), spec metadata (spec_title, spec_description, spec_base_url, api_name_from_spec) and operation extraction with local $ref inlining (extract_operations, Operation, ParamSpec)
src/promptise/mcpcast/classify.py Deterministic, ordered risk classification with escalation signals (classify, classify_operation, Classification)
src/promptise/mcpcast/plan.py Deterministic planning: one tool per operation, profile filtering, tool budget, snake_case naming and worked examples (build_plan, derive_tool_name, example_value, make_example)
src/promptise/mcpcast/curate.py LLM-assisted tool design with every post-condition enforced in code (curate, check_postconditions, apply_curation, render_curation_prompt, CurationResult, CuratedTool, CuratedParam, CurationViolation)
src/promptise/mcpcast/emit.py Code generation from the plan: the <name>_mcp/ package, the server.py launcher, tests/, README.md and the scaffold files (render_project, write_project, load_generated_server, package_name, tool_group, SCAFFOLD_ONCE, describe_written)
src/promptise/mcpcast/readiness.py Agent Readiness Score: task generation, in-process agent evaluation through TestClient, spec-derived upstream mocks, scoring, grading and the eval/ report (evaluate, generate_tasks, tools_from_server, mock_transport, EvalTransport, base_url_override, NO_MOCK_STATUS, credential_slot, score, grade_for, write_eval, EvalReport, EvalTask, TaskResult, ToolCall, ConfusedPair)
src/promptise/mcpcast/wizard.py The guided setup: the Textual terminal wizard behind promptise mcpcast with no arguments, plus the plain helpers it is built from (run_wizard, MCPcastWizard, ParsedSpec, preview_profile, detect_local_apis, review_warnings, recommended_auth, equivalent_command, WizardResult)
src/promptise/mcpcast/_llm.py Private LLM plumbing shared by curation and readiness: every model call goes through build_agent(); a scripted Completer can be injected by tests (complete=), never by production code