bls-labor-mcp-server
Fetch US Bureau of Labor Statistics data — CPI, unemployment, wages, JOLTS, and more via MCP.
¿Debería usar esto?
Calidad y seguridad
Basado en el análisis automatizado de las definiciones de herramientas y el cumplimiento del protocolo.
Costo de contexto
Este es el número aproximado de tokens que se consumen cada vez que las herramientas del servidor se cargan en el contexto de un modelo. Los recuentos más altos reducen la atención disponible para otras tareas.
Instalar
Instalación con un clic
Agrega esto a tu archivo `claude_desktop_config.json`:
{
"mcpServers": {
"bls-labor-mcp-server": {
"command": "bun",
"args": [
"@cyanheads/bls-labor-mcp-server"
]
}
}
}Paquetes ejecutables
0.5.7streamable-httpPuntos de conexión remotos
https://bls-labor.caseyjhand.com/mcpstreamable-httpQué puede hacer
Inventario de herramientas
Herramientas (6)
🟢bls_list_surveys(category)
List BLS survey programs with their abbreviation codes, full names, and metadata about calculation support and annual averages. Use to discover which survey covers a topic before calling bls_search_series; bls_search_series covers only the surveys in its offline index, and series in the others are fetched by SeriesID with bls_get_series. Optional category filter narrows results to prices, employment, wages, productivity, injuries, or time_use surveys.
Esquema de entrada
{
"type": "object",
"properties": {
"category": {
"description": "Optional category filter. One of: prices, employment, wages, productivity, injuries, time_use. A survey can appear under more than one category (CES under employment and wages). Omit to list all surveys.",
"type": "string",
"enum": [
"prices",
"employment",
"wages",
"productivity",
"injuries",
"time_use"
]
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"surveys": {
"type": "array",
"items": {
"type": "object",
"properties": {
"abbreviation": {
"type": "string",
"description": "Two-character survey abbreviation (e.g. CU, CE, LN)."
},
"name": {
"type": "string",
"description": "Full survey name (e.g. CPI - All Urban Consumers)."
},
"allowsNetChange": {
"type": "boolean",
"description": "True when the survey supports BLS-computed net change via calculations=true."
},
"allowsPercentChange": {
"type": "boolean",
"description": "True when the survey supports BLS-computed percent change via calculations=true."
},
"hasAnnualAverages": {
"type": "boolean",
"description": "True when BLS reports that the survey publishes annual average observations. Advisory only: it does not predict whether a given series returns annual-average rows for bls_get_series with annual_average=true — LN, CE, LA and SM report true yet return none. Read annualAverageRows on that response for what actually came back."
}
},
"required": [
"abbreviation",
"name",
"allowsNetChange",
"allowsPercentChange",
"hasAnnualAverages"
],
"additionalProperties": false,
"description": "A BLS survey program entry."
},
"description": "BLS survey programs matching the filter, sorted alphabetically by abbreviation."
},
"total": {
"type": "number",
"description": "Total surveys returned."
},
"categoryFilter": {
"description": "Category filter applied, if any. Absent when all surveys were listed.",
"type": "string"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `invalid_api_key`: BLS rejected the configured BLS_API_KEY as invalid. `service_unavailable`: BLS /surveys API is unreachable or returns a non-200 response. `serialization_failure`: BLS /surveys response cannot be parsed (malformed JSON or unexpected schema). Other values are possible when a failure originates below the handler.",
"examples": [
"invalid_api_key",
"service_unavailable",
"serialization_failure"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"surveys",
"total"
]
},
{
"required": [
"error"
]
}
]
}🟢bls_search_series(query, survey, area, seasonal_adjustment, limit, ...)
Search the BLS series catalog by natural language query, survey code, geographic area, or keywords to resolve cryptic SeriesIDs. Returns matching series with decoded components (survey, area, item, seasonal flag, and publication frequency for surveys that publish one series at several frequencies) and plain-language names, ranked by relevance and paged with limit and offset. Use this before bls_get_series when you have a concept but not a SeriesID. Operates offline against an index of the major surveys — no API quota consumed. Survey filter accepts the two-letter code of an indexed survey (CU, AP, CE, LN, LA, PC, WP, JT, EC, PR, MP, CW, CM, CI; OE only when the server sets BLS_CATALOG_INCLUDE_OES=true); series in other surveys are fetched by SeriesID with bls_get_series. Area filter accepts state names, MSA names, or FIPS area codes.
Esquema de entrada
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1,
"description": "Natural language or keyword query (e.g. \"unemployment rate\", \"CPI food\", \"nonfarm payrolls\"). Also accepts a SeriesID directly for exact lookup."
},
"survey": {
"description": "Two-letter LABSTAT survey abbreviation to filter results, case-insensitive (e.g. CU for CPI, CE for CES, LN for CPS, LA for LAUS, JT for JOLTS). Indexed surveys: CU, AP, CE, LN, LA, PC, WP, JT, EC, PR, MP, CW, CM, CI; OE (OEWS) only when the server sets BLS_CATALOG_INCLUDE_OES=true. Omit to search all indexed surveys.",
"type": "string"
},
"area": {
"description": "State name, MSA name, or FIPS area code to narrow results to a geographic area — a case-insensitive substring of the area name, title, or SeriesID. Omit for national series, which then rank first among equal matches.",
"type": "string"
},
"seasonal_adjustment": {
"description": "When true, return only seasonally adjusted series. When false, return only not-seasonally-adjusted. Omit to return both.",
"type": "boolean"
},
"limit": {
"default": 10,
"description": "Maximum number of results to return (1–50, default 10).",
"type": "integer",
"minimum": 1,
"maximum": 50
},
"offset": {
"default": 0,
"description": "Number of ranked results to skip, for paging past the first page (default 0). Pass nextOffset from the previous response to get the next page.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"query"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"seriesId": {
"type": "string",
"description": "BLS SeriesID — pass to bls_get_series or bls_get_latest to fetch data."
},
"title": {
"type": "string",
"description": "Plain-language series name."
},
"survey": {
"type": "string",
"description": "Survey abbreviation (e.g. CU, CE, LN)."
},
"area": {
"description": "Geographic area name, when decoded.",
"type": "string"
},
"item": {
"description": "Item or subject name, when decoded.",
"type": "string"
},
"seasonal": {
"type": "string",
"description": "Seasonality descriptor matching the data-tool form: \"Seasonally Adjusted\" or \"Not Seasonally Adjusted\"."
},
"frequency": {
"description": "Publication frequency — \"Monthly\", \"Semi-Annual\", \"Quarterly\", or \"Annual\" — for CU, CW, and LN, which publish one series at several frequencies under the same title. Absent for other surveys.",
"type": "string"
}
},
"required": [
"seriesId",
"title",
"survey",
"seasonal"
],
"additionalProperties": false,
"description": "A matching BLS series entry."
},
"description": "Matching series, ordered by relevance."
},
"totalCount": {
"type": "number",
"description": "Total ranked results after every filter (area included), before limit and offset. A lower bound when capped is true — the catalog index may contain more matching series."
},
"truncated": {
"description": "True when ranked results remain past this page; nextOffset fetches them.",
"type": "boolean"
},
"shown": {
"description": "Number of series returned in this response.",
"type": "number"
},
"cap": {
"description": "The result limit that capped the returned list.",
"type": "number"
},
"nextOffset": {
"description": "Offset of the next page. Present only when truncated is true.",
"type": "number"
},
"capped": {
"type": "boolean",
"description": "True when the FTS candidate pool, after the survey/area/seasonal filters, reached the internal cap (~1000). totalCount is then a lower bound and offset paging stops at the pool. Narrow the query, add filters, or use a direct SeriesID to get an exact count."
},
"catalogSize": {
"type": "number",
"description": "Total series in the loaded catalog index. Distinguishes an empty-result search from a failed catalog load."
},
"effectiveQuery": {
"type": "string",
"description": "Query string as the server received and searched on. Confirms interpretation for self-correction."
},
"surveyFilter": {
"description": "Survey filter applied, trimmed and uppercased. Absent when no survey filter was passed or it was blank.",
"type": "string"
},
"areaFilter": {
"description": "Area filter applied, trimmed. Absent when no area filter was passed or it was blank.",
"type": "string"
},
"seasonalFilter": {
"description": "Seasonal-adjustment filter applied, if any. Absent when not passed.",
"type": "boolean"
},
"limitApplied": {
"type": "number",
"description": "Result limit in effect (defaults to 10 when omitted)."
},
"offsetApplied": {
"type": "number",
"description": "Offset in effect (defaults to 0 when omitted)."
},
"notice": {
"description": "Guidance on the result: the survey is not in the offline index, the offset is past the last result, nothing matched, which offset fetches the next page, or, on the last page of a capped list, that other series may also match. Absent when this page ends an uncapped list with results.",
"type": "string"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `catalog_unavailable`: The catalog index failed to load at startup. Other values are possible when a failure originates below the handler.",
"examples": [
"catalog_unavailable"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"series",
"totalCount",
"capped",
"catalogSize",
"effectiveQuery",
"limitApplied",
"offsetApplied"
]
},
{
"required": [
"error"
]
}
]
}🟢bls_get_latest(series_ids)
Return the single most recent observation for one or more BLS series. Use for "what is X right now" questions — the current unemployment rate, the latest CPI reading, etc. Each series consumes one API query against the 500/day limit; for the current value of many series, bls_get_series with a 1-year window is more quota-efficient (one query for up to 50 series). Recommended limit: 10 series; maximum: 50.
Esquema de entrada
{
"type": "object",
"properties": {
"series_ids": {
"minItems": 1,
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "One or more BLS SeriesIDs (1–50). Each consumes one daily API query. Use bls_search_series to resolve concepts to SeriesIDs. Recommended: ≤10 series."
}
},
"required": [
"series_ids"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"seriesId": {
"type": "string",
"description": "BLS SeriesID."
},
"title": {
"description": "Series name when returned by the API.",
"type": "string"
},
"area": {
"description": "Geographic area when returned by the API.",
"type": "string"
},
"item": {
"description": "Item/subject when returned by the API.",
"type": "string"
},
"seasonal": {
"description": "Seasonality indicator when returned by the API.",
"type": "string"
},
"latestObservation": {
"description": "Most recent observation.",
"type": "object",
"properties": {
"year": {
"type": "string",
"description": "Observation year (e.g. \"2024\")."
},
"period": {
"type": "string",
"description": "Observation period code (e.g. \"M12\" for December, \"Q01\" for Q1)."
},
"periodName": {
"description": "Human-readable period name (e.g. \"December\").",
"type": "string"
},
"value": {
"type": "string",
"description": "Raw observation value from BLS. The literal \"-\" means unavailable; check available before arithmetic and read footnotes for the reason."
},
"available": {
"type": "boolean",
"description": "False when BLS published the \"-\" missing-value sentinel for this period."
},
"footnotes": {
"description": "Footnote codes and text, when present.",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"year",
"period",
"value",
"available"
],
"additionalProperties": false
}
},
"required": [
"seriesId"
],
"additionalProperties": false,
"description": "Latest observation result for one BLS series."
},
"description": "Successfully fetched series with their latest observations. Series that failed appear in failed[] instead."
},
"succeeded": {
"type": "number",
"description": "Number of series with a successfully fetched observation."
},
"failed": {
"type": "array",
"items": {
"type": "object",
"properties": {
"seriesId": {
"type": "string",
"description": "SeriesID that failed."
},
"error": {
"type": "string",
"description": "Error message. Common values: \"Series does not exist\" or \"Invalid Series\" (invalid SeriesID — use bls_search_series to find valid IDs), \"No observations returned\" (series exists but has no current data). The generic \"Your request has failed. Please check your input parameters, and try your request again.\" means BLS rejected the request itself twice, the second time without catalog metadata, rather than naming the SeriesID. A quota, API-key, or database-lock failure lands here only when another series returned an observation; notice then names its reason and recovery."
}
},
"required": [
"seriesId",
"error"
],
"additionalProperties": false,
"description": "A series that could not be fetched, e.g. due to an invalid SeriesID or empty data window."
},
"description": "Series that failed to fetch. Inspect seriesId and error for per-item details. Not-found series appear here rather than as a tool-level error."
},
"notice": {
"description": "Guidance when one or more series failed — bls_search_series for a SeriesID BLS rejected, or the reason and recovery for a quota, API-key, or database-lock failure that left other series answered. Absent when all series returned data.",
"type": "string"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `invalid_api_key`: BLS rejected the configured BLS_API_KEY as invalid, and no requested series returned an observation. `quota_exceeded`: The BLS API 500 query/day limit has been reached, and no requested series returned an observation. `series_locked`: The BLS database is temporarily locked for the requested series, and no requested series returned an observation. Other values are possible when a failure originates below the handler.",
"examples": [
"invalid_api_key",
"quota_exceeded",
"series_locked"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"results",
"succeeded",
"failed"
]
},
{
"required": [
"error"
]
}
]
}🟢bls_get_series(series_ids, start_year, end_year, calculations, annual_average)
Fetch time-series data for 1–50 BLS series by SeriesID in a single API request (one query against the 500/day limit). Supports optional year range (up to 20 years per request) and BLS-computed period-over-period calculations (net change and percent change; a survey returns whichever it supports and silently omits the rest — CPI and PPI return percent change only, the inflation rate). BLS can publish a '-' missing-value sentinel; check observation.available before arithmetic. Set annual_average to add each year's annual-average row, which is that year's mean rather than an additional period. When the total observation count would exceed the inline context budget, results spill to a canvas dataframe and the response includes a dataset.name handle. Call bls_dataframe_describe with that name to inspect the dataframe schema, then use the name in bls_dataframe_query SQL. Use bls_search_series first if you need to resolve a concept to a SeriesID.
Esquema de entrada
{
"type": "object",
"properties": {
"series_ids": {
"minItems": 1,
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "One or more BLS SeriesIDs (1–50). The entire batch counts as one API query. Use bls_search_series to resolve concepts to SeriesIDs."
},
"start_year": {
"description": "Start year for the data range (inclusive). The BLS API allows up to 20 years per request and requires both bounds or neither: supplying start_year alone resolves end_year to the current year, capped at start_year + 19 so the window stays inside the 20-year limit. Omit both for the API default (typically 3–20 years depending on survey).",
"type": "integer",
"minimum": 1900,
"maximum": 2100
},
"end_year": {
"description": "End year for the data range (inclusive). Supplying it without start_year is rejected before the request — BLS applies no default start year alongside an explicit end year. Pair it with start_year, or omit both for the API default window.",
"type": "integer",
"minimum": 1900,
"maximum": 2100
},
"calculations": {
"description": "When true, request BLS-computed period-over-period calculations. The flag is a single boolean (you cannot select an individual calculation type), but the API returns whichever the survey supports and omits the rest — CPI and PPI return percent change only (the inflation rate), and a survey that supports neither simply returns its observations without calculation fields. Requesting calculations never fails, so it is always safe to set; consult bls_list_surveys (allowsNetChange / allowsPercentChange) only to predict which fields will come back. Monthly-cadence series return each supported change type over 1, 3, 6, and 12-month intervals; other cadences return a subset. A series served from the local observation mirror, when the server runs one, carries no calculation fields; enrichment.calculationsApplied is then false and notice names it.",
"type": "boolean"
},
"annual_average": {
"default": false,
"description": "When true, add each year's annual-average row to the observations. An annual average is the mean of that year's real periods, returned as an extra row named \"Annual\" with period M13 (monthly series), Q05 (quarterly) or S03 (semiannual) — not an additional month or quarter, so it must be excluded from any sum or average over observations. Defaults to false, which returns real periods only; check available before aggregating. Independent of start_year/end_year. Surveys that publish no annual averages return the same rows either way; enrichment.annualAverageRows reports how many rows were actually added.",
"type": "boolean"
}
},
"required": [
"series_ids"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"seriesId": {
"type": "string",
"description": "BLS SeriesID."
},
"title": {
"description": "Series name when returned by the API.",
"type": "string"
},
"area": {
"description": "Geographic area when returned by the API.",
"type": "string"
},
"item": {
"description": "Item/subject when returned by the API.",
"type": "string"
},
"seasonal": {
"description": "Seasonality indicator when returned by the API.",
"type": "string"
},
"observationCount": {
"type": "number",
"description": "Total period rows for this series, including unavailable BLS placeholder rows. When spilled to canvas, all rows are on the dataframe; inline only shows a preview."
},
"availableObservationCount": {
"type": "number",
"description": "Rows with a published numeric value, excluding the BLS \"-\" sentinel."
},
"observations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"year": {
"type": "string",
"description": "Observation year."
},
"period": {
"type": "string",
"description": "BLS period code: M01–M12 are months, Q01–Q04 quarters, S01–S02 semiannual halves. M13, Q05 and S03 are not further periods — each is the mean of that year's real observations, named \"Annual\", and appears only when annual_average is true. Exclude them from any sum or average over observations."
},
"periodName": {
"description": "Human-readable period name.",
"type": "string"
},
"value": {
"type": "string",
"description": "Raw observation value from BLS. The literal \"-\" means unavailable; check available before arithmetic and read footnotes for the reason."
},
"available": {
"type": "boolean",
"description": "False when BLS published the \"-\" missing-value sentinel for this period."
},
"footnotes": {
"description": "Footnote codes and text, when present.",
"type": "array",
"items": {
"type": "string"
}
},
"netChange1Month": {
"description": "1-month net change (when calculations=true).",
"type": "string"
},
"netChange3Month": {
"description": "3-month net change (when calculations=true).",
"type": "string"
},
"netChange6Month": {
"description": "6-month net change (when calculations=true).",
"type": "string"
},
"netChange12Month": {
"description": "12-month net change (when calculations=true).",
"type": "string"
},
"pctChange1Month": {
"description": "1-month percent change (when calculations=true).",
"type": "string"
},
"pctChange3Month": {
"description": "3-month percent change (when calculations=true).",
"type": "string"
},
"pctChange6Month": {
"description": "6-month percent change (when calculations=true).",
"type": "string"
},
"pctChange12Month": {
"description": "12-month percent change (when calculations=true).",
"type": "string"
}
},
"required": [
"year",
"period",
"value",
"available"
],
"additionalProperties": false,
"description": "One observation data point."
},
"description": "Inline observations. All observations when no spillover; preview rows when spilled to canvas."
}
},
"required": [
"seriesId",
"observationCount",
"availableObservationCount",
"observations"
],
"additionalProperties": false,
"description": "Time-series data for one BLS series."
},
"description": "Series data, in request order."
},
"dataset": {
"description": "Canvas dataframe handle — present when the observation volume exceeded the inline budget. Call bls_dataframe_describe with dataset.name to inspect column_schema, then use that table name in bls_dataframe_query SQL across the full data.",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Canvas table name (df_XXXXX_XXXXX). Pass to bls_dataframe_describe first to inspect column_schema, then use it in bls_dataframe_query SQL."
},
"row_count": {
"type": "number",
"description": "Total rows in the canvas table."
},
"expires_at": {
"type": "string",
"description": "ISO 8601 expiry timestamp (sliding 24h window)."
}
},
"required": [
"name",
"row_count",
"expires_at"
],
"additionalProperties": false
},
"spilled": {
"type": "boolean",
"description": "True when results spilled to canvas due to inline budget overflow."
},
"totalObservations": {
"type": "number",
"description": "Total observation rows across all requested series."
},
"availableObservations": {
"type": "number",
"description": "Rows with a published numeric value."
},
"unavailableObservations": {
"type": "number",
"description": "Rows carrying the BLS \"-\" missing-value sentinel."
},
"seriesRequested": {
"type": "number",
"description": "Number of SeriesIDs requested. Do not compare it against series[] length to find empty series — a SeriesID that returned no data is still listed in series[] with observationCount 0. Check observationCount per entry, or read notice, which names every SeriesID that came back empty."
},
"startYearApplied": {
"description": "Start year applied to the query, whether it was served live or from the local observation mirror. Absent when no year range was in effect.",
"type": "number"
},
"endYearApplied": {
"description": "End year applied to the query. Resolved from start_year when end_year was omitted, so it can differ from the requested range; notice names the cap when the 20-year window decided it. Absent when no year range was in effect.",
"type": "number"
},
"calculationsApplied": {
"description": "Whether BLS net/percent-change calculations were applied. True when calculations=true reached BLS for every returned series; false when calculations=false, or when the local observation mirror served a series, whose rows carry no calculations — notice then names those series. Absent when calculations was omitted.",
"type": "boolean"
},
"annualAverageApplied": {
"type": "boolean",
"description": "Whether annual-average rows were requested. When false, observations hold real periods only; filter on available before aggregation."
},
"annualAverageRows": {
"description": "How many observations across all series are annual-average rows (period M13/Q05/S03). Present only when annual_average is true; 0 means none of the requested surveys publish annual averages.",
"type": "number"
},
"notice": {
"description": "Guidance for agents — names any SeriesID that returned zero observations with its reason (including a failed live fallback's reason and recovery for a SeriesID the local observation mirror does not hold), names mirror-served series that lack requested calculations, reports a resolved end_year the 20-year window capped, and reports the bls_dataframe_describe then bls_dataframe_query workflow when results spill to canvas. Absent when every requested series returned data over the window as asked and it all fit inline.",
"type": "string"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `invalid_api_key`: BLS rejected the configured BLS_API_KEY as invalid. `quota_exceeded`: The BLS API 500 query/day limit has been reached. `request_rejected`: BLS returned a non-success status with a message matching no known failure mode, both for the request and for its automatic re-issue without catalog metadata — e.g. a rejected combination of request parameters. `series_not_found`: No requested series returned data and at least one SeriesID does not exist. A batch mixing an invalid SeriesID with one BLS has no data for lands here too, and names both in the message and recovery hint. `series_locked`: The BLS database is temporarily locked for the requested series. `no_data_for_period`: The requested year range is unusable before the request — start_year after end_year, a span of 20 years or more, or end_year without start_year — or BLS returned data for none of the requested series over the range. `calculations_not_supported`: calculations=true was requested for a survey that does not support it. `canvas_unavailable`: The result set exceeds the inline budget and canvas (DuckDB) is not configured. `canvas_registration_failed`: The result set exceeds the inline budget and canvas is configured, but registering the dataframe failed. Other values are possible when a failure originates below the handler.",
"examples": [
"invalid_api_key",
"quota_exceeded",
"request_rejected",
"series_not_found",
"series_locked",
"no_data_for_period",
"calculations_not_supported",
"canvas_unavailable",
"canvas_registration_failed"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"series",
"spilled",
"totalObservations",
"availableObservations",
"unavailableObservations",
"seriesRequested",
"annualAverageApplied"
]
},
{
"required": [
"error"
]
}
]
}🟢bls_dataframe_describe(name)
List canvas dataframes materialized by bls_get_series or by bls_dataframe_query register_as, with provenance (source tool, query parameters), TTL, row count, and column schema. Use before writing SQL to confirm column names and types. Lazy-sweeps expired tables before responding. Requires CANVAS_PROVIDER_TYPE=duckdb.
Esquema de entrada
{
"type": "object",
"properties": {
"name": {
"description": "Optional dataframe name to describe a single dataframe — a df_XXXXX_XXXXX name from bls_get_series, or a register_as name from bls_dataframe_query. Omit, or leave blank, to list all active dataframes.",
"type": "string"
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"dataframes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Canvas table name — df_XXXXX_XXXXX, or the register_as name given."
},
"source_tool": {
"type": "string",
"description": "Tool that produced this dataframe."
},
"query_params": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {},
"description": "Input parameters the source tool was called with."
},
"created_at": {
"type": "string",
"description": "ISO 8601 creation timestamp."
},
"expires_at": {
"type": "string",
"description": "ISO 8601 expiry timestamp (sliding TTL)."
},
"row_count": {
"type": "number",
"description": "Rows materialized in the dataframe."
},
"column_schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Column name."
},
"type": {
"type": "string",
"description": "Column type as the canvas reports it: VARCHAR, INTEGER, BIGINT, DOUBLE, BOOLEAN, DATE, TIMESTAMP, JSON, or BLOB. A register_as column of another DuckDB type (DECIMAL, HUGEINT from SUM over integers, SMALLINT, a list) reports VARCHAR, though SQL still sees its real type."
},
"nullable": {
"type": "boolean",
"description": "Whether the column permits NULL."
}
},
"required": [
"name",
"type",
"nullable"
],
"additionalProperties": false,
"description": "Schema descriptor for one column in the dataframe."
},
"description": "Column schema — all BLS dataframe columns are nullable."
}
},
"required": [
"name",
"source_tool",
"query_params",
"created_at",
"expires_at",
"row_count",
"column_schema"
],
"additionalProperties": false,
"description": "Metadata for one canvas dataframe."
},
"description": "Active dataframes for this tenant, newest first."
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: The DataCanvas service is not configured for this deployment. `invalid_dataframe_name`: name is not blank and is not a canvas identifier, so no dataframe can carry it. Other values are possible when a failure originates below the handler.",
"examples": [
"canvas_unavailable",
"invalid_dataframe_name"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"dataframes"
]
},
{
"required": [
"error"
]
}
]
}🟢bls_dataframe_query(sql, register_as, preview, row_limit)
Run a single-statement SELECT against the canvas dataframes registered by bls_get_series or by an earlier register_as. Read-only: writes, DDL, DROP, COPY, PRAGMA, ATTACH, and external-file table functions are rejected. System catalogs (information_schema, pg_catalog, sqlite_master, duckdb_*) are denied at the bridge layer — use bls_dataframe_describe to list available dataframes. Supports JOINs, aggregates, window functions, and CTEs. Optional register_as persists the result as a new dataframe with a fresh TTL for chained analysis. Canvas SQL operations consume zero BLS API quota. Requires CANVAS_PROVIDER_TYPE=duckdb.
Esquema de entrada
{
"type": "object",
"properties": {
"sql": {
"type": "string",
"minLength": 1,
"description": "Single-statement SELECT against df_<id> tables on the shared canvas. Reference dataframes by the names returned in bls_get_series responses or listed by bls_dataframe_describe. Standard DuckDB SQL — joins, aggregates, window functions, CTEs all supported. Example: SELECT series_id, year, period, value FROM df_AAAAA_BBBBB WHERE year >= '2020' ORDER BY year DESC."
},
"register_as": {
"description": "When set, persist the query result as a new dataframe under this name. Fresh TTL — not inherited from parent tables. Use to chain analyses without re-running source SQL or consuming additional BLS quota.",
"type": "string"
},
"preview": {
"description": "Inline row preview count. Defaults to row_limit. Set lower (e.g. 50) when chaining via register_as and only a sample is needed immediately.",
"type": "integer",
"minimum": 0,
"maximum": 10000
},
"row_limit": {
"default": 1000,
"description": "Hard cap on rows materialized in the response (default 1000, max 10000). Full results live on-canvas under register_as when provided.",
"type": "integer",
"minimum": 1,
"maximum": 10000
}
},
"required": [
"sql"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}Esquema de salida
{
"type": "object",
"properties": {
"columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Column names in projection order."
},
"row_count": {
"type": "number",
"description": "Rows materialized by the query. Exact when register_as is used; otherwise equals row_limit when truncated is true."
},
"rows": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Materialized rows, bounded by preview / row_limit."
},
"registered_as": {
"description": "Set when register_as was supplied and the result was materialized.",
"type": "string"
},
"expires_at": {
"description": "ISO 8601 expiry for the newly registered dataframe, when applicable.",
"type": "string"
},
"truncated": {
"description": "True when the returned rows were capped.",
"type": "boolean"
},
"shown": {
"description": "Number of rows returned inline.",
"type": "number"
},
"cap": {
"description": "The preview or row_limit cap that was applied.",
"type": "number"
},
"notice": {
"description": "Guidance when results were capped by preview or row_limit — names which parameter was the binding limiter and suggests how to retrieve the rest. Absent when all rows fit in the response.",
"type": "string"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `canvas_unavailable`: The DataCanvas service is not configured for this deployment. Other values are possible when a failure originates below the handler.",
"examples": [
"canvas_unavailable"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"columns",
"row_count",
"rows"
]
},
{
"required": [
"error"
]
}
]
}Comunidad
Evidencia