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
|
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: |
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: |
3
|
complete
|
Completer | None
|
Override the completion function (tests inject a script). |
None
|
Raises:
| Type | Description |
|---|---|
MCPcastError
|
If no valid proposal is obtained within |
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 |
True
|
force
|
bool
|
Write anyway — into a non-empty directory that holds no
|
False
|
Raises:
| Type | Description |
|---|---|
MCPcastError
|
If out_dir is a non-empty directory without a plan
file, a rendered |
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 |
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 |
True
|
headers
|
dict[str, str] | None
|
MCP request headers for the in-process client (defaults
from |
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
¶
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.
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
============== ======== ======================= ==============================
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 ( |
required |
cancelled
|
Callable[[], bool] | None
|
Polled while a URL is downloading; returning |
None
|
Raises:
| Type | Description |
|---|---|
MCPcastError
|
If the source cannot be read or parsed, if the download
exceeds |
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 |
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
|
|
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
|
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
|
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 |
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: |
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: |
None
|
Returns:
| Type | Description |
|---|---|
MCPcastPlan
|
A validated :class: |
Raises:
| Type | Description |
|---|---|
MCPcastError
|
If the plan cannot be built — no base URL, a
|
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: |
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
¶
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 |
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
¶
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 |
True
|
base_url
|
str | None
|
The |
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: |
required |
unmatched
|
Sequence[str]
|
|
()
|
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 |
None
|
cwd
|
str | PathLike[str] | None
|
Directory relative paths resolve against. |
None
|
model
|
str | None
|
Pre-fill the curation model ( |
None
|
eval_tasks
|
int | None
|
Pre-fill the Agent Readiness task count ( |
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 |
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 ( |
None
|
base_url
|
str | None
|
Pre-fill the API base URL override. |
None
|
out_dir
|
str | PathLike[str] | None
|
Pre-fill the output folder ( |
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
|
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 ( |
None
|
eval_tasks
|
int | None
|
Pre-fill the Agent Readiness task count ( |
None
|
force
|
bool
|
Pre-fill the overwrite switch ( |
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 |
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: |
None
|
cancelled
|
Callable[[], bool] | None
|
Polled after every chunk while a URL downloads
(:func: |
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
|
paths
|
Sequence[str]
|
Paths to try on each port (see :data: |
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, |
list[str]
|
is nothing to flag. |
recommended_auth¶
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
|
paths
|
Sequence[str]
|
Paths to try on each port (see :data: |
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
|
max_bytes
|
int
|
Largest body read (see :data: |
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; |
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
¶
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 |