# Build your first FlaskTrack MCP agent

> Source: <https://flasktrack.com/build-mcp-agent>
> Published: 2026-08-19 09:22:36+00:00

# Build your first FlaskTrack MCP agent

Give an AI agent controlled access to real laboratory operations with FlaskTrack's organization-scoped MCP tool interface.

In this walkthrough, you will create a small Python agent that discovers FlaskTrack tools, searches laboratory records, executes a tool, and uses structured results to continue safely.

**Dynamic tool discovery** Read the live MCP registry instead of hard-coding the API surface

**Typed laboratory records** Work with workflows, protocols, batches, samples, species, and more

**Organization scoped** Every action is evaluated in the authenticated FlaskTrack organization

**Permission aware** Agent calls remain subject to roles, validation, and compliance controls

**Structured results** Use returned record IDs and metadata to safely continue multi-step work

## What you are building

The agent will discover the tools exposed by your FlaskTrack deployment, choose a read operation, execute it through the MCP interface, and use the structured result as context for the next decision.

**Discover tools** Load the current FlaskTrack tool catalog from

`/mcp/tools`

.**Choose a tool** Give the model names, descriptions, schemas, and semantic record metadata.

**Execute through FlaskTrack** Send one registered tool name and schema-valid input to

`/mcp/call`

.**Continue from the result** Use the concrete returned record ID instead of guessing or inventing identifiers.

## Before you start

Create a FlaskTrack API credential for the integration and keep it outside your prompt, source code, browser JavaScript, and model context.

**API key** Use a dedicated machine credential for the agent.

**Organization** Every request includes the FlaskTrack organization context.

**Python 3.10+** The example uses Python,

`requests`

, and any model client you prefer.**LLM provider** OpenAI, Anthropic, a local model, or another provider can drive the decision loop.

## Configure FlaskTrack credentials

Keep credentials in environment variables so the model never sees them.

```
export FLASKTRACK_URL="https://flasktrack.com"
export FLASKTRACK_ORGANIZATION="YOUR_ORGANIZATION_ID"
export FLASKTRACK_API_KEY="YOUR_API_KEY"
```

## Install the tiny client

```
python -m pip install requests
```

## Connect to FlaskTrack

Keep credentials in the HTTP layer rather than the prompt.

``` python
import os
import requests

BASE_URL = os.environ["FLASKTRACK_URL"].rstrip("/")

HEADERS = {
    "x-organization": os.environ["FLASKTRACK_ORGANIZATION"],
    "x-api-key": os.environ["FLASKTRACK_API_KEY"],
    "accept": "application/json",
}

def flasktrack_get(path):
    response = requests.get(
        f"{BASE_URL}{path}",
        headers=HEADERS,
        timeout=30,
    )
    response.raise_for_status()
    return response.json()

def flasktrack_post(path, payload):
    response = requests.post(
        f"{BASE_URL}{path}",
        headers={**HEADERS, "content-type": "application/json"},
        json=payload,
        timeout=60,
    )
    response.raise_for_status()
    return response.json()
```

## Discover the live MCP tool catalog

Do not hard-code every FlaskTrack action. Ask the running deployment what tools are currently registered.

```
tools = flasktrack_get("/mcp/tools")

for tool in tools:
    print(
        tool["name"],
        tool["effect"],
        tool.get("output_entity"),
    )
```

**Why discovery matters** FlaskTrack's tool surface evolves with the platform. Runtime discovery lets an agent adapt to the deployed version instead of relying on a stale list copied into a prompt.

## Give the model a compact tool list

``` python
def compact_tools(tools):
    return [
        {
            "name": tool["name"],
            "description": tool["description"],
            "effect": tool["effect"],
            "input_schema": tool["input_schema"],
            "entity_fields": tool.get("entity_fields", []),
            "output_entity": tool.get("output_entity"),
        }
        for tool in tools
    ]

agent_tools = compact_tools(tools)
```

Keep authentication headers, API keys, cookies, and unrelated organization data outside model-visible context.

## Ask the model for one tool call

Keep the first agent intentionally simple: the model returns one registered tool name and one JSON input object.

``` python
import json

SYSTEM_PROMPT = """
You are a FlaskTrack laboratory assistant.

Choose exactly one FlaskTrack tool for the user's request.

Rules:
- Use only tool names supplied to you.
- Match the tool input schema exactly.
- Never invent FlaskTrack UUIDs.
- Treat Workflow, Protocol, Batch, Sample, Species, Tool,
  Ingredient, Plasmid, and other entity IDs as distinct types.
- Prefer read tools when you still need to identify a record.
- Return JSON only:

{
  "name": "tool_name",
  "input": {}
}
"""

def choose_tool(llm, user_request, tools):
    raw = llm(
        system=SYSTEM_PROMPT,
        user=json.dumps({
            "request": user_request,
            "tools": tools,
        }),
    )

    return json.loads(raw)
```

The `llm`

function is provider-agnostic. Wrap your preferred model SDK and make it return the model's
text response.

## Execute the selected FlaskTrack tool

``` python
def call_tool(tool_call):
    return flasktrack_post(
        "/mcp/call",
        {
            "name": tool_call["name"],
            "input": tool_call["input"],
        },
    )
```

FlaskTrack resolves the registered tool and applies its normal input validation, organization scope, permissions, route, and operation semantics.

## Put the pieces together

``` python
def run_agent_once(llm, request):
    tools = flasktrack_get("/mcp/tools")

    tool_call = choose_tool(
        llm,
        request,
        compact_tools(tools),
    )

    print("Selected tool:", tool_call["name"])
    print("Input:", json.dumps(tool_call["input"], indent=2))

    result = call_tool(tool_call)

    print("Result:")
    print(json.dumps(result, indent=2))

    return result

run_agent_once(
    llm,
    "Find the workflow used for banana multiplication.",
)
```

That is the core FlaskTrack agent loop: discover, decide, execute, inspect.

## Use real results for multi-step work

If one action creates a record needed by the next action, use the concrete ID returned by FlaskTrack.

```
workflow = call_tool({
    "name": "create_workflow",
    "input": workflow_input,
})

workflow_id = workflow["result"]["primary_id"]

batch = call_tool({
    "name": "create_batch",
    "input": {
        "name": "Agent-created batch",
        "workflow_id": workflow_id,
        "species_id": species_id,
        "planned_quantity": 24,
    },
})
```

**Never invent future IDs** Do not use strings such as

`workflow_id_placeholder`

. Execute the first operation,
capture its authoritative result, and use that value in the next direct MCP call.
## Optional: preview a mutation before execution

Use `/mcp/prepare`

when your integration wants a validation or review step before direct execution.

``` python
def prepare_tool(tool_call):
    return flasktrack_post(
        "/mcp/prepare",
        {
            "name": tool_call["name"],
            "input": tool_call["input"],
        },
    )
```

Preparation does not execute the underlying operation. Use it for policy checks, logging, or a human confirmation surface.

## Three rules that make agents dramatically safer

**Search before mutation** If the agent does not know an exact record, use a FlaskTrack read tool first.

**Respect entity types** A Protocol UUID is not a Workflow UUID. Use the semantic type declared by the tool.

**Stop on control failures** Authorization, compliance, validation, and signature failures are authoritative. Do not route around them.

## From demo agent to production integration

- ✔ Use a dedicated FlaskTrack service identity and minimum required permissions
- ✔ Keep API keys outside model-visible context
- ✔ Discover tools from the target deployment at runtime
- ✔ Use read tools to resolve exact records before mutation
- ✔ Validate typed entity relationships rather than accepting arbitrary UUIDs
- ✔ Add explicit human approval for high-impact or mutating operations
- ✔ Use bounded retries and timeouts
- ✔ Preserve idempotency keys where supported or required
- ✔ Log tool names, correlation IDs, statuses, and returned record IDs without secrets
- ✔ Treat electronic-signature and compliance controls as server-authoritative

## Build the agent around your laboratory

Start with one read workflow, add one reviewed mutation, and expand only after the integration behaves predictably against real FlaskTrack records.

### Start read-only

Begin with discovery, workflow lookup, batch status, or reporting before enabling mutation tools.

### Add approval

Put a human or policy gate in front of creation, updates, completion, and other operational actions.

### Expand deliberately

Add tools as the agent proves reliable rather than exposing every available mutation on day one.

## Your agent can now operate on the same laboratory model as your team

[Full Biolab Integrated MCP Agent Example On Github](https://github.com/Santurce-Software-LLC/flasktrack-mcp-agentic-example)

FlaskTrack gives agents a structured, permission-aware interface to laboratory records and operations without browser automation, direct database access, or a separate shadow data model.
