{"slug": "build-your-first-flasktrack-mcp-agent", "title": "Build your first FlaskTrack MCP agent", "summary": "FlaskTrack released a walkthrough for building an MCP agent that gives AI models controlled access to laboratory operations, using dynamic tool discovery from the /mcp/tools endpoint and schema-valid execution via /mcp/call. The agent, written in Python 3.10+ with the requests library, reads the live tool catalog, selects a read operation, and uses structured results with record IDs to continue multi-step work, with all actions scoped to the authenticated FlaskTrack organization and subject to roles, validation, and compliance controls.", "body_md": "# Build your first FlaskTrack MCP agent\n\nGive an AI agent controlled access to real laboratory operations with FlaskTrack's organization-scoped MCP tool interface.\n\nIn 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.\n\n**Dynamic tool discovery** Read the live MCP registry instead of hard-coding the API surface\n\n**Typed laboratory records** Work with workflows, protocols, batches, samples, species, and more\n\n**Organization scoped** Every action is evaluated in the authenticated FlaskTrack organization\n\n**Permission aware** Agent calls remain subject to roles, validation, and compliance controls\n\n**Structured results** Use returned record IDs and metadata to safely continue multi-step work\n\n## What you are building\n\nThe 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.\n\n**Discover tools** Load the current FlaskTrack tool catalog from\n\n`/mcp/tools`\n\n.**Choose a tool** Give the model names, descriptions, schemas, and semantic record metadata.\n\n**Execute through FlaskTrack** Send one registered tool name and schema-valid input to\n\n`/mcp/call`\n\n.**Continue from the result** Use the concrete returned record ID instead of guessing or inventing identifiers.\n\n## Before you start\n\nCreate a FlaskTrack API credential for the integration and keep it outside your prompt, source code, browser JavaScript, and model context.\n\n**API key** Use a dedicated machine credential for the agent.\n\n**Organization** Every request includes the FlaskTrack organization context.\n\n**Python 3.10+** The example uses Python,\n\n`requests`\n\n, and any model client you prefer.**LLM provider** OpenAI, Anthropic, a local model, or another provider can drive the decision loop.\n\n## Configure FlaskTrack credentials\n\nKeep credentials in environment variables so the model never sees them.\n\n```\nexport FLASKTRACK_URL=\"https://flasktrack.com\"\nexport FLASKTRACK_ORGANIZATION=\"YOUR_ORGANIZATION_ID\"\nexport FLASKTRACK_API_KEY=\"YOUR_API_KEY\"\n```\n\n## Install the tiny client\n\n```\npython -m pip install requests\n```\n\n## Connect to FlaskTrack\n\nKeep credentials in the HTTP layer rather than the prompt.\n\n``` python\nimport os\nimport requests\n\nBASE_URL = os.environ[\"FLASKTRACK_URL\"].rstrip(\"/\")\n\nHEADERS = {\n    \"x-organization\": os.environ[\"FLASKTRACK_ORGANIZATION\"],\n    \"x-api-key\": os.environ[\"FLASKTRACK_API_KEY\"],\n    \"accept\": \"application/json\",\n}\n\ndef flasktrack_get(path):\n    response = requests.get(\n        f\"{BASE_URL}{path}\",\n        headers=HEADERS,\n        timeout=30,\n    )\n    response.raise_for_status()\n    return response.json()\n\ndef flasktrack_post(path, payload):\n    response = requests.post(\n        f\"{BASE_URL}{path}\",\n        headers={**HEADERS, \"content-type\": \"application/json\"},\n        json=payload,\n        timeout=60,\n    )\n    response.raise_for_status()\n    return response.json()\n```\n\n## Discover the live MCP tool catalog\n\nDo not hard-code every FlaskTrack action. Ask the running deployment what tools are currently registered.\n\n```\ntools = flasktrack_get(\"/mcp/tools\")\n\nfor tool in tools:\n    print(\n        tool[\"name\"],\n        tool[\"effect\"],\n        tool.get(\"output_entity\"),\n    )\n```\n\n**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.\n\n## Give the model a compact tool list\n\n``` python\ndef compact_tools(tools):\n    return [\n        {\n            \"name\": tool[\"name\"],\n            \"description\": tool[\"description\"],\n            \"effect\": tool[\"effect\"],\n            \"input_schema\": tool[\"input_schema\"],\n            \"entity_fields\": tool.get(\"entity_fields\", []),\n            \"output_entity\": tool.get(\"output_entity\"),\n        }\n        for tool in tools\n    ]\n\nagent_tools = compact_tools(tools)\n```\n\nKeep authentication headers, API keys, cookies, and unrelated organization data outside model-visible context.\n\n## Ask the model for one tool call\n\nKeep the first agent intentionally simple: the model returns one registered tool name and one JSON input object.\n\n``` python\nimport json\n\nSYSTEM_PROMPT = \"\"\"\nYou are a FlaskTrack laboratory assistant.\n\nChoose exactly one FlaskTrack tool for the user's request.\n\nRules:\n- Use only tool names supplied to you.\n- Match the tool input schema exactly.\n- Never invent FlaskTrack UUIDs.\n- Treat Workflow, Protocol, Batch, Sample, Species, Tool,\n  Ingredient, Plasmid, and other entity IDs as distinct types.\n- Prefer read tools when you still need to identify a record.\n- Return JSON only:\n\n{\n  \"name\": \"tool_name\",\n  \"input\": {}\n}\n\"\"\"\n\ndef choose_tool(llm, user_request, tools):\n    raw = llm(\n        system=SYSTEM_PROMPT,\n        user=json.dumps({\n            \"request\": user_request,\n            \"tools\": tools,\n        }),\n    )\n\n    return json.loads(raw)\n```\n\nThe `llm`\n\nfunction is provider-agnostic. Wrap your preferred model SDK and make it return the model's\ntext response.\n\n## Execute the selected FlaskTrack tool\n\n``` python\ndef call_tool(tool_call):\n    return flasktrack_post(\n        \"/mcp/call\",\n        {\n            \"name\": tool_call[\"name\"],\n            \"input\": tool_call[\"input\"],\n        },\n    )\n```\n\nFlaskTrack resolves the registered tool and applies its normal input validation, organization scope, permissions, route, and operation semantics.\n\n## Put the pieces together\n\n``` python\ndef run_agent_once(llm, request):\n    tools = flasktrack_get(\"/mcp/tools\")\n\n    tool_call = choose_tool(\n        llm,\n        request,\n        compact_tools(tools),\n    )\n\n    print(\"Selected tool:\", tool_call[\"name\"])\n    print(\"Input:\", json.dumps(tool_call[\"input\"], indent=2))\n\n    result = call_tool(tool_call)\n\n    print(\"Result:\")\n    print(json.dumps(result, indent=2))\n\n    return result\n\nrun_agent_once(\n    llm,\n    \"Find the workflow used for banana multiplication.\",\n)\n```\n\nThat is the core FlaskTrack agent loop: discover, decide, execute, inspect.\n\n## Use real results for multi-step work\n\nIf one action creates a record needed by the next action, use the concrete ID returned by FlaskTrack.\n\n```\nworkflow = call_tool({\n    \"name\": \"create_workflow\",\n    \"input\": workflow_input,\n})\n\nworkflow_id = workflow[\"result\"][\"primary_id\"]\n\nbatch = call_tool({\n    \"name\": \"create_batch\",\n    \"input\": {\n        \"name\": \"Agent-created batch\",\n        \"workflow_id\": workflow_id,\n        \"species_id\": species_id,\n        \"planned_quantity\": 24,\n    },\n})\n```\n\n**Never invent future IDs** Do not use strings such as\n\n`workflow_id_placeholder`\n\n. Execute the first operation,\ncapture its authoritative result, and use that value in the next direct MCP call.\n## Optional: preview a mutation before execution\n\nUse `/mcp/prepare`\n\nwhen your integration wants a validation or review step before direct execution.\n\n``` python\ndef prepare_tool(tool_call):\n    return flasktrack_post(\n        \"/mcp/prepare\",\n        {\n            \"name\": tool_call[\"name\"],\n            \"input\": tool_call[\"input\"],\n        },\n    )\n```\n\nPreparation does not execute the underlying operation. Use it for policy checks, logging, or a human confirmation surface.\n\n## Three rules that make agents dramatically safer\n\n**Search before mutation** If the agent does not know an exact record, use a FlaskTrack read tool first.\n\n**Respect entity types** A Protocol UUID is not a Workflow UUID. Use the semantic type declared by the tool.\n\n**Stop on control failures** Authorization, compliance, validation, and signature failures are authoritative. Do not route around them.\n\n## From demo agent to production integration\n\n- ✔ Use a dedicated FlaskTrack service identity and minimum required permissions\n- ✔ Keep API keys outside model-visible context\n- ✔ Discover tools from the target deployment at runtime\n- ✔ Use read tools to resolve exact records before mutation\n- ✔ Validate typed entity relationships rather than accepting arbitrary UUIDs\n- ✔ Add explicit human approval for high-impact or mutating operations\n- ✔ Use bounded retries and timeouts\n- ✔ Preserve idempotency keys where supported or required\n- ✔ Log tool names, correlation IDs, statuses, and returned record IDs without secrets\n- ✔ Treat electronic-signature and compliance controls as server-authoritative\n\n## Build the agent around your laboratory\n\nStart with one read workflow, add one reviewed mutation, and expand only after the integration behaves predictably against real FlaskTrack records.\n\n### Start read-only\n\nBegin with discovery, workflow lookup, batch status, or reporting before enabling mutation tools.\n\n### Add approval\n\nPut a human or policy gate in front of creation, updates, completion, and other operational actions.\n\n### Expand deliberately\n\nAdd tools as the agent proves reliable rather than exposing every available mutation on day one.\n\n## Your agent can now operate on the same laboratory model as your team\n\n[Full Biolab Integrated MCP Agent Example On Github](https://github.com/Santurce-Software-LLC/flasktrack-mcp-agentic-example)\n\nFlaskTrack gives agents a structured, permission-aware interface to laboratory records and operations without browser automation, direct database access, or a separate shadow data model.", "url": "https://wpnews.pro/news/build-your-first-flasktrack-mcp-agent", "canonical_source": "https://flasktrack.com/build-mcp-agent", "published_at": "2026-08-19 09:22:36+00:00", "updated_at": "2026-08-19 09:42:32.963808+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["FlaskTrack"], "alternates": {"html": "https://wpnews.pro/news/build-your-first-flasktrack-mcp-agent", "markdown": "https://wpnews.pro/news/build-your-first-flasktrack-mcp-agent.md", "text": "https://wpnews.pro/news/build-your-first-flasktrack-mcp-agent.txt", "jsonld": "https://wpnews.pro/news/build-your-first-flasktrack-mcp-agent.jsonld"}}