Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
query_healthB

Query Garmin health data (e.g. heart rate, sleep, stress, body battery) for a field over a date range. resolution is "daily" or "intraday" — most fields only support one of the two (e.g. resting_heart_rate is daily-only, heart_rate_series is intraday-only); pass the field name that matches what you want, see list_available_fields() for the full list. This parameter is accepted for forward compatibility but not currently used to pick between two resolutions of the same field, since no field in this archive currently offers both — each field's own stored resolution already determines whether the answer is a single daily value or a full timeseries.

v1.7.1.1 field-filter fix (2026-08-28 session): field is now passed through to the SQLite branch — previously it was silently dropped, so every call returned all ~26 health fields regardless of what was asked for, including this archive's intraday *_series fields (full day-long timeseries), inflating a single-value answer to hundreds of KB and confusing small local LLMs summarizing the result.

v1.7.1.6 unit field: every field in the returned result now carries a "unit" key alongside "values"/"fallback"/ "source_resolution" — see FIELD_UNITS in mcp_field_registry.py. Applied AFTER the routing weiche below, so it covers both branches identically (today, only the SQLite branch is ever actually taken — see _route_query()'s docstring).

v1.7.1.9 unknown-field detection (this session): mirrors query_context()'s v1.7.1.4 fix, applied here with a delayed session (see that function's docstring for the original rationale -- a valid-but-dataless field and an unregistered field previously returned the identical silent {"health": {}}, leaving the caller unable to tell the two apart). Checked BEFORE the _route_query() switch below, so it applies regardless of which branch (sqlite/ live) ends up serving the request -- the field registry itself (mcp_map.list_available_fields) is unrelated to that routing decision.

Three unknown-field outcomes, checked in this order:

  1. Unambiguous near-match against the known health field names (e.g. a typo) -> auto-resolved, field_used replaces the caller's input transparently, but the substitution is always visible via _meta.field_resolved_from / _meta.field_used — never a silent rewrite.

  2. The field IS registered, but under query_context's domain, not query_health's (e.g. "temperature_max") -> a domain-specific error naming query_context, no did_you_mean list (a health-domain suggestion would be wrong here).

  3. Neither of the above (no close match, and not a query_context field either) -> a generic "unknown field" error, with a did_you_mean suggestion list when difflib found any candidates, without one when it found none.

A valid field's result (with or without data in range) is returned exactly as before this session — none of the above runs unless field is unrecognized.

Deliberately NOT addressed here (see AKTIONSPLAN_v1.7.1.9_ health_fallback.md Abschnitt 3/4 for the full analysis): a model that picks a completely unrelated but real, registered field instead of a near-match typo (verified empirically against the 2026-09-05 test run's Hermes3 cases, e.g. resting_heart_rate returned for a steps question) is not a field-registry problem — no near-match exists for the fallback to catch, since the wrong field is itself a valid, unrelated field name. Tracked as a parking-lot item (query_health docstring example-field guidance), not pulled into this fix.

v1.7.1.9 Session 2 -- sleep_score fan-out: "sleep_score" is itself an already-valid, registered field (unlike the alias candidates below), so it would never reach the unknown-field checks above -- it always short-circuits straight to the normal valid-field path. Checked here, BEFORE the bundle check, precisely because it is valid and would otherwise never trigger any of the outcomes below. Fans out to the two closely related fields sleep_score_feedback and sleep_score_qualifier and returns all three together in the same {"garmin": {field: {...}}} shape a normal multi-field result already has -- no new result shape, _enrich_with_units() handles it unchanged. _meta.field_resolved_from is set to "sleep_score" so the fan-out is visible; no field_used, since all three delivered field names are already the dict's own keys, unlike the 1:1 alias case where the substitution would otherwise be invisible. A direct call to "sleep_score_feedback" or "sleep_score_qualifier" is NOT affected -- only the exact bare "sleep_score" triggers this.

v1.7.1.9 Session 2 -- short-form alias mapping: three short-form field names (steps, hrv, hill) sit far enough below any workable difflib cutoff against their real target field names (steps_series, hrv_last_night, hill_score -- confirmed down to cutoff=0.7, see NOTES_v1.7.1.9.md Session 2) that no cutoff tuning can catch them without introducing new ambiguities elsewhere. HEALTH_FIELD_ALIASES below resolves these explicitly, checked before outcome 1's near-match logic (an alias hit is more certain than a near-match and should not have to pass through it). "spo2" was considered and explicitly excluded (real collision between spo2_avg and spo2_series, no reliable disambiguation signal available -- see NOTES_v1.7.1.9.md Session 2 for the full analysis).

query_contextC

Query external context data (weather, pollen, air quality) for a field over a date range. Fans out across all sources that recognize the field.

v1.7.1.3 field-filter fix: field is now passed through to the SQLite branch — previously it was silently dropped (this call site never forwarded it at all), so every call returned all four context categories (weather/brightsky/airquality/pollen) regardless of what was asked for, inflating a single-value answer to hundreds of KB and confusing small local LLMs summarizing the result. Same fix as query_health()'s v1.7.1.1/v1.7.1.2 field-filter, applied here with a one-session delay.

v1.7.1.4 unknown-field detection (this session): a field that is valid for query_context() but unregistered anywhere in the context domain previously returned the same silent {"context": {}} as a registered field with no data in the requested range — the caller (LLM or human) could not tell "field does not exist" apart from "field exists, no data here". This is checked BEFORE the _route_query() switch below, so the check applies regardless of which branch (sqlite/live) ends up serving the request — the field registry itself (mcp_map.list_available_fields) is unrelated to that routing decision.

Three unknown-field outcomes, checked in this order:

  1. Unambiguous near-match against the known context field names (e.g. a typo) -> auto-resolved, field_used replaces the caller's input transparently, but the substitution is always visible via _meta.field_resolved_from / _meta.field_used — never a silent rewrite.

  2. The field IS registered, but under query_health's domain, not query_context's (e.g. "sleep") -> a domain-specific error naming query_health, no did_you_mean list (a context-domain suggestion would be wrong here).

  3. Neither of the above (e.g. a category name like "weather", or no close match at all) -> a generic "unknown field" error, with a did_you_mean suggestion list when difflib found any candidates, without one when it found none.

A valid field's result (with or without data in range) is returned exactly as before this session — none of the above runs unless field is unrecognized.

v1.7.1.5 category bundles (this session): a field value naming a known bundle key ("weather"/"pollen"/"air") is resolved BEFORE any of the three unknown-field outcomes above -- a bundle name is never a registered field itself, so without this check it would always fall through to the generic "unknown field" branch. Each bundle field is queried individually through the SAME sqlite/live routing weiche used everywhere else in this function -- the bundle path only adds collection, flattening, and collision tie-breaking on top, it does not bypass or duplicate the existing data-access path. See _CONTEXT_CATEGORY_BUNDLES above for the priority-list mechanics.

v1.7.1.11 Session 4 -- resolution is decided by the field name itself, same principle as query_health(): a "_series" suffix always means intraday/timeseries data, a plain field name always means a single daily value -- no field in this archive offers both under one name, so the caller already knows which shape to expect before the query even runs. This holds regardless of which branch (sqlite/live) below ends up serving the request -- both branches return the same "values" contract (see mcp_sql.get_context_range() / clients/mcp_sql.py, and maps/_context_io.py's read_summary_field()/ read_raw_field() for the underlying {"date","value"} vs. {"date","series"} shapes).

v1.7.1.12 -- CONTEXT_FIELD_ALIASES / CONTEXT_FIELD_AMBIGUOUS checked here, BEFORE the bundle check, mirroring query_health()'s HEALTH_FIELD_ALIASES ordering (an alias hit is more certain than a near-match and should not have to pass through the bundle or difflib logic). Three outcomes now precede the pre-existing bundle/ unknown-field handling below:

  1. CONTEXT_FIELD_ALIASES hit -> auto-resolved, field_used/ field_resolved_from set, same as the alias path in query_health().

  2. CONTEXT_FIELD_AMBIGUOUS hit -> NOT resolved. Returns the existing error/did_you_mean shape with a field-specific error message and the known candidate list as did_you_mean -- no new response shape (see CONTEXT_FIELD_AMBIGUOUS's own comment for the rationale). field_used/field_resolved_from are NOT set.

  3. Neither -> falls through unchanged to the bundle check and the existing unknown-field difflib logic below. See NOTES_v1.7.1.12.md for the full candidate-by-candidate analysis behind both tables.

query_fit_activitiesA

Query FIT activity data for a field over a date range. Not yet available (FIT pipeline is v1.8) — returns a clean "not available" result until then, never an error.

query_rawB

Query raw, unprocessed archive data for a passthrough field over a date range. domain restricts the query to one domain ("health", "fit", "context") — omit to search all domains.

get_archive_metadataA

Request archive-state metadata. kind selects the artefact: "stats" (coverage/quality overview — use this for "how big/healthy is my archive" questions), "device_table", "quality_log", "source_api_log", "token_log", "capability_config", "daily_logs", "fail_logs", "recent_logs".

date_from/date_to (ISO "YYYY-MM-DD", inclusive) optionally narrow "quality_log", "source_api_log", "daily_logs", "fail_logs", and "recent_logs" to a date range — ignored for the other four kinds.

v1.7.1.16 clarification (no behavior change): the 30-day-default- plus-"note" convenience described below only exists on the LIVE path (mcp_map.get_archive_metadata() -> metadata_map.py). On the SQLite-cached path (mcp_sql.get_metadata_range() — the one actually taken today, see _route_query()), omitting both dates for one of the five date-filterable kinds instead returns an empty result with no "note" at all; this is deliberate on that path (see mcp_sql.get_metadata_range()'s own docstring: "no 30-day-default fallback in this cache read"), not a bug — but the difference was previously undocumented at this public tool's own docstring level.

Live path: omit both to get the last 30 days of that kind rather than the full archive history; the response then includes a "note" field saying so. Pass both explicitly for a specific or wider range on either path.

list_available_fieldsA

List all queryable fields, grouped by domain and source. Use this first if the set of available fields is unknown — omit domain for a full overview, or pass "health"/"context"/"fit" to narrow it.

v1.7.1.6 unit field (this session): the result gains a "units" key alongside the existing "fields" key — a flat {field_name: unit} dict covering every field returned under "fields" for the requested domain(s). Additive only: "fields" itself keeps its original shape unchanged (a nested {domain: {source: [field, ...]}} name list), so existing callers reading "fields" (e.g. mcp_context.py's _resolve_context_bundle(), which iterates the plain name lists) are unaffected. See FIELD_UNITS in mcp_field_registry.py for the unit values and their source.

refresh_cacheA

Manually trigger a SQLite cache sync against the archive — use this if recent archive changes (a sync just run, a backfill/recheck just completed) might not yet be reflected in query results. Runs the same sync the server already performs automatically at startup. May take a while on a large pending delta (long idle period since the last sync) — this call blocks until the sync finishes.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A3.7/5.0

Scored across 7 tools

Disambiguation4/5

The core split is clear: query_health vs query_context vs query_fit_activities vs query_raw are separated by domain and by processed-vs-raw semantics, which the descriptions state explicitly. Minor overlap remains because query_raw also accepts a date range, field, and domain filter, so an agent could plausibly reach for it instead of the domain-specific query tools, and query_fit_activities is a placeholder that always returns 'not available'.

Naming Consistency5/5

Every tool follows a clean snake_case verb_noun pattern (query_health, query_context, query_raw, query_fit_activities, get_archive_metadata, list_available_fields, refresh_cache). The verb varies appropriately with the operation type (query/get/list/refresh) rather than being arbitrarily inconsistent.

Tool Count5/5

Seven tools is well-scoped for a local archive reader: three domain queries, a raw passthrough, metadata, field discovery, and cache refresh. Nothing feels padded, and no obvious capability is missing for the stated scope.

Completeness4/5

Coverage of the archive lifecycle is solid — field discovery, health/context/raw queries, metadata of many kinds, and a manual sync trigger address the main workflows. The one real gap is that activity/FIT data is unreachable until v1.8, so any fitness-activity question dead-ends at query_fit_activities.

Maintenance

ActivityActive
ResponsivenessResponsive