Skip to main content
Glama

flora-mcp

An MCP server that identifies plants from a photo and tells you whether that species is native, introduced or invasive in a given country.

The answer depends on the place, not just on the species: Pontederia crassipes is native in the Amazon basin and an aggressive invasive in Guatemala. That is exactly the kind of question a language model gets wrong from memory and a tool call gets right.

Built for the course CC3067 - Redes (Universidad del Valle de Guatemala). It speaks stdio and Streamable HTTP, so any MCP host can use it.

Tools

Tool

Purpose

Needs an API key

identify_plant_from_photo

Identify a species from a local photo via Pl@ntNet, with confidence scores.

Pl@ntNet

classify_species_in_country

Native / introduced / invasive verdict for a species in a country, with its evidence.

No

get_management_recommendations

Curated control measures for a known invasive species.

No

list_country_alien_species

Plants in a country's GRIIS register of introduced species.

No

Related MCP server: ALA API MCP Server

How the status is decided

GBIF publishes no single "is this invasive here?" field, so the verdict combines three public sources and always returns the evidence alongside it:

Source

What it contributes

species/{key}/distributions

A national checklist's establishmentMeans for that country: NATIVE, INTRODUCED, NATURALISED, MANAGED, INVASIVE.

GRIIS (per country)

The country's Global Register of Introduced and Invasive Species. Membership means the species is alien there.

GISD

The Global Invasive Species Database: species documented as invasive somewhere in the world.

Precedence:

  1. A distribution row for that country wins — it is a national checklist stating the establishment means directly. INTRODUCED plus a GISD listing is reported as invasora; INTRODUCED alone as introducida; NATIVE as nativa.

  2. Otherwise, presence in the country's GRIIS register means alien there; combined with GISD, invasive.

  3. Otherwise, occurrence records only prove the species has been seen there, which is reported as "presente, estatus no documentado" rather than guessed at.

Coverage is genuinely uneven. Pinus oocarpa is native to Guatemala but no checklist says so on GBIF, so the tool reports "present, status not documented" instead of inventing an answer. A host's system prompt should tell the model to pass that honesty through.

One taxonomic trap is worth knowing about: Pl@ntNet returns Eichhornia crassipes while GBIF's backbone has moved to Pontederia crassipes, and each checklist is indexed under whichever name its compiler used. Every lookup tries both the matched key and the accepted key for this reason.

Requirements

  • Python >= 3.11

  • A free Pl@ntNet API key (my.plantnet.org) — only for photo identification; 500 identifications/day on the free plan

GBIF needs no key and no account.

Installation

git clone https://github.com/USUARIO/flora-mcp.git
cd flora-mcp
uv venv .venv
.venv\Scripts\activate      # Windows; source .venv/bin/activate elsewhere
uv pip install -e .

Copy .env.example to .env and add your Pl@ntNet key:

Copy-Item .env.example .env

Environment variables

Variable

Required

Purpose

PLANTNET_API_KEY

For identify_plant_from_photo only

Pl@ntNet API key.

FLORA_IMAGES_DIR

No

Where relative image names are resolved. Defaults to ./workspace. A host whose images live elsewhere should set this to an absolute path.

Running

flora-mcp                                  # stdio (default)
flora-mcp --http --port 8100               # Streamable HTTP, this machine only
flora-mcp --http --host 0.0.0.0 --port 8100  # reachable from the network

Over stdio the process stays silent and waits for JSON-RPC on stdin — that is normal for that transport, not a hang. Nothing is ever written to stdout, since that channel carries the protocol.

The server has no authentication, so only bind 0.0.0.0 on a network you trust.

Tool reference

identify_plant_from_photo

Parameter

Type

Required

Description

image_path

string

yes

Absolute path, or a file name inside FLORA_IMAGES_DIR.

organ

string

no

What the photo shows: auto (default), leaf, flower, fruit, bark, habit, other.

{ "image_path": "planta2.jpg" }
{
  "found": true,
  "image": "planta2.jpg",
  "organ": "auto",
  "candidates": [
    {
      "scientific_name": "Spathoglottis plicata",
      "common_names": ["Orquídea de tierra"],
      "family": "Orchidaceae",
      "confidence": 27.3
    }
  ]
}

Always report the confidence. Anything below roughly 30% is a guess, and the tool returns several candidates precisely so the caller can say so.

classify_species_in_country

Parameter

Type

Required

Description

scientific_name

string

yes

Scientific name; a synonym is accepted and resolved.

place

string

yes

Country name or ISO code — Guatemala, GT, GTM.

{ "scientific_name": "Eichhornia crassipes", "place": "Guatemala" }
{
  "resolved": true,
  "species": "Eichhornia crassipes (Mart.) Solms",
  "country": "Guatemala",
  "country_code": "GT",
  "status": "invasora",
  "basis": "introducida segun checklist nacional (INTRODUCED) y listada en GISD",
  "evidence": {
    "distribution_rows": [
      {
        "establishment_means": "INTRODUCED",
        "occurrence_status": null,
        "source": "Global Dataset of Freshwater Invasive and Alien Species"
      }
    ],
    "in_country_griis": false,
    "in_gisd": true,
    "occurrence_records": 98,
    "native_range_sample": ["BR"]
  }
}

status is one of nativa, introducida, invasora, presente, estatus no documentado, or sin registros. basis says which rule produced it, and evidence carries what it was based on.

get_management_recommendations

Parameter

Type

Required

Description

scientific_name

string

yes

Scientific name; synonyms in the table are resolved.

{ "scientific_name": "Eichhornia crassipes" }
{
  "found": true,
  "scientific_name": "pontederia crassipes",
  "common_name": "Jacinto de agua / lirio acuatico",
  "urgency": "alta",
  "why": "Forma tapetes flotantes que agotan el oxigeno del agua...",
  "methods": [
    "Control mecanico: extraccion manual o con cosechadora antes de la floracion...",
    "Control biologico: gorgojos Neochetina bruchi y N. eichhorniae..."
  ]
}

When a species is not in the table the reply is {"found": false, ...} with a covered_species list, so the caller can say what the server does and does not know instead of improvising control measures.

list_country_alien_species

Parameter

Type

Required

Description

place

string

yes

Country name or ISO code.

limit

integer

no

Maximum species to return. Default 25.

{ "place": "Guatemala", "limit": 5 }
{
  "resolved": true,
  "country": "Guatemala",
  "country_code": "GT",
  "species": [
    { "scientific_name": "Acanthophora spicifera (M.Vahl) Børgesen", "canonical_name": "Acanthophora spicifera", "family": "Rhodomelaceae" },
    { "scientific_name": "Pinus caribaea Morelet", "canonical_name": "Pinus caribaea", "family": "Pinaceae" }
  ]
}

GRIIS registers introduced species, only some of which are invasive. Present these as introduced, and use classify_species_in_country to find out which is which. Not every country publishes a GRIIS checklist.

Configuring it in an MCP host

Generic stdio configuration:

{
  "servers": {
    "flora": {
      "transport": "stdio",
      "command": "C:/ruta/al/repo/flora-mcp/.venv/Scripts/python.exe",
      "args": ["-m", "flora_mcp.server"],
      "cwd": "C:/ruta/al/repo/flora-mcp",
      "env": { "PLANTNET_API_KEY": "..." }
    }
  }
}

On Linux or macOS use .venv/bin/python. With uv installed, a host can skip the explicit path entirely:

uv run --directory ../flora-mcp flora-mcp

If the host keeps its images in its own folder rather than this repo's workspace/, pass FLORA_IMAGES_DIR as an absolute path pointing there — otherwise relative file names resolve against this repo and photo lookups fail.

Testing with MCP Inspector

npx -y @modelcontextprotocol/inspector .venv\Scripts\python.exe -m flora_mcp.server

Suggested sequence:

  1. list_country_alien_species with place = Guatemala

  2. classify_species_in_country with one of the species it returns

  3. get_management_recommendations for the same species

  4. identify_plant_from_photo with a photo dropped in workspace/

Tests

.venv\Scripts\python.exe -m pytest -m "not network"

That covers the classification heuristic against stubbed GBIF responses and launches the server as a real subprocess to exercise it over JSON-RPC, on both stdio and HTTP. Drop the -m filter to also run the tests that hit the live GBIF API.

Extending the knowledge base

src/flora_mcp/knowledge_base.py is the hand-written half of the server: GBIF says what a species is, not what to do about it. Add entries keyed by lowercase scientific name, and register any synonym Pl@ntNet might return in SYNONYMS so both names reach the same advice.

Limitations

  • GBIF's per-country coverage is uneven; absence of a status is not a status.

  • The management table is curated by hand and currently covers seven species relevant to Mesoamerica.

  • Pl@ntNet's confidence is often low on partial or unusual photos. The server reports it rather than hiding it.

  • Both Pl@ntNet and GBIF require internet access.

  • No authentication: anything that can reach the process can call the tools.

Technologies

Python, MCP Python SDK, JSON-RPC, Pl@ntNet API, GBIF API.

Academic use

Developed for CC3067 Redes, Universidad del Valle de Guatemala. Where third-party code or documentation is reused, the corresponding attribution is kept in the comments.

Available Tools

4 tools
classify_species_in_countryA

Determine whether a plant species is native, introduced or invasive in a country, using GBIF distribution records, the country's GRIIS register of introduced species, and the Global Invasive Species Database.

place accepts a country name or an ISO code ("Guatemala", "GT", "GTM"). The reply carries a status, the basis for it and the raw evidence. GBIF's coverage is uneven, so a status of "presente, estatus no documentado" means the species is recorded there but no checklist states its origin -- report that honestly instead of assuming it is native.

ParametersJSON Schema
NameRequiredDescriptionDefault
placeYes
scientific_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the output fields (status, basis, evidence), explains the meaning of a specific status value, and instructs the agent to report honestly instead of assuming native. It does not explicitly state read-only behavior, but 'determine using records' strongly implies it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and source list, then gives input formatting, output shape, and a necessary caveat. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists, the description does not need to detail the return structure, but it still names key output fields. It explains place formats and gives important behavioral guidance about GBIF gaps. It does not cover scientific_name format or explicitly route away from sibling tools, but the core call context is well covered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It significantly clarifies the place parameter, explaining that it accepts a country name or ISO code with examples. However, scientific_name receives no explanation beyond what is obvious from the tool name and purpose.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action: classify a plant species as native, introduced, or invasive in a country, and names the data sources used. It does not explicitly contrast with sibling tools like list_country_alien_species, so it slightly misses full differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies its use case through its purpose and input requirements, but it does not explicitly state when to prefer this tool over the sibling tools or mention any exclusions. Context is clear, but alternative routing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_management_recommendationsA

Control and management measures for a known invasive species, from a curated table focused on Mesoamerica.

Only covers the species listed in covered_species when nothing matches; it is not a global database, so say so rather than inventing measures.

ParametersJSON Schema
NameRequiredDescriptionDefault
scientific_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and discloses important behavioral traits: coverage is limited to a curated Mesoamerican table and the tool is not a global database. The instruction to avoid inventing measures is especially valuable. It does not describe response formatting or matching details, but an output schema exists and the main limitation is clearly exposed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise, front-loaded sentences. The first states the core purpose and scope, while the second adds the critical limitation and fallback behavior. There is no filler or redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter retrieval tool with an output schema, the description covers the main essentials: what the tool returns, where the data comes from, and how to behave when no match is found. It could clarify the `covered_species` matching nuance further or add an example, but an agent has enough information to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has one required parameter, `scientific_name`, with 0% schema description coverage, so the description needed to compensate. It adds some meaning by framing the input as a 'known invasive species' and implying it must match a species in `covered_species`. However, it does not explain expected format, synonym handling, or exact-match behavior, leaving the compensation incomplete.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the resource as 'control and management measures' and adds useful scope: 'known invasive species' and 'curated table focused on Mesoamerica.' It distinguishes the tool from sibling tools like identify_plant_from_photo and list_country_alien_species, though it does not state an explicit verb such as 'retrieves' or 'returns.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when the tool applies by stating it only covers species listed in `covered_species` and is not a global database. It also tells the agent to say so rather than inventing measures when no match exists, which is strong usage guidance. It does not explicitly name sibling alternatives or say when to prefer another tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

identify_plant_from_photoA

Identify a plant species from a local photo using Pl@ntNet.

image_path may be absolute or a file name inside the workspace folder. organ describes what the photo shows: auto, leaf, flower, fruit, bark, habit or other. Returns the top candidates with a confidence score each -- always report the confidence, and treat anything below ~30% as a guess.

ParametersJSON Schema
NameRequiredDescriptionDefault
organNoauto
image_pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that it returns top candidates with confidence scores, instructs to always report confidence, and flags a ~30% threshold for guesses. It also explains the organ parameter's meaning. It does not cover error conditions or rate limits, but for a simple identification tool this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences with zero redundancy. The main purpose is front-loaded, followed by essential parameter details and a critical behavioral note. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with two parameters, the description covers the core purpose, parameter formats, and output behavior (confidence scores). It mentions the output schema exists, so return-value details are not required. It does not address failure modes, but that is a minor omission given the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It explains image_path may be absolute or a workspace-relative file name, and enumerates organ values (auto, leaf, flower, fruit, bark, habit, other). This adds substantial meaning beyond the bare schema titles, fully compensating for the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Identify a plant species from a local photo using Pl@ntNet.' It clearly distinguishes the tool from its siblings (classify_species_in_country, get_management_recommendations, list_country_alien_species) which address different tasks, so an agent can select it without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context: it handles local photos and uses Pl@ntNet, implying it should be used when a user supplies a photo to identify. It does not explicitly name alternatives or exclusions, but the sibling tools make the domain clear. This meets the 'clear context, no exclusions' bar.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_country_alien_speciesA

List the plant species a country's GRIIS register records as alien (non-native) there.

GRIIS is a register of introduced species, only some of which are invasive, so present these as introduced rather than as invasive. Use classify_species_in_country on any individual species to find out which. Not every country publishes a GRIIS checklist.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
placeYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It discloses that GRIIS is a register of introduced species (only some invasive) and instructs to present them as introduced rather than invasive, a critical semantic nuance. It also mentions the limitation of country coverage. It does not mention error handling or pagination, but the presence of an output schema mitigates the need to describe return format. The disclosed caveats add genuine value beyond the tool's name.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, no filler. The core purpose is front-loaded in the first sentence, followed by crucial behavioral guidance and a sibling pointer. Every sentence earns its place; it is concise without sacrificing necessary context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (list of species) and the existence of an output schema, the description covers the essential behavior, the introduced-vs-invasive distinction, and the country availability caveat. It also routes to the correct sibling for deeper analysis. The only missing piece is explicit parameter guidance, but that is partially addressed by the context of 'country' in the description. Overall, an agent would be well-equipped to use this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides no parameter descriptions (0% coverage), so the description must compensate. It implies 'place' refers to a country ('a country's GRIIS register') but does not explain the expected format (e.g., name vs. code). The 'limit' parameter is not mentioned at all, leaving its semantics entirely to inference. This is a significant gap given the zero coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('List'), a resource ('a country's GRIIS register'), and a precise scope ('plant species... alien (non-native)'). It also differentiates itself from classify_species_in_country by explicitly directing individual species queries there. This is unambiguous and distinguishes the tool from all siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance: it explains this tool lists introduced species, not just invasive, and instructs the agent to use classify_species_in_country for individual species to determine invasiveness. It also warns that not every country publishes a GRIIS checklist, setting expectations for potential empty results. This is more than adequate routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedclassify_species_in_country
    • First observedget_management_recommendations
    • First observedidentify_plant_from_photo
    • First observedlist_country_alien_species

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool targets a distinct stage in the plant management workflow: photo identification, status classification, management advice, and country-level alien listing. There is no overlap in purpose, and the boundary between identifying a plant and classifying its status is clear.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: identify_plant_from_photo, classify_species_in_country, get_management_recommendations, list_country_alien_species. The naming is uniform and predictable.

Tool Count5/5

Four tools is a compact but well-scoped set for a flora-management assistant. Each tool is substantive and maps to one distinct part of the domain, avoiding both bloat and thinness.

Completeness4/5

The tools cover the main workflow: identify a plant, classify its status, view management recommendations, and list alien species for a country. A minor gap is the lack of a direct species lookup by name without a photo, but the overall lifecycle is well covered.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables querying relationships between plant species, small molecules, and mitochondrial Complex I inhibitors by bridging natural-product, biodiversity, and PubMed datasets. Allows LLMs to perform structured searches and reasoning over biological data to identify potential plant-derived mitochondrial inhibitors.
    GPL 3.0
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables interaction with the Atlas of Living Australia API to search and access 152+ million biodiversity records. Supports occurrence searches, spatial queries, bulk data downloads, and taxonomic data retrieval through natural language.
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to query and retrieve biodiversity data from the Global Biodiversity Information Facility (GBIF), including species, occurrences, datasets, and literature.
    -