@cyanheads/gbif-biodiversity-mcp-server 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/Dockerfile +10 -6
- package/README.md +11 -7
- package/dist/mcp-server/resources/definitions/gbif-dataset.resource.d.ts +16 -0
- package/dist/mcp-server/resources/definitions/gbif-dataset.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/gbif-dataset.resource.js +61 -1
- package/dist/mcp-server/resources/definitions/gbif-dataset.resource.js.map +1 -1
- package/dist/mcp-server/resources/definitions/gbif-species.resource.js +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-dataset.tool.d.ts +7 -0
- package/dist/mcp-server/tools/definitions/gbif-get-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-dataset.tool.js +41 -14
- package/dist/mcp-server/tools/definitions/gbif-get-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-occurrence.tool.d.ts +21 -0
- package/dist/mcp-server/tools/definitions/gbif-get-occurrence.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-occurrence.tool.js +77 -0
- package/dist/mcp-server/tools/definitions/gbif-get-occurrence.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-species-classification.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-species-classification.tool.js +6 -4
- package/dist/mcp-server/tools/definitions/gbif-get-species-classification.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-species.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-get-species.tool.js +19 -2
- package/dist/mcp-server/tools/definitions/gbif-get-species.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-occurrence-facets.tool.d.ts +3 -0
- package/dist/mcp-server/tools/definitions/gbif-occurrence-facets.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-occurrence-facets.tool.js +15 -1
- package/dist/mcp-server/tools/definitions/gbif-occurrence-facets.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-search-datasets.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/gbif-search-datasets.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-search-datasets.tool.js +25 -12
- package/dist/mcp-server/tools/definitions/gbif-search-datasets.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/gbif-search-species.tool.js +1 -1
- package/dist/mcp-server/tools/utils.d.ts +34 -0
- package/dist/mcp-server/tools/utils.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils.js +41 -0
- package/dist/mcp-server/tools/utils.js.map +1 -1
- package/dist/services/gbif/gbif-service.d.ts +1 -0
- package/dist/services/gbif/gbif-service.d.ts.map +1 -1
- package/dist/services/gbif/gbif-service.js +2 -0
- package/dist/services/gbif/gbif-service.js.map +1 -1
- package/dist/services/gbif/types.d.ts +25 -4
- package/dist/services/gbif/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/server.json +3 -3
package/CLAUDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** @cyanheads/gbif-biodiversity-mcp-server
|
|
4
|
-
**Version:** 0.5.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.
|
|
4
|
+
**Version:** 0.5.2
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.14`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
8
8
|
**Zod:** ^4.4.3
|
package/Dockerfile
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
# This stage installs all dependencies (including dev), builds the TypeScript
|
|
5
5
|
# source code into JavaScript, and prepares the production assets.
|
|
6
6
|
# ==============================================================================
|
|
7
|
-
FROM oven/bun:1.3 AS build
|
|
7
|
+
FROM oven/bun:1.3.14 AS build
|
|
8
8
|
|
|
9
9
|
WORKDIR /usr/src/app
|
|
10
10
|
|
|
11
11
|
# Copy dependency manifests for optimized layer caching
|
|
12
12
|
COPY package.json bun.lock ./
|
|
13
13
|
|
|
14
|
-
# Install all dependencies (including dev dependencies for building)
|
|
15
|
-
|
|
14
|
+
# Install all dependencies (including dev dependencies for building).
|
|
15
|
+
# The BuildKit cache mount persists Bun's global package cache across builds.
|
|
16
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
17
|
+
bun install --frozen-lockfile --ignore-scripts
|
|
16
18
|
|
|
17
19
|
# Copy the rest of the source code
|
|
18
20
|
COPY . .
|
|
@@ -28,7 +30,7 @@ RUN bun run build
|
|
|
28
30
|
# application. It uses a slim base image and only includes production
|
|
29
31
|
# dependencies and build artifacts.
|
|
30
32
|
# ==============================================================================
|
|
31
|
-
FROM oven/bun:1.3-slim AS production
|
|
33
|
+
FROM oven/bun:1.3.14-slim AS production
|
|
32
34
|
|
|
33
35
|
WORKDIR /usr/src/app
|
|
34
36
|
|
|
@@ -49,13 +51,15 @@ COPY package.json bun.lock ./
|
|
|
49
51
|
|
|
50
52
|
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
51
53
|
# that are not needed in the final production image.
|
|
52
|
-
RUN bun
|
|
54
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
55
|
+
bun install --production --frozen-lockfile --ignore-scripts
|
|
53
56
|
|
|
54
57
|
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
55
58
|
# These are not bundled by default to keep the base image lean. Enable at build time
|
|
56
59
|
# with: docker build --build-arg OTEL_ENABLED=true
|
|
57
60
|
ARG OTEL_ENABLED=true
|
|
58
|
-
RUN
|
|
61
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
62
|
+
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
59
63
|
bun add @hono/otel \
|
|
60
64
|
@opentelemetry/instrumentation-http \
|
|
61
65
|
@opentelemetry/exporter-metrics-otlp-http \
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/gbif-biodiversity-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/gbif-biodiversity-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
| `gbif_bulk_match_species` | Match up to 50 scientific names to backbone taxon keys in one call — results in input order, per-name NONE/ERROR isolation |
|
|
34
34
|
| `gbif_get_species` | Fetch a single backbone taxon by key — full classification, authorship, synonymy, vernacular name, descendant count |
|
|
35
35
|
| `gbif_search_species` | Search or browse the GBIF backbone taxonomy by name fragment, rank, kingdom, family, or genus |
|
|
36
|
-
| `gbif_get_species_classification` | Return the
|
|
36
|
+
| `gbif_get_species_classification` | Return the root-to-parent classification chain for a taxon — root-first ordered array from kingdom to the queried taxon's immediate parent (the taxon itself is not included) |
|
|
37
37
|
| `gbif_get_species_children` | List direct children of a backbone taxon — genera within a family, species within a genus |
|
|
38
38
|
| `gbif_search_occurrences` | Search 2.4B+ GBIF occurrence records with Darwin Core filters — country, bounding box, WKT geometry, year, month, basis of record |
|
|
39
39
|
| `gbif_count_occurrences` | Count occurrences matching a filter without fetching records — fast single-number response |
|
|
@@ -93,9 +93,10 @@ Search or browse the GBIF backbone taxonomy.
|
|
|
93
93
|
|
|
94
94
|
### `gbif_get_species_classification`
|
|
95
95
|
|
|
96
|
-
Return the
|
|
96
|
+
Return the root-to-parent classification chain for a taxon as an ordered array.
|
|
97
97
|
|
|
98
|
-
- Root-first
|
|
98
|
+
- Root-first from kingdom down to the immediate parent of the queried taxon (kingdom → phylum → class → … → parent)
|
|
99
|
+
- The queried taxon itself is not included — use `gbif_get_species` for its own record
|
|
99
100
|
- Each entry: rank, canonical name, scientific name, taxon key
|
|
100
101
|
- Useful for building taxonomic trees or placing an unfamiliar taxon in context without manual backbone navigation
|
|
101
102
|
|
|
@@ -138,7 +139,8 @@ Count occurrences matching a filter without fetching any records.
|
|
|
138
139
|
|
|
139
140
|
Fetch a single occurrence record by GBIF occurrence key.
|
|
140
141
|
|
|
141
|
-
- Complete Darwin Core record — all coordinate fields,
|
|
142
|
+
- Complete Darwin Core record — all coordinate fields, administrative geography (continent, country, state/province, locality), dates
|
|
143
|
+
- `occurrenceID`, full classification (`class`/`classKey`), GADM administrative units (levels 0–2, each with a stable GID and name), and source `identifiers`
|
|
142
144
|
- Collections metadata: institution code, collection code, catalog number
|
|
143
145
|
- Collector and identifier names, individual count, sex, life stage
|
|
144
146
|
- Associated media (images, audio, video) with URLs and license
|
|
@@ -152,7 +154,8 @@ Aggregate occurrence counts across a dimension.
|
|
|
152
154
|
|
|
153
155
|
- Facets: `COUNTRY`, `YEAR`, `BASIS_OF_RECORD`, `DATASET_KEY`, `KINGDOM_KEY`, `PHYLUM_KEY`, `CLASS_KEY`, `ORDER_KEY`, `FAMILY_KEY`, `GENUS_KEY`, `SPECIES_KEY`, `PUBLISHING_COUNTRY`, `MONTH`
|
|
154
156
|
- Scope with `taxonKey`, `country`, `year`, `geometry`, or `basisOfRecord` filters
|
|
155
|
-
- Returns top-N values (up to 100)
|
|
157
|
+
- Returns top-N values ranked by count (up to `facetLimit`, max 100) — no record payloads
|
|
158
|
+
- Page past the first `facetLimit` with `facetOffset` (advance by `facetLimit` per page) to walk high-cardinality facets like `DATASET_KEY`; enrichment echoes the applied `facetOffset` and sets `moreValuesLikely` when a full page suggests more values remain
|
|
156
159
|
- Core tool for distribution analysis ("which countries have the most records?") and trend queries ("how has observation volume changed since 2010?")
|
|
157
160
|
|
|
158
161
|
---
|
|
@@ -162,7 +165,7 @@ Aggregate occurrence counts across a dimension.
|
|
|
162
165
|
Search GBIF datasets by keyword, type, country, or publishing organization.
|
|
163
166
|
|
|
164
167
|
- Filters: free-text query, dataset type (`OCCURRENCE`, `CHECKLIST`, `METADATA`, `SAMPLING_EVENT`), publishing country, hosting organization UUID
|
|
165
|
-
- Returns title, type, description, license, DOI, and record count
|
|
168
|
+
- Returns title, type, description, license, DOI, and record count. The `description` is a 300-character preview — `descriptionTruncated` flags when it was shortened, and `gbif_get_dataset` returns the full text
|
|
166
169
|
- Use `hostingOrg` from `gbif_search_publishers` to scope to datasets from one organization
|
|
167
170
|
- Paginated — limit up to 1000
|
|
168
171
|
|
|
@@ -174,6 +177,7 @@ Fetch full dataset metadata by UUID.
|
|
|
174
177
|
|
|
175
178
|
- Full description, citation text (for academic reference), license, DOI
|
|
176
179
|
- Contacts with role, name, organization, and email
|
|
180
|
+
- Temporal and geographic coverage ranges when the publisher declares them
|
|
177
181
|
- `numConstituents` for aggregate datasets (e.g. iNaturalist, eBird)
|
|
178
182
|
- Use after `gbif_search_datasets` or when an occurrence record's `datasetKey` needs provenance detail
|
|
179
183
|
|
|
@@ -17,6 +17,22 @@ export declare const gbifDatasetResource: import("@cyanheads/mcp-ts-core").Resou
|
|
|
17
17
|
publishingCountry: z.ZodOptional<z.ZodString>;
|
|
18
18
|
recordCount: z.ZodOptional<z.ZodNumber>;
|
|
19
19
|
numConstituents: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
contacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
+
type: z.ZodOptional<z.ZodString>;
|
|
22
|
+
firstName: z.ZodOptional<z.ZodString>;
|
|
23
|
+
lastName: z.ZodOptional<z.ZodString>;
|
|
24
|
+
organization: z.ZodOptional<z.ZodString>;
|
|
25
|
+
email: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
+
}, z.core.$strip>>>;
|
|
27
|
+
contactsTotal: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
contactsReturned: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
temporalCoverages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
30
|
+
start: z.ZodOptional<z.ZodString>;
|
|
31
|
+
end: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>>>;
|
|
33
|
+
geographicCoverages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
+
description: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>>>;
|
|
20
36
|
}, z.core.$strip>, readonly [{
|
|
21
37
|
readonly reason: "not_found";
|
|
22
38
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gbif-dataset.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/gbif-dataset.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"gbif-dataset.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/gbif-dataset.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAgB3E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkI9B,CAAC"}
|
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
-
import { stripHtml } from '../../../mcp-server/tools/utils.js';
|
|
7
|
+
import { compactGeographicCoverages, compactTemporalCoverages, projectContacts, stripHtml, } from '../../../mcp-server/tools/utils.js';
|
|
8
8
|
import { getGbifService } from '../../../services/gbif/gbif-service.js';
|
|
9
|
+
/**
|
|
10
|
+
* Fixed contact cap for the resource. The resource has no contactLimit input (unlike
|
|
11
|
+
* gbif_get_dataset), so it applies a constant cap while still reporting the full count.
|
|
12
|
+
*/
|
|
13
|
+
const CONTACT_CAP = 10;
|
|
9
14
|
export const gbifDatasetResource = resource('gbif://dataset/{datasetKey}', {
|
|
10
15
|
name: 'gbif-dataset',
|
|
11
16
|
title: 'GBIF Dataset',
|
|
@@ -27,6 +32,58 @@ export const gbifDatasetResource = resource('gbif://dataset/{datasetKey}', {
|
|
|
27
32
|
publishingCountry: z.string().optional().describe('Publishing organization country.'),
|
|
28
33
|
recordCount: z.number().optional().describe('Number of records. May be absent.'),
|
|
29
34
|
numConstituents: z.number().optional().describe('Number of sub-datasets. May be absent.'),
|
|
35
|
+
contacts: z
|
|
36
|
+
.array(z
|
|
37
|
+
.object({
|
|
38
|
+
type: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Contact type (e.g., ADMINISTRATIVE_POINT_OF_CONTACT). May be absent.'),
|
|
42
|
+
firstName: z.string().optional().describe('First name. May be absent.'),
|
|
43
|
+
lastName: z.string().optional().describe('Last name. May be absent.'),
|
|
44
|
+
organization: z.string().optional().describe('Organization name. May be absent.'),
|
|
45
|
+
email: z
|
|
46
|
+
.array(z.string())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Contact email addresses. May be absent.'),
|
|
49
|
+
})
|
|
50
|
+
.describe('A dataset contact with role, name, organization, and email.'))
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Dataset contacts, capped at 10. Absent when the dataset has no contacts.'),
|
|
53
|
+
contactsTotal: z
|
|
54
|
+
.number()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Total contacts before the cap. Present when the dataset has any contacts.'),
|
|
57
|
+
contactsReturned: z
|
|
58
|
+
.number()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Number of contacts included (≤ 10). Present when the dataset has any contacts.'),
|
|
61
|
+
temporalCoverages: z
|
|
62
|
+
.array(z
|
|
63
|
+
.object({
|
|
64
|
+
start: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Coverage start as an ISO 8601 date-time. May be absent.'),
|
|
68
|
+
end: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Coverage end as an ISO 8601 date-time. May be absent.'),
|
|
72
|
+
})
|
|
73
|
+
.describe('A temporal coverage range.'))
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Temporal coverage ranges declared by the dataset. May be absent.'),
|
|
76
|
+
geographicCoverages: z
|
|
77
|
+
.array(z
|
|
78
|
+
.object({
|
|
79
|
+
description: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Geographic coverage description (e.g. "Worldwide"). May be absent.'),
|
|
83
|
+
})
|
|
84
|
+
.describe('A geographic coverage entry.'))
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Geographic coverage descriptions declared by the dataset. May be absent.'),
|
|
30
87
|
}),
|
|
31
88
|
errors: [
|
|
32
89
|
{
|
|
@@ -68,6 +125,9 @@ export const gbifDatasetResource = resource('gbif://dataset/{datasetKey}', {
|
|
|
68
125
|
publishingCountry: raw.publishingCountry,
|
|
69
126
|
recordCount: raw.numRecords ?? raw.recordCount,
|
|
70
127
|
numConstituents: raw.numConstituents,
|
|
128
|
+
...projectContacts(raw.contacts, CONTACT_CAP),
|
|
129
|
+
temporalCoverages: compactTemporalCoverages(raw.temporalCoverages),
|
|
130
|
+
geographicCoverages: compactGeographicCoverages(raw.geographicCoverages),
|
|
71
131
|
};
|
|
72
132
|
},
|
|
73
133
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gbif-dataset.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/gbif-dataset.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,
|
|
1
|
+
{"version":3,"file":"gbif-dataset.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/gbif-dataset.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,SAAS,GACV,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGjE;;;GAGG;AACH,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC,6BAA6B,EAAE;IACzE,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,gFAAgF;QAChF,uFAAuF;QACvF,0CAA0C;IAC5C,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;KACjD,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QACpD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QACvD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QACrD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAClF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;QAC7E,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QACvE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;QAClF,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QACrF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QAChF,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;QACzF,QAAQ,EAAE,CAAC;aACR,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,sEAAsE,CAAC;YACnF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;YACvE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;YACrE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YACjF,KAAK,EAAE,CAAC;iBACL,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,EAAE;iBACV,QAAQ,CAAC,yCAAyC,CAAC;SACvD,CAAC;aACD,QAAQ,CAAC,6DAA6D,CAAC,CAC3E;aACA,QAAQ,EAAE;aACV,QAAQ,CAAC,0EAA0E,CAAC;QACvF,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,2EAA2E,CAAC;QACxF,gBAAgB,EAAE,CAAC;aAChB,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,gFAAgF,CAAC;QAC7F,iBAAiB,EAAE,CAAC;aACjB,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,yDAAyD,CAAC;YACtE,GAAG,EAAE,CAAC;iBACH,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,uDAAuD,CAAC;SACrE,CAAC;aACD,QAAQ,CAAC,4BAA4B,CAAC,CAC1C;aACA,QAAQ,EAAE;aACV,QAAQ,CAAC,kEAAkE,CAAC;QAC/E,mBAAmB,EAAE,CAAC;aACnB,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,oEAAoE,CAAC;SAClF,CAAC;aACD,QAAQ,CAAC,8BAA8B,CAAC,CAC5C;aACA,QAAQ,EAAE;aACV,QAAQ,CAAC,0EAA0E,CAAC;KACxF,CAAC;IAEF,MAAM,EAAE;QACN;YACE,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,yDAAyD;YAC/D,QAAQ,EACN,sHAAsH;SACzH;KACF;IAED,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG;QACvB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QAC9E,IAAI,GAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,cAAc,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,4EAA4E;YAC5E,2EAA2E;YAC3E,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBACtE,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,MAAM,CAAC,UAAU,qBAAqB,EAAE;oBAC7E,GAAG,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC;iBAChC,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,MAAM,CAAC,UAAU,qBAAqB,EAAE;gBAC7E,GAAG,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC;aAChC,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;YACrE,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,YAAY,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI;YAChC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,WAAW,EAAE,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,WAAW;YAC9C,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC;YAC7C,iBAAiB,EAAE,wBAAwB,CAAC,GAAG,CAAC,iBAAiB,CAAC;YAClE,mBAAmB,EAAE,0BAA0B,CAAC,GAAG,CAAC,mBAAmB,CAAC;SACzE,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -76,7 +76,7 @@ export const gbifSpeciesResource = resource('gbif://species/{taxonKey}', {
|
|
|
76
76
|
taxonomicStatus: raw.taxonomicStatus,
|
|
77
77
|
kingdom: raw.kingdom,
|
|
78
78
|
phylum: raw.phylum,
|
|
79
|
-
class: raw.
|
|
79
|
+
class: raw.class,
|
|
80
80
|
order: raw.order,
|
|
81
81
|
family: raw.family,
|
|
82
82
|
genus: raw.genus,
|
|
@@ -27,6 +27,13 @@ export declare const gbifGetDataset: import("@cyanheads/mcp-ts-core").ToolDefini
|
|
|
27
27
|
}, z.core.$strip>>>;
|
|
28
28
|
contactsTotal: z.ZodOptional<z.ZodNumber>;
|
|
29
29
|
contactsReturned: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
temporalCoverages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
31
|
+
start: z.ZodOptional<z.ZodString>;
|
|
32
|
+
end: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, z.core.$strip>>>;
|
|
34
|
+
geographicCoverages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
35
|
+
description: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>>>;
|
|
30
37
|
}, z.core.$strip>, readonly [{
|
|
31
38
|
readonly reason: "not_found";
|
|
32
39
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gbif-get-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"gbif-get-dataset.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAU3E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+LzB,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
7
|
import { getGbifService } from '../../../services/gbif/gbif-service.js';
|
|
8
|
-
import { stripHtml } from '../utils.js';
|
|
8
|
+
import { compactGeographicCoverages, compactTemporalCoverages, projectContacts, stripHtml, } from '../utils.js';
|
|
9
9
|
export const gbifGetDataset = tool('gbif_get_dataset', {
|
|
10
10
|
title: 'Get Dataset',
|
|
11
11
|
description: 'Fetch full dataset metadata by UUID key — title, description, citation text, contacts, license, ' +
|
|
@@ -71,6 +71,32 @@ export const gbifGetDataset = tool('gbif_get_dataset', {
|
|
|
71
71
|
.number()
|
|
72
72
|
.optional()
|
|
73
73
|
.describe('Number of contacts included in this response (≤ contactLimit). Present when the dataset has any contacts.'),
|
|
74
|
+
temporalCoverages: z
|
|
75
|
+
.array(z
|
|
76
|
+
.object({
|
|
77
|
+
start: z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe('Coverage start as an ISO 8601 date-time. May be absent.'),
|
|
81
|
+
end: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Coverage end as an ISO 8601 date-time. May be absent.'),
|
|
85
|
+
})
|
|
86
|
+
.describe('A temporal coverage range.'))
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('Temporal coverage ranges declared by the dataset. May be absent.'),
|
|
89
|
+
geographicCoverages: z
|
|
90
|
+
.array(z
|
|
91
|
+
.object({
|
|
92
|
+
description: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Geographic coverage description (e.g. "Worldwide"). May be absent.'),
|
|
96
|
+
})
|
|
97
|
+
.describe('A geographic coverage entry.'))
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Geographic coverage descriptions declared by the dataset. May be absent.'),
|
|
74
100
|
}),
|
|
75
101
|
errors: [
|
|
76
102
|
{
|
|
@@ -99,15 +125,6 @@ export const gbifGetDataset = tool('gbif_get_dataset', {
|
|
|
99
125
|
...ctx.recoveryFor('not_found'),
|
|
100
126
|
});
|
|
101
127
|
}
|
|
102
|
-
const allContacts = raw.contacts ?? [];
|
|
103
|
-
const contactsTotal = allContacts.length;
|
|
104
|
-
const contacts = allContacts.slice(0, input.contactLimit).map((c) => ({
|
|
105
|
-
type: c.type,
|
|
106
|
-
firstName: c.firstName,
|
|
107
|
-
lastName: c.lastName,
|
|
108
|
-
organization: c.organization,
|
|
109
|
-
email: c.email?.length ? c.email : undefined,
|
|
110
|
-
}));
|
|
111
128
|
return {
|
|
112
129
|
key: raw.key,
|
|
113
130
|
title: raw.title,
|
|
@@ -119,10 +136,11 @@ export const gbifGetDataset = tool('gbif_get_dataset', {
|
|
|
119
136
|
publishingCountry: raw.publishingCountry,
|
|
120
137
|
recordCount: raw.numRecords ?? raw.recordCount,
|
|
121
138
|
numConstituents: raw.numConstituents,
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
// contactLimit: 0 suppresses contact detail while projectContacts still reports
|
|
140
|
+
// contactsTotal/contactsReturned, so callers learn the dataset has contacts.
|
|
141
|
+
...projectContacts(raw.contacts, input.contactLimit),
|
|
142
|
+
temporalCoverages: compactTemporalCoverages(raw.temporalCoverages),
|
|
143
|
+
geographicCoverages: compactGeographicCoverages(raw.geographicCoverages),
|
|
126
144
|
};
|
|
127
145
|
},
|
|
128
146
|
format: (result) => {
|
|
@@ -142,6 +160,15 @@ export const gbifGetDataset = tool('gbif_get_dataset', {
|
|
|
142
160
|
lines.push(`**Records:** ${result.recordCount.toLocaleString()}`);
|
|
143
161
|
if (result.numConstituents != null)
|
|
144
162
|
lines.push(`**Constituent datasets:** ${result.numConstituents}`);
|
|
163
|
+
if (result.temporalCoverages?.length) {
|
|
164
|
+
const ranges = result.temporalCoverages.map((t) => `${t.start ?? '?'} → ${t.end ?? '?'}`);
|
|
165
|
+
lines.push(`**Temporal coverage:** ${ranges.join('; ')}`);
|
|
166
|
+
}
|
|
167
|
+
if (result.geographicCoverages?.length) {
|
|
168
|
+
const descs = result.geographicCoverages.map((g) => g.description).filter(Boolean);
|
|
169
|
+
if (descs.length > 0)
|
|
170
|
+
lines.push(`**Geographic coverage:** ${descs.join('; ')}`);
|
|
171
|
+
}
|
|
145
172
|
if (result.citationText)
|
|
146
173
|
lines.push(`\n**Citation:**\n> ${result.citationText}`);
|
|
147
174
|
if (result.description)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gbif-get-dataset.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-dataset.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,
|
|
1
|
+
{"version":3,"file":"gbif-get-dataset.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-dataset.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,11 +9,14 @@ export declare const gbifGetOccurrence: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
9
9
|
}, z.core.$strip>, z.ZodObject<{
|
|
10
10
|
key: z.ZodOptional<z.ZodNumber>;
|
|
11
11
|
datasetKey: z.ZodOptional<z.ZodString>;
|
|
12
|
+
occurrenceID: z.ZodOptional<z.ZodString>;
|
|
12
13
|
taxonKey: z.ZodOptional<z.ZodNumber>;
|
|
13
14
|
scientificName: z.ZodOptional<z.ZodString>;
|
|
14
15
|
canonicalName: z.ZodOptional<z.ZodString>;
|
|
15
16
|
kingdom: z.ZodOptional<z.ZodString>;
|
|
16
17
|
phylum: z.ZodOptional<z.ZodString>;
|
|
18
|
+
class: z.ZodOptional<z.ZodString>;
|
|
19
|
+
classKey: z.ZodOptional<z.ZodNumber>;
|
|
17
20
|
order: z.ZodOptional<z.ZodString>;
|
|
18
21
|
family: z.ZodOptional<z.ZodString>;
|
|
19
22
|
genus: z.ZodOptional<z.ZodString>;
|
|
@@ -27,6 +30,20 @@ export declare const gbifGetOccurrence: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
27
30
|
countryCode: z.ZodOptional<z.ZodString>;
|
|
28
31
|
stateProvince: z.ZodOptional<z.ZodString>;
|
|
29
32
|
locality: z.ZodOptional<z.ZodString>;
|
|
33
|
+
gadm: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
level0: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
gid: z.ZodOptional<z.ZodString>;
|
|
36
|
+
name: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
level1: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
gid: z.ZodOptional<z.ZodString>;
|
|
40
|
+
name: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
level2: z.ZodOptional<z.ZodObject<{
|
|
43
|
+
gid: z.ZodOptional<z.ZodString>;
|
|
44
|
+
name: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
}, z.core.$strip>>;
|
|
30
47
|
publishingCountry: z.ZodOptional<z.ZodString>;
|
|
31
48
|
eventDate: z.ZodOptional<z.ZodString>;
|
|
32
49
|
year: z.ZodOptional<z.ZodNumber>;
|
|
@@ -49,6 +66,10 @@ export declare const gbifGetOccurrence: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
49
66
|
title: z.ZodOptional<z.ZodString>;
|
|
50
67
|
license: z.ZodOptional<z.ZodString>;
|
|
51
68
|
}, z.core.$strip>>>;
|
|
69
|
+
identifiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
70
|
+
type: z.ZodOptional<z.ZodString>;
|
|
71
|
+
identifier: z.ZodOptional<z.ZodString>;
|
|
72
|
+
}, z.core.$strip>>>;
|
|
52
73
|
}, z.core.$strip>, readonly [{
|
|
53
74
|
readonly reason: "not_found";
|
|
54
75
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gbif-get-occurrence.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-occurrence.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"gbif-get-occurrence.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/gbif-get-occurrence.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AA8B3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2S5B,CAAC"}
|
|
@@ -5,6 +5,27 @@
|
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
7
|
import { getGbifService } from '../../../services/gbif/gbif-service.js';
|
|
8
|
+
/** Output schema for one GADM administrative level. A fresh instance per call keeps the emitted JSON Schema inline (no $ref). */
|
|
9
|
+
const gadmLevel = () => z.object({
|
|
10
|
+
gid: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('GADM GID — stable administrative-unit identifier (e.g. SWE, SWE.2_1). May be absent.'),
|
|
14
|
+
name: z.string().optional().describe('Administrative-unit name. May be absent.'),
|
|
15
|
+
});
|
|
16
|
+
/** Keep a GADM level only when it carries a gid or name; drop empty placeholders. */
|
|
17
|
+
const compactLevel = (l) => l && (l.gid || l.name) ? { gid: l.gid, name: l.name } : undefined;
|
|
18
|
+
/** Project the raw GADM object to gid/name at levels 0–2; undefined when no level carries data. */
|
|
19
|
+
const compactGadm = (g) => {
|
|
20
|
+
if (!g)
|
|
21
|
+
return;
|
|
22
|
+
const level0 = compactLevel(g.level0);
|
|
23
|
+
const level1 = compactLevel(g.level1);
|
|
24
|
+
const level2 = compactLevel(g.level2);
|
|
25
|
+
if (!level0 && !level1 && !level2)
|
|
26
|
+
return;
|
|
27
|
+
return { level0, level1, level2 };
|
|
28
|
+
};
|
|
8
29
|
export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
9
30
|
title: 'Get Occurrence Record',
|
|
10
31
|
description: 'Fetch a single occurrence record by its GBIF occurrence key. Returns the complete Darwin Core ' +
|
|
@@ -18,11 +39,17 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
18
39
|
output: z.object({
|
|
19
40
|
key: z.number().optional().describe('GBIF occurrence key.'),
|
|
20
41
|
datasetKey: z.string().optional().describe('UUID of the source dataset.'),
|
|
42
|
+
occurrenceID: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Darwin Core occurrenceID — the source record identifier, often a URL back to the origin record. May be absent.'),
|
|
21
46
|
taxonKey: z.number().optional().describe('Backbone taxon key.'),
|
|
22
47
|
scientificName: z.string().optional().describe('Scientific name from occurrence record.'),
|
|
23
48
|
canonicalName: z.string().optional().describe('Canonical name without authorship.'),
|
|
24
49
|
kingdom: z.string().optional().describe('Kingdom classification.'),
|
|
25
50
|
phylum: z.string().optional().describe('Phylum classification.'),
|
|
51
|
+
class: z.string().optional().describe('Class classification. May be absent.'),
|
|
52
|
+
classKey: z.number().optional().describe('Backbone taxon key for the class. May be absent.'),
|
|
26
53
|
order: z.string().optional().describe('Order classification.'),
|
|
27
54
|
family: z.string().optional().describe('Family classification.'),
|
|
28
55
|
genus: z.string().optional().describe('Genus classification.'),
|
|
@@ -45,6 +72,14 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
45
72
|
countryCode: z.string().optional().describe('ISO 3166-1 alpha-2 country code. May be absent.'),
|
|
46
73
|
stateProvince: z.string().optional().describe('State or province. May be absent.'),
|
|
47
74
|
locality: z.string().optional().describe('Locality description. May be absent.'),
|
|
75
|
+
gadm: z
|
|
76
|
+
.object({
|
|
77
|
+
level0: gadmLevel().optional().describe('GADM level 0 — country. May be absent.'),
|
|
78
|
+
level1: gadmLevel().optional().describe('GADM level 1 — state/province. May be absent.'),
|
|
79
|
+
level2: gadmLevel().optional().describe('GADM level 2 — county/district. May be absent.'),
|
|
80
|
+
})
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('GADM administrative geography — stable GIDs and names at levels 0–2. May be absent.'),
|
|
48
83
|
publishingCountry: z
|
|
49
84
|
.string()
|
|
50
85
|
.optional()
|
|
@@ -87,6 +122,18 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
87
122
|
.describe('A media item (image, audio, video) associated with the occurrence.'))
|
|
88
123
|
.optional()
|
|
89
124
|
.describe('Associated media (images, audio, video). May be absent.'),
|
|
125
|
+
identifiers: z
|
|
126
|
+
.array(z
|
|
127
|
+
.object({
|
|
128
|
+
type: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('Identifier type (e.g. URL, DOI, GBIF_PORTAL). May be absent.'),
|
|
132
|
+
identifier: z.string().optional().describe('The identifier value. May be absent.'),
|
|
133
|
+
})
|
|
134
|
+
.describe('An alternative identifier for the occurrence record.'))
|
|
135
|
+
.optional()
|
|
136
|
+
.describe('Alternative record identifiers from the source. May be absent.'),
|
|
90
137
|
}),
|
|
91
138
|
errors: [
|
|
92
139
|
{
|
|
@@ -118,11 +165,14 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
118
165
|
return {
|
|
119
166
|
key: raw.key,
|
|
120
167
|
datasetKey: raw.datasetKey,
|
|
168
|
+
occurrenceID: raw.occurrenceID,
|
|
121
169
|
taxonKey: raw.taxonKey,
|
|
122
170
|
scientificName: raw.scientificName,
|
|
123
171
|
canonicalName: raw.canonicalName,
|
|
124
172
|
kingdom: raw.kingdom,
|
|
125
173
|
phylum: raw.phylum,
|
|
174
|
+
class: raw.class,
|
|
175
|
+
classKey: raw.classKey,
|
|
126
176
|
order: raw.order,
|
|
127
177
|
family: raw.family,
|
|
128
178
|
genus: raw.genus,
|
|
@@ -136,6 +186,7 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
136
186
|
countryCode: raw.countryCode,
|
|
137
187
|
stateProvince: raw.stateProvince,
|
|
138
188
|
locality: raw.locality,
|
|
189
|
+
gadm: compactGadm(raw.gadm),
|
|
139
190
|
publishingCountry: raw.publishingCountry,
|
|
140
191
|
eventDate: raw.eventDate,
|
|
141
192
|
year: raw.year,
|
|
@@ -160,6 +211,9 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
160
211
|
license: m.license,
|
|
161
212
|
}))
|
|
162
213
|
: undefined,
|
|
214
|
+
identifiers: raw.identifiers?.length
|
|
215
|
+
? raw.identifiers.map((id) => ({ type: id.type, identifier: id.identifier }))
|
|
216
|
+
: undefined,
|
|
163
217
|
};
|
|
164
218
|
},
|
|
165
219
|
format: (result) => {
|
|
@@ -183,6 +237,10 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
183
237
|
taxParts.push(`Kingdom: ${result.kingdom}`);
|
|
184
238
|
if (result.phylum)
|
|
185
239
|
taxParts.push(`Phylum: ${result.phylum}`);
|
|
240
|
+
if (result.class)
|
|
241
|
+
taxParts.push(`Class: ${result.class}${result.classKey != null ? ` (${result.classKey})` : ''}`);
|
|
242
|
+
else if (result.classKey != null)
|
|
243
|
+
taxParts.push(`Class key: ${result.classKey}`);
|
|
186
244
|
if (result.order)
|
|
187
245
|
taxParts.push(`Order: ${result.order}`);
|
|
188
246
|
if (result.family)
|
|
@@ -225,6 +283,16 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
225
283
|
lines.push(`**Location:** ${geo.join(', ')}${result.countryCode ? ` (${result.countryCode})` : ''}`);
|
|
226
284
|
else if (result.countryCode)
|
|
227
285
|
lines.push(`**Country code:** ${result.countryCode}`);
|
|
286
|
+
if (result.gadm) {
|
|
287
|
+
const gadmParts = [];
|
|
288
|
+
for (const lvl of [result.gadm.level0, result.gadm.level1, result.gadm.level2]) {
|
|
289
|
+
if (lvl?.name || lvl?.gid) {
|
|
290
|
+
gadmParts.push(`${lvl.name ?? ''}${lvl.gid ? ` (${lvl.gid})` : ''}`.trim());
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (gadmParts.length > 0)
|
|
294
|
+
lines.push(`**GADM:** ${gadmParts.join(' › ')}`);
|
|
295
|
+
}
|
|
228
296
|
if (result.recordedBy)
|
|
229
297
|
lines.push(`**Recorded by:** ${result.recordedBy}`);
|
|
230
298
|
if (result.identifiedBy)
|
|
@@ -242,6 +310,8 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
242
310
|
lines.push(`**Collection:** ${result.collectionCode}`);
|
|
243
311
|
if (result.catalogNumber)
|
|
244
312
|
lines.push(`**Catalog number:** ${result.catalogNumber}`);
|
|
313
|
+
if (result.occurrenceID)
|
|
314
|
+
lines.push(`**Occurrence ID:** ${result.occurrenceID}`);
|
|
245
315
|
if (result.datasetKey)
|
|
246
316
|
lines.push(`**Dataset key:** ${result.datasetKey}`);
|
|
247
317
|
if (result.publishingCountry)
|
|
@@ -256,6 +326,13 @@ export const gbifGetOccurrence = tool('gbif_get_occurrence', {
|
|
|
256
326
|
lines.push(` - ${mediaType}${mediaTitle}${mediaFmt}${mediaLicense}${m.identifier ? `: ${m.identifier}` : ''}`);
|
|
257
327
|
}
|
|
258
328
|
}
|
|
329
|
+
if (result.identifiers?.length) {
|
|
330
|
+
lines.push(`\n**Identifiers:** ${result.identifiers.length} item(s)`);
|
|
331
|
+
for (const id of result.identifiers) {
|
|
332
|
+
const idType = id.type ? `[${id.type}] ` : '';
|
|
333
|
+
lines.push(` - ${idType}${id.identifier ?? '(no value)'}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
259
336
|
if (result.issues?.length)
|
|
260
337
|
lines.push(`\n**Quality issues:** ${result.issues.join(', ')}`);
|
|
261
338
|
return [{ type: 'text', text: lines.join('\n') }];
|