farmos-mcp
by c41m4n
README.md
# farmos-mcp
An [MCP](https://modelcontextprotocol.io) server for [farmOS](https://farmos.org) 4. It lets an AI assistant look up and record farm data (assets, logs, plans, quantities, taxonomy terms) through farmOS's JSON:API.
It was written against the farmOS 4.0 API changes: assets use the `archived` boolean instead of a status, plans have `planning / active / done / abandoned` statuses, and logs can be `abandoned`. On a farmOS 3 server it falls back to the old `status` handling for archiving.
> **Early release (v0.1.0, alpha).** Unit-tested against a mock farmOS server, but **not yet run against a live farmOS instance.** Expect rough edges and possible breaking changes. Try it on a test or demo instance first (see "Verify against your server" below), and do not point write access at production data you can't restore.
>
> **Built with AI.** This project was written by an AI assistant (Claude, by Anthropic) working with a human maintainer. See [AI disclosure](#ai-disclosure).
## Tools
| Tool | What it does | Needs writes |
|---|---|---|
| `farm_info` | Server name/version, current user, available resource types, whether writes are on | |
| `list_resource_types` | Entity types and bundles on this server | |
| `query_records` | Filter / sort / page / include on any one bundle | |
| `get_record` | One record by UUID | |
| `search_assets` | Find assets by name across all bundles; active/archived filter | |
| `list_logs` | Logs across bundles by status, asset, text, date range | |
| `list_plans` | Plans by status / archived / name | |
| `list_terms` | Taxonomy terms in a vocabulary (units, categories, plant types...) | |
| `create_log` | Log with locations, categories and quantities in one call | yes |
| `create_record` | Create an asset, log, plan, quantity, term or organization | yes |
| `update_record` | Change fields on a record | yes |
| `set_archived` | Archive / restore an asset or plan | yes |
| `set_log_status` | `pending`, `done` or `abandoned` | yes |
| `delete_record` | Permanently delete a record | yes + delete |
## Safety defaults
- **Read-only by default.** Writes need `FARMOS_ALLOW_WRITES=true`. Deletes additionally need `FARMOS_ALLOW_DELETE=true`.
- Writes are limited to `asset`, `log`, `plan`, `quantity`, `taxonomy_term` and `organization`. Users, roles and configuration cannot be written through this server.
- Credentials are only sent to the configured `FARMOS_URL`. Pagination links pointing at another host are refused. Plain `http://` is rejected for non-local hosts.
- Everything the assistant can do is bounded by the farmOS account it logs in as. Use a dedicated account with the lowest role that does the job (see Roles below).
## farmOS setup
farmOS 4 made the API and OAuth2 server optional, so check these on your instance:
1. **Enable the API module** (`farm_api`) and the **OAuth2 server module** (`farm_api_oauth`) under *Administration > Modules* (or `drush en farm_api farm_api_oauth`).
2. **Create OAuth2 keys.** farmOS 4 no longer generates them at install. See [OAuth2 keys](https://farmos.org/hosting/install/#oauth2-keys).
3. Pick an auth method:
- **Password grant (simplest).** Uses the built-in `farm` client with a farmOS username and password. This is a legacy grant type: it also requires the `simple_oauth_password_grant` module, and is only appropriate when the MCP server is a trusted first-party client on your own machine.
- **Client credentials (better for unattended use).** Create a client (consumer) in farmOS with a secret and the client credentials grant, mapped to a user. Set `FARMOS_CLIENT_ID` and `FARMOS_CLIENT_SECRET`.
### Roles
farmOS 4 split the default roles into separate modules and changed what they can do:
- **Viewer**: read-only. Right for a read-only deployment.
- **Worker**: can create and edit records, but in v4 can no longer delete other users' records or edit/delete taxonomy terms.
- **Manager**: full records access including terms and deletes.
The OAuth `scope` must match a role the user has. The default scope here is `farm_manager`; change `FARMOS_SCOPE` to `farm_worker` or `farm_viewer` for a narrower token.
## Install
```bash
cd farmos-mcp
python3 -m venv .venv
.venv/bin/pip install -e .
```
Requires Python 3.11+ and `mcp` 2.x (this project uses the 2.x `MCPServer` API).
## Configure
Environment variables (see `.env.example`):
| Variable | Default | Meaning |
|---|---|---|
| `FARMOS_URL` | required | Base URL, e.g. `https://farm.example.com` |
| `FARMOS_USERNAME` / `FARMOS_PASSWORD` | | Password grant credentials |
| `FARMOS_CLIENT_ID` | `farm` | OAuth client id |
| `FARMOS_CLIENT_SECRET` | | Client secret (client credentials grant) |
| `FARMOS_SCOPE` | `farm_manager` | OAuth scope (`farm_manager`, `farm_worker`, `farm_viewer`, ...) |
| `FARMOS_TOKEN_PATH` | `/oauth/token` | Token endpoint path |
| `FARMOS_ALLOW_WRITES` | `false` | Enable create/update/archive/status tools |
| `FARMOS_ALLOW_DELETE` | `false` | Enable `delete_record` (also needs writes) |
| `FARMOS_TIMEOUT` | `30` | Request timeout, seconds |
| `FARMOS_INSECURE_TLS` | `false` | Skip TLS verification (not recommended) |
| `FARMOS_ALLOW_INSECURE_HTTP` | `false` | Allow `http://` to a non-local host |
If a password is set it wins; otherwise a client secret selects the client credentials grant.
### Claude Desktop / Claude Code
```json
{
"mcpServers": {
"farmos": {
"command": "/absolute/path/to/farmos-mcp/.venv/bin/farmos-mcp",
"env": {
"FARMOS_URL": "https://farm.example.com",
"FARMOS_USERNAME": "claude-assistant",
"FARMOS_PASSWORD": "...",
"FARMOS_SCOPE": "farm_worker",
"FARMOS_ALLOW_WRITES": "false"
}
}
}
}
```
For Claude Code: `claude mcp add farmos --env FARMOS_URL=https://farm.example.com --env FARMOS_USERNAME=... --env FARMOS_PASSWORD=... -- /absolute/path/to/farmos-mcp/.venv/bin/farmos-mcp`
## Verify against your server
Do these in order against a test instance (the [public demo](https://farmos-demo.rootedsolutions.io) is a reasonable target for reads):
1. Ask for `farm_info`. You should see version `4.x` and a list of resource types. A token error here means a setup problem from the section above, not a code problem.
2. `search_assets` with no arguments, then `list_logs` with `status: done`.
3. Enable writes on a throwaway instance and try `create_log` with a quantity.
Parts most worth checking on a real server, because they follow documented behavior but were only exercised against the mock:
- boolean filters (`archived` is sent as `0`/`1`),
- quantity `value` written as `{"decimal": "..."}`,
- timestamp filters as epoch seconds, timestamp writes as ISO 8601,
- the token endpoint path (`/oauth/token`, overridable).
## Development
```bash
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
```
Tests use `httpx.MockTransport`, so no farmOS server is needed.
## AI disclosure
This repository was developed with substantial use of AI:
- **The code, tests and documentation were written by Claude** (Claude Sonnet 5.5, Anthropic) in an interactive session with the maintainer, who directed the work. The design choices, API behavior and docs were drafted by the model from farmOS's public documentation.
- **The farmOS behavior is based on documentation, not on a live server.** The model had no access to a real farmOS instance, so request shapes (filters, quantity values, timestamps, the OAuth token endpoint) follow the published docs and have only been exercised against a mock written alongside the code. The mock encodes the same assumptions as the code, so passing tests do not prove the server matches real farmOS behavior.
- **Human review is limited.** The code has not had a full independent audit. Treat it as you would any unreviewed third-party code: read it before running it, especially before giving it credentials or write access.
- **Security-relevant choices to check:** this server sends your farmOS credentials to the configured URL, can create/modify/delete records when enabled, and is driven by an AI model that can make mistakes. Use a dedicated low-privilege farmOS account (`farm_viewer` or `farm_worker`), keep `FARMOS_ALLOW_WRITES` and `FARMOS_ALLOW_DELETE` off unless you need them, and keep backups.
Issues and pull requests that fix incorrect farmOS behavior are especially welcome. Please say in your PR if AI tools were used.
## Not included
- Uploading files/images, geometry editing, and data streams.
- farmOS add-on modules with custom fields are reachable through `query_records` / `create_record`, but have no dedicated tools.
- Only the stdio transport. Remote/HTTP hosting would need `mcp.run(transport="streamable-http")` plus your own authentication in front.
TDQS
A3.9/5.0
Scored across 14 tools
Disambiguation5/5
The tools are highly differentiated by their scope and action. General record operations are distinct from specific asset, log, and plan operations, making selection intuitive.
Naming Consistency4/5
The majority of tools follow the 'verb_noun' convention (e.g., list_logs, create_record). While there are variations like 'set_archived', these follow a predictable pattern for state modification.
Tool Count5/5
With 14 tools, the set is perfectly scoped. It covers the entire lifecycle and metadata needs of a complex system without being overly granular or sparse.
Completeness4/5
The set provides solid CRUD and state management coverage for its core entities (records, logs, assets). While highly complete, advanced relational query logic might require chaining multiple tools.
Maintenance
ActivityMaintained
ResponsivenessNo issues