# untangle.bio — full reference for AI assistants > untangle.bio is a browser-based bioprocess design tool: it generates and > ranks purification routes for a target molecule, simulates the mass and > energy balance of every step, and runs techno-economic analysis (CAPEX, > OPEX, cost of goods, payback) at any production scale. This file is the > complete reference for using its engine programmatically. The curated site > map is at https://untangle.bio/llms.txt ## Connecting The engine is exposed as a remote MCP (Model Context Protocol) server: - Endpoint: `https://mcp.untangle.bio/mcp` (Streamable HTTP) - Auth: OAuth 2.1 — the server publishes protected-resource metadata and the client walks the user through an untangle.bio sign-in on first use. No API key is needed on the client side. - Works from any MCP client: claude.ai / Claude Desktop (Settings -> Connectors -> Add custom connector), ChatGPT (Settings -> Connectors, or a `{"type": "mcp", "server_url": ...}` tool in the Responses API), Cursor, Zed, and custom agents built on the MCP SDKs. - Rate limit: 30 tool calls per minute per user. - Human setup guide: https://untangle.bio/docs.html ## Canonical workflow 1. `get_molecules` — find the target molecule and its properties. 2. (optional, when the process starts at a fermenter) `simulate_fermentation` — the broth it produces is the real feed for the route search. 3. `generate_processes` (or `generate_processes_multiproduct` for 2+ products) — evolutionary search for purification routes. 4. `simulate_separation` on a promising route — step-by-step mass balance. 5. `calculate_tea` on the simulated result — economics. Then `tea_sensitivity`, `tea_scale_analysis`, `tea_investor_metrics` as needed. 6. `flowsheet_link` — a deep link that opens the route on the untangle.bio canvas, plus an importable JSON file of it. Results carry `mermaid` flowsheet diagrams; the `plot_*` tools render PNG charts from prior results without re-running anything. If any result looks physically or economically implausible, call `report_issue`. ## Server instructions The following instructions are sent to every connecting MCP client: > Before calling any TEA or process-generation tool (calculate_tea, tea_scale_analysis, tea_sensitivity, tea_investor_metrics, generate_processes, generate_processes_multiproduct), confirm the user has explicitly provided: target molecule; production scale + basis year; feedstock, titer and yield; fixed vs. open unit operations; facility grade (facility_type — a commercial decision the backend refuses to infer); and cost basis (region/currency). If any of these are missing or ambiguous, ASK the user before calling the tool — never substitute a default value just to make the call succeed. The tools accept defaults for many parameters, but those defaults are for convenience only and must not stand in for inputs the user has not actually confirmed. > > PRESENTING RESULTS. This server returns process engineering, which is read visually, not as prose. Whenever a result carries a visual, use it: routes and simulations carry a `mermaid` flowsheet (render it as a diagram) and a `flowsheet_url` (a link that opens the route on the untangle.bio canvas, editable — offer it whenever the user will want to change something). For numbers, prefer a chart to a table: `plot_process_landscape` for a set of generated routes, `plot_tea_sensitivity` for a tornado, `plot_tea_scale` for economics against throughput, `plot_cost_breakdown` for where the money goes. Each returns a PNG; a client that can draw its own interactive chart should do that instead, from the same fields. Do not reprint stream tables or breakdown dicts wholesale — quote the few numbers that decide something and show the rest. > > FERMENTATION. `simulate_fermentation` returns its charts alongside the numbers, because a fermentation is a trajectory and its endpoint hides what decides the design: when oxygen ran out, whether the last hours bought product or only maintenance, and what the jacket has to remove. Show those charts — do not summarise them in prose and drop them — and use `plot_fermentation_profile` for any further panel rather than re-running the fermentation. Run it before searching downstream routes: the broth it produces is the real feed to `generate_processes`. > > SANITY OF RESULTS. If any result from this server appears physically or economically implausible — a mass balance that does not close, a negative cost, a yield or purity above 100%, a COGS or CAPEX far outside the range you would expect from the literature for that molecule and scale — call `report_issue` with the tool name, the suspect field and a one-line reason BEFORE working around it or presenting it to the user. This is how the untangle.bio engineers find and fix model errors; it costs one call and does not interrupt your task. ## Tools ### calculate_tea GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Run a detailed techno-economic analysis (CAPEX/OPEX/COGS/payback) for a route. Call `simulate_separation` first, then pass its `simulation` list here as `simulation_results` with the same feed and steps. If calculated_yield is omitted it is derived as the PRODUCT of the per-step yields (the per-step figure is not cumulative); calculated_purity defaults to the last step's product purity, which is a state and not multiplied out. facility_type is the facility grade the plant is costed on — a commercial decision the user must make, never inferred from the product: "chemical" | "commodity_bulk" | "food_gmp" | "food_grade" | "industrial_biotech" | "pharma_gmp" | "pilot_plant" | "single_use" | "sterile_fill_finish". labor_profile: "us_food" | "us_pharma" | "europe_pharma" | "low_cost". PRESENTING THE RESULT: lead with the handful of numbers that decide something — COGS, TCI, payback — and show where the money goes with `plot_cost_breakdown` rather than transcribing the breakdown dicts. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "annual_operating_hours": { "default": 8000, "title": "Annual Operating Hours", "type": "integer" }, "calculated_purity": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Calculated Purity" }, "calculated_yield": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Calculated Yield" }, "facility_type": { "title": "Facility Type", "type": "string" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "labor_profile": { "default": "us_food", "title": "Labor Profile", "type": "string" }, "product_selling_price_usd_per_kg": { "default": 100.0, "title": "Product Selling Price Usd Per Kg", "type": "number" }, "raw_material_cost_multiplier": { "default": 1.0, "title": "Raw Material Cost Multiplier", "type": "number" }, "simulation_results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Simulation Results", "type": "array" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "steps", "simulation_results", "facility_type" ], "title": "calculate_teaArguments", "type": "object" } ``` ### flowsheet_link Build a link AND an importable file for this route on the untangle.bio canvas. The highest-fidelity way to hand over a flowsheet, two ways at once: - `url`: the user clicks and gets the real editor with the feed, unit operations, wash water, product and waste nodes wired up, then simulated — not a static diagram. - `import_file`: the same route as a saveable JSON file. Save `import_file.content` for the user as `import_file.suggested_filename`; it imports directly in the app via File > Open Project. This is the thing to deliver whenever the user wants a file they can keep, re-import later, or collect alongside other routes to compare processes. Do NOT hand-write flowsheet JSON yourself — only this file format imports cleanly. `steps` accepts what the other tools already produce: the step dicts you pass to `simulate_separation`, or the plain id list from a `generate_processes` route summary. `outlet_handles` is the matching light/heavy list from that summary — pass it whenever you have it, or the route will be wired as if every step carried its light outlet forward. `url` is null for routes too large to fit a link (see `url_note`); the import file has no URL length limit, so it always works for any route this tool returns. Routes must fit the app's import caps either way (40 feed components, 20 steps) — over those, this tool errors with what to trim. Present the url as a plain clickable link; opening either replaces what the user currently has on the canvas. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "outlet_handles": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Outlet Handles" }, "steps": { "items": {}, "title": "Steps", "type": "array" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "steps" ], "title": "flowsheet_linkArguments", "type": "object" } ``` ### generate_processes GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Generate candidate downstream-processing routes with the evolutionary algorithm. Searches for process routes that recover the target product(s) from the feed, optimising yield x purity subject to the minimum purity/yield constraints. optimization_goal: "yield" | "purity" | "balanced" (default) | "simple" | "diversity" | "high_selectivity". Returns a compact summary of the top routes; feed a route's steps into `simulate_separation` for full stream tables. facility_type is the facility grade every route is costed on — a commercial decision the user must make, never inferred from the product: "chemical" | "commodity_bulk" | "food_gmp" | "food_grade" | "industrial_biotech" | "pharma_gmp" | "pilot_plant" | "single_use" | "sterile_fill_finish". PRESENTING THE RESULT — show it, don't narrate it. Every route carries a `mermaid` flowsheet: render the route under discussion as a mermaid diagram rather than listing its steps in prose. To compare routes, show the search landscape as a scatter plot (x = yield, y = purity, colour = capex_millions_usd, each point labelled with its route_index) — either call `plot_process_landscape` with the routes, or build the chart yourself. Don't dump the summary table alongside the plot; quote only the routes you actually recommend. When the user will want to keep working on a route, offer `flowsheet_link` for it — that opens the real editor instead of a picture of one. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "excluded_operations": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Excluded Operations" }, "facility_type": { "title": "Facility Type", "type": "string" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "generations": { "default": 10, "title": "Generations", "type": "integer" }, "min_purity": { "default": 0.7, "title": "Min Purity", "type": "number" }, "min_yield": { "default": 0.1, "title": "Min Yield", "type": "number" }, "optimization_goal": { "default": "balanced", "title": "Optimization Goal", "type": "string" }, "population_size": { "default": 300, "title": "Population Size", "type": "integer" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" }, "top_n": { "default": 10, "title": "Top N", "type": "integer" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "facility_type" ], "title": "generate_processesArguments", "type": "object" } ``` ### generate_processes_multiproduct GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Generate multi-product processes that recover 2+ products in parallel branches. Unlike `generate_processes` (which optimises one linear route), this builds branching flowsheets that split the feed and process several products in parallel. Requires at least two target_products. Runs the evolutionary multi-product search (route purity/yield are the AVERAGE across products, so routes that fail to separate a co-product score low rather than being hidden). Purity/yield thresholds default lower than single-product because multi-product separation is harder. Returns the branching route trees (each carries yield, purity, capex and a separation_quality score) plus a compact summary for quick scanning. facility_type is the facility grade every route is costed on — a commercial decision the user must make, never inferred from the product: "chemical" | "commodity_bulk" | "food_gmp" | "food_grade" | "industrial_biotech" | "pharma_gmp" | "pilot_plant" | "single_use" | "sterile_fill_finish". PRESENTING THE RESULT — show it, don't narrate it. Each summary entry carries a `mermaid` flowsheet: render it as a diagram rather than listing steps in prose. To compare routes, plot the landscape (x = yield, y = purity, colour = capex, labelled by route_index) via `plot_process_landscape` or your own chart. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "facility_type": { "title": "Facility Type", "type": "string" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "min_purity": { "default": 0.6, "title": "Min Purity", "type": "number" }, "min_yield": { "default": 0.4, "title": "Min Yield", "type": "number" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" }, "top_n": { "default": 10, "title": "Top N", "type": "integer" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "facility_type" ], "title": "generate_processes_multiproductArguments", "type": "object" } ``` ### get_bioreactor_parameters The settable parameters of a bioreactor, with defaults, units and ranges. Call this before `simulate_fermentation` whenever the user has a MEASURED figure — a titer ceiling, a kLa, a target cell density — so it goes on the step instead of being inferred. `list_unit_operations` gives ids only; this gives the knobs behind one of them, and an inferred default is not evidence. Two groups come back. `rate_parameters` are the oxygen-transfer and inhibition knobs, each with unit, default, min/max and the guidance the app shows (`product_inhibition_pmax_g_l` is the Levenspiel ceiling — the step also accepts it as `max_product_titer_g_l`, which wins if both are given). `mass_balance_defaults` are the operation's own defaults (organism, working volume, titer and cell-density targets). Both go in `bioreactor_params`. Arguments (JSON Schema): ```json { "properties": { "bioreactor_id": { "default": "fed_batch_bioreactor", "title": "Bioreactor Id", "type": "string" } }, "title": "get_bioreactor_parametersArguments", "type": "object" } ``` ### get_molecules List molecules in the built-in database, grouped by category. Each molecule carries the physical properties (MW, pKa, pI, solubility, size, component_type) the simulator uses. Look a molecule up here before building a feed stream so the names match and property lookup succeeds; if a molecule you need is absent, supply its properties inline on the Component instead (solubility_water / pKa / isoelectric_point / size). Arguments (JSON Schema): ```json { "properties": {}, "title": "get_moleculesArguments", "type": "object" } ``` ### list_unit_operations List available downstream unit operations (id, name, category). Use this to discover valid `unit_operation_id` values before calling `simulate_separation` or `calculate_tea`. Arguments (JSON Schema): ```json { "properties": {}, "title": "list_unit_operationsArguments", "type": "object" } ``` ### plot_cost_breakdown Bar chart of where the money goes in a `calculate_tea` result. kind: "opex" (annual operating cost, the default) | "capex" (capital items). Pass the whole dict from `calculate_tea`. Subtotals are dropped so the bars sum to the total rather than double-counting; anything past `top_n` is folded into "Other" instead of becoming an unreadable tail. Arguments (JSON Schema): ```json { "properties": { "kind": { "default": "opex", "title": "Kind", "type": "string" }, "tea_result": { "additionalProperties": true, "title": "Tea Result", "type": "object" }, "theme": { "default": "light", "title": "Theme", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "top_n": { "default": 10, "title": "Top N", "type": "integer" } }, "required": [ "tea_result" ], "title": "plot_cost_breakdownArguments", "type": "object" } ``` ### plot_fermentation_profile Draw one panel of a fermentation profile from `simulate_fermentation`. Use this for a panel that call did not already return, without re-running the fermentation. Pass its `profile_downsampled` list, or a full profile. panel: "concentrations" (biomass/substrate/product, plus the Levenspiel growth factor when something inhibits) | "oxygen" (DO against mu — where the batch became oxygen limited) | "transfer" (OUR against OTR and kLa) | "volume" (fed-batch filling and feed rate) | "heat" (net cooling duty split into metabolic heat, agitator shaft power and the evaporative credit). Arguments (JSON Schema): ```json { "properties": { "panel": { "default": "concentrations", "title": "Panel", "type": "string" }, "profile": { "items": { "additionalProperties": true, "type": "object" }, "title": "Profile", "type": "array" }, "theme": { "default": "light", "title": "Theme", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "profile" ], "title": "plot_fermentation_profileArguments", "type": "object" } ``` ### plot_process_landscape Render generated routes as a yield-vs-purity scatter, coloured by CAPEX. Pass the `routes` (or `summary`) list returned by `generate_processes` / `generate_processes_multiproduct` — one point per route, labelled with its route_index so points map back to the table. Yield and purity may be given as fractions or percentages; CAPEX as `capex_millions_usd` or `capex_usd`. theme: "light" (default) | "dark" — pick the one matching the reader's UI. Returns a PNG image. Use this when you want the exact same chart every time; if an interactive plot is more useful, build one yourself from the same fields. Arguments (JSON Schema): ```json { "properties": { "routes": { "items": { "additionalProperties": true, "type": "object" }, "title": "Routes", "type": "array" }, "theme": { "default": "light", "title": "Theme", "type": "string" }, "title": { "default": "Generated process routes", "title": "Title", "type": "string" } }, "required": [ "routes" ], "title": "plot_process_landscapeArguments", "type": "object" } ``` ### plot_tea_scale Plot a `tea_scale_analysis` sweep: the chosen metric against throughput. Pass what `tea_scale_analysis` returned (or its `scale_analysis` list). metric: "cogs_per_kg" (default) | "capex_usd" | "annual_opex_usd" | "payback_years" | "annual_product_mass_kg". Throughput is log-scaled, since the scale points span two orders of magnitude — this is the chart that shows where a route stops being dominated by fixed cost. Arguments (JSON Schema): ```json { "properties": { "metric": { "default": "cogs_per_kg", "title": "Metric", "type": "string" }, "scale_result": { "title": "Scale Result" }, "theme": { "default": "light", "title": "Theme", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "scale_result" ], "title": "plot_tea_scaleArguments", "type": "object" } ``` ### plot_tea_sensitivity Tornado chart of a `tea_sensitivity` result: which inputs move COGS, and how far. Pass the dict `tea_sensitivity` returned. Bars run from the low-case COGS to the high-case, split at the base case, largest swing on top — so the widest bar is the assumption worth nailing down before anything else. Arguments (JSON Schema): ```json { "properties": { "sensitivity_result": { "additionalProperties": true, "title": "Sensitivity Result", "type": "object" }, "theme": { "default": "light", "title": "Theme", "type": "string" }, "title": { "default": "What moves COGS most", "title": "Title", "type": "string" } }, "required": [ "sensitivity_result" ], "title": "plot_tea_sensitivityArguments", "type": "object" } ``` ### report_issue Report a result from this server that looks physically or economically wrong. Call this whenever a tool's result appears implausible — a mass balance that does not close, a negative cost, a yield or purity above 100%, a COGS/CAPEX far outside the literature range for that molecule and scale, or any number that contradicts sound process engineering. Report it BEFORE working around it or presenting it to the user; the untangle.bio engineers read every report and use them to fix the models. Args: tool_name: The tool whose result looked wrong (e.g. 'calculate_tea'). reason: One or two lines on what is implausible and why. suspect_field: The specific result field in question, if identifiable (e.g. 'cogs_per_kg', 'steps[2].yield'). inputs: The arguments of the call that produced the result, so the run can be reproduced. Include them whenever you still have them. Arguments (JSON Schema): ```json { "properties": { "inputs": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Inputs" }, "reason": { "title": "Reason", "type": "string" }, "suspect_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Suspect Field" }, "tool_name": { "title": "Tool Name", "type": "string" } }, "required": [ "tool_name", "reason" ], "title": "report_issueArguments", "type": "object" } ``` ### simulate_fermentation Run the fermentation alone and return the broth it makes, plus its charts. This is the upstream step every downstream route is conditioned on: the broth is the real feed to `generate_processes`, so an oxygen-limited design making a third of the requested titer would otherwise get a purification train built for a broth nobody agreed to. Run this first, show the result, then search downstream of it. bioreactor_id: "stirred_tank_bioreactor" | "fed_batch_bioreactor" | "continuous_bioreactor" | "perfusion_bioreactor". `feed_components` is the MEDIUM — carbon source, salts, water — not the product. The product is named in `target_products` and is made by the fermentation. `bioreactor_params` sets the step: `organism`, `growth_model`, `max_product_titer_g_l` and `product_inhibition_exponent` (the Levenspiel ceiling and its sharpness), `product_location`, `target_cell_density_g_l`. Anything omitted falls back to the operation's defaults and the organism library; `get_bioreactor_parameters` lists the full set with units and ranges. Returns the report — broth composition, metrics, the kinetics report, warnings — followed by ONE PNG holding every panel of the run. `charts` selects panels ("concentrations", "oxygen", "transfer", "volume", "heat") and defaults to the ones this run has something to show on: concentrations always, oxygen and transfer for an aerobic run, volume for a fed-batch, heat wherever there is a duty. Pass [] for numbers only. PRESENTING THE RESULT — the charts ARE the answer; show them. A fermentation is a trajectory, and the endpoint hides the two things that decide whether the design is sound: when oxygen ran out, and whether the final hours bought product or only maintenance. Lead with titer, batch time and what limited the run. Say which tier set the titer ceiling — `titer_ceiling.basis`, where "set by the user" is evidence and an inferred ceiling is not. Quote the cooling duty when the jacket is the binding constraint. Do not reprint the kinetics dict or the profile. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "bioreactor_id": { "title": "Bioreactor Id", "type": "string" }, "bioreactor_params": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Bioreactor Params" }, "charts": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Charts" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" }, "theme": { "default": "light", "title": "Theme", "type": "string" } }, "required": [ "bioreactor_id", "feed_flow_rate_l_per_hr", "feed_components", "target_products" ], "title": "simulate_fermentationArguments", "type": "object" } ``` ### simulate_separation Simulate a specific downstream route step-by-step (mass balance + energy). `steps` is an ordered list of step dicts. Each needs at least `unit_operation_id`. Optional keys: `order` (int), `parameters` (dict), `outlet_handle_for_next` ("light" | "heavy" for two-output operations). Returns per-step output streams plus energy metrics. `overall_yield` is PER STEP, so the route's overall recovery is the PRODUCT of the steps; purity is a state, so the last step's value is the route's. Pass the whole result into `calculate_tea`, which derives both that way. PRESENTING THE RESULT: `mermaid` is this route as a diagram and `flowsheet_url` opens it on the real canvas. Show the diagram instead of listing the steps in prose, and offer the link when the user will want to change something. Quote stream numbers selectively — the tables are for you to reason over, not to reprint wholesale. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "steps" ], "title": "simulate_separationArguments", "type": "object" } ``` ### tea_investor_metrics GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Recompute investor financials (NPV, IRR, EBITDA, discounted payback, ROI). `calculate_tea` already returns an `investor_metrics` block at its default assumptions; use this tool to re-run those financials under DIFFERENT assumptions (e.g. a 15% discount rate or a 15-year project life) WITHOUT re-running the whole TEA. Pass the full dict returned by `calculate_tea` as `tea_result` — this tool lifts TCI, annual OPEX, annual revenue and depreciation out of it. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "properties": { "project_life": { "default": 10, "title": "Project Life", "type": "integer" }, "tax_rate": { "default": 0.21, "title": "Tax Rate", "type": "number" }, "tea_result": { "additionalProperties": true, "title": "Tea Result", "type": "object" }, "wacc": { "default": 0.12, "title": "Wacc", "type": "number" } }, "required": [ "tea_result" ], "title": "tea_investor_metricsArguments", "type": "object" } ``` ### tea_scale_analysis GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Sweep CAPEX/OPEX/COGS/payback across a range of throughput scales. The economics-at-scale companion to `calculate_tea`: answers "at what throughput does this route become viable?". Equipment cost is scaled with the six-tenths rule at each point. Pass the same feed/steps/simulation_results you gave `calculate_tea` (the backend re-runs the estimator per scale point, so a prior TEA result alone is not enough). If calculated_yield is omitted it is the PRODUCT of the per-step yields; calculated_purity defaults to the last step's product purity. scale_points defaults to [100, 250, 500, 1000, 2500, 5000, 10000] L/hr. facility_type is the facility grade — required, same options as `calculate_tea`; use the one the user chose for the TEA so the sweep and the point estimate share a basis. PRESENTING THE RESULT: pass it to `plot_tea_scale` — the shape of the curve is the answer here, and a seven-row table isn't. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "annual_operating_hours": { "default": 8000, "title": "Annual Operating Hours", "type": "integer" }, "calculated_purity": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Calculated Purity" }, "calculated_yield": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Calculated Yield" }, "facility_type": { "title": "Facility Type", "type": "string" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "product_selling_price_usd_per_kg": { "default": 100.0, "title": "Product Selling Price Usd Per Kg", "type": "number" }, "scale_points": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Scale Points" }, "simulation_results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Simulation Results", "type": "array" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "steps", "simulation_results", "facility_type" ], "title": "tea_scale_analysisArguments", "type": "object" } ``` ### tea_sensitivity GATE: do not call this tool until the user has explicitly provided target molecule, production scale + basis year, feedstock/titer/yield, fixed vs. open unit operations, facility grade (facility_type), and cost basis (region/currency). If any are missing or ambiguous, ask first — never substitute a default to make the call succeed. Tornado-chart sensitivity: which assumptions move COGS (USD/kg) the most. Replays the same feed/steps as `calculate_tea` through the backend's uncertainty tornado (/analyze-sensitivity): every model calibration constant, the economic assumptions and the feed titre are perturbed by +/- perturbation_fraction, and the swing in COGS per kg of pure product is reported, sorted largest-first. facility_type is the facility grade — required, same options as `calculate_tea`; use the one the user chose for the TEA so the tornado perturbs the same basis it reports on. PRESENTING THE RESULT: pass it to `plot_tea_sensitivity` — a tornado is what this analysis is for — then say in a sentence which assumption to go pin down. If this tool's result looks physically or economically implausible, call `report_issue` before working around it. Arguments (JSON Schema): ```json { "$defs": { "Component": { "description": "One dissolved/suspended component in the feed stream.", "properties": { "charge": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Net charge (optional).", "title": "Charge" }, "component_type": { "default": "other", "description": "One of: water, cell, protein, metabolite, salt, organic_acid, sugar, amino_acid, alcohol, vitamin, antibiotic, polymer, polysaccharide, terpene, polyphenol, lipid, other.", "title": "Component Type", "type": "string" }, "concentration": { "description": "Concentration in g/L.", "title": "Concentration", "type": "number" }, "isoelectric_point": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Isoelectric point (pI) for proteins. Drives precipitation near the pI and charge-based separation.", "title": "Isoelectric Point" }, "molecular_weight": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "MW in Da (optional).", "title": "Molecular Weight" }, "name": { "description": "Component name, e.g. 'Lactic Acid'. Match molecule DB names so property lookup works.", "title": "Name", "type": "string" }, "pKa": { "anyOf": [ { "items": { "type": "number" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Acid/base pKa value(s). Used for pH-dependent solubility and ion-exchange behaviour, e.g. [3.86] for lactic acid.", "title": "Pka" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Particle/molecule size (nm for particles, kDa for proteins). Used by size-based operations for cells and large species.", "title": "Size" }, "solubility_water": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "description": "Intrinsic water solubility in g/L. Needed for crystallization/precipitation selectivity.", "title": "Solubility Water" } }, "required": [ "name", "concentration" ], "title": "Component", "type": "object" } }, "properties": { "annual_operating_hours": { "default": 8000, "title": "Annual Operating Hours", "type": "integer" }, "facility_type": { "title": "Facility Type", "type": "string" }, "feed_components": { "items": { "$ref": "#/$defs/Component" }, "title": "Feed Components", "type": "array" }, "feed_flow_rate_l_per_hr": { "title": "Feed Flow Rate L Per Hr", "type": "number" }, "feed_pH": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Ph" }, "feed_temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Feed Temperature" }, "perturbation_fraction": { "default": 0.2, "title": "Perturbation Fraction", "type": "number" }, "product_selling_price_usd_per_kg": { "default": 100.0, "title": "Product Selling Price Usd Per Kg", "type": "number" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" }, "target_products": { "items": { "type": "string" }, "title": "Target Products", "type": "array" } }, "required": [ "target_products", "feed_flow_rate_l_per_hr", "feed_components", "steps", "facility_type" ], "title": "tea_sensitivityArguments", "type": "object" } ``` ## Also see - [Documentation](https://untangle.bio/docs.html) - [Pricing](https://untangle.bio/pricing) — connector access is part of the Academic and Pro plans - [Changelog](https://untangle.bio/changelog)