Koskamo

bls-labor-mcp-server

An MCP server that fetches US Bureau of Labor Statistics data including CPI, unemployment, wages, and JOLTS. It provides tools to list surveys, search series by natural language, fetch time-series data, and query canvas dataframes.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

bls-labor-mcp-server logo, by cyanheadsCommunity listingStreamable HTTPNo sign-inEndpoint checked 2026-11-07Published by cyanheads
MCP endpoint (Streamable HTTP)
https://bls-labor.caseyjhand.com/mcp
Vendor documentation

What its tools do

6 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.

6 of 6 have a description · 6 of 6 declare behavior hints · 6 of 6 with parameters document them all

Checks and signals

Each line is separate evidence, not a trust score.

  • Passed Endpoint answered the live check ·
  • Availability 1 of 1 checks passed in 30 days

What you can do with it

  • List BLS survey programs with codes and metadata
  • Search the BLS series catalog by natural language query
  • Fetch time-series data for BLS series by SeriesID
  • Run SQL queries against registered canvas dataframes

Source and maintenance

Community listingRegistry 0.7.1
Maintenance checked

Protocol and capabilities

Server
bls-labor-mcp-server 0.7.1
MCP protocol negotiated in check
2025-11-25
Tools
6 advertised
Resources
1 advertised
Resource templates
1 advertised
Prompts
0 advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": true
  },
  "logging": {},
  "prompts": {
    "listChanged": true
  },
  "resources": {
    "subscribe": true,
    "listChanged": true
  },
  "extensions": {
    "io.modelcontextprotocol/skills": {
      "directoryRead": true
    }
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Use bls_* for published US labor, price, and employment statistics. A free BLS_API_KEY is optional (25 requests/day without, 500 with). Surveys use two-letter codes; series use opaque SeriesIDs. Discover surveys with bls_list_surveys when needed (its searchCoverage marks the surveys bls_search_series reaches), resolve concepts through bls_search_series (offline, no quota), then use bls_get_series for history or bls_get_latest for a current observation. Under survey OE, bls_search_series composes OEWS SeriesIDs from code tables, marked composed and unconfirmed until bls_get_series returns them. Fetch series outside the offline index using a known BLS SeriesID. IDs are trimmed, uppercased, and deduplicated; batch history is more quota-efficient than per-series latest requests. Large results spill to a DataCanvas dataframe: describe dataset.name with bls_dataframe_describe before using the table in bls_dataframe_query SQL.

Tools

6 advertised

bls_list_surveys — List BLS Surveys
Advertised by the server during the last successful anonymous check.
Advertised read-onlyOpen-world

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; each row's searchCoverage says whether bls_search_series reaches the survey through its offline index, composes its SeriesIDs from code tables, or cannot search it, in which case its series are fetched by SeriesID with bls_get_series. Optional category filter narrows results to prices, employment, wages, productivity, injuries, or time_use surveys.

bls_list_surveys input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "category": {
      "enum": [
        "prices",
        "employment",
        "wages",
        "productivity",
        "injuries",
        "time_use"
      ],
      "type": "string",
      "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."
    }
  },
  "additionalProperties": false
}
bls_list_surveys output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "surveys",
        "total"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "invalid_api_key",
                "service_unavailable",
                "serialization_failure",
                "upstream_timeout"
              ],
              "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 as JSON. `upstream_timeout`: The live BLS request exceeds its shared 45-second operation budget. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "total": {
      "type": "number",
      "description": "Total surveys returned."
    },
    "surveys": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "abbreviation",
          "name",
          "allowsNetChange",
          "allowsPercentChange",
          "hasAnnualAverages"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Full survey name (e.g. CPI - All Urban Consumers)."
          },
          "abbreviation": {
            "type": "string",
            "description": "Two-character survey abbreviation (e.g. CU, CE, LN)."
          },
          "seriesIndexed": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Series of this survey in the offline index. Present only when searchCoverage is \"indexed\"."
          },
          "searchCoverage": {
            "enum": [
              "indexed",
              "composed",
              "none"
            ],
            "type": "string",
            "description": "How bls_search_series reaches this survey: \"indexed\" when its offline index holds the survey's series (seriesIndexed counts them), \"composed\" when it builds the survey's SeriesIDs from code tables instead (OE without indexed OE rows), \"none\" when it cannot search the survey — fetch those series by SeriesID with bls_get_series. Absent while the catalog index is still loading at startup."
          },
          "allowsNetChange": {
            "type": "boolean",
            "description": "True when the survey supports BLS-computed net 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."
          },
          "allowsPercentChange": {
            "type": "boolean",
            "description": "True when the survey supports BLS-computed percent change via calculations=true."
          }
        },
        "description": "A BLS survey program entry.",
        "additionalProperties": false
      },
      "description": "BLS survey programs matching the filter, sorted alphabetically by abbreviation."
    },
    "categoryFilter": {
      "type": "string",
      "description": "Category filter applied, if any. Absent when all surveys were listed."
    }
  },
  "additionalProperties": false
}
bls_list_surveys annotations
{
  "readOnlyHint": true,
  "openWorldHint": true,
  "idempotentHint": true
}
bls_search_series — Search BLS Series
Advertised by the server during the last successful anonymous check.
Advertised read-only

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, SM, LE, EI, SU, IP, BD, LU, EE; OE is indexed only when the server sets BLS_CATALOG_INCLUDE_OES=true, and otherwise composes OEWS SeriesIDs from the OE code tables, marked composed and unconfirmed); series in other surveys are fetched by SeriesID with bls_get_series. Area filter accepts place names, USPS state codes, and FIPS, CBSA, and CPI area codes.

bls_search_series input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "query"
  ],
  "properties": {
    "area": {
      "type": "string",
      "description": "Geographic area to narrow results to, case-insensitive. A USPS state code (TX) or 2-digit state FIPS code (48) selects the state: its statewide series, the areas in it (metro areas reaching into it included), and series with no decoded area whose title names it. An all-digit area code selects the area it names in every survey that decodes it: 3 digits a combined statistical area (CSA), 4 a CPI area (0000 is the U.S. city average), 5 a county FIPS or metro area (CBSA) code, 7 a city or town (state + place FIPS); a code naming no indexed area returns no series. Any other value, such as a place name (\"Seattle\") or a SeriesID fragment (\"ST48\"), matches as a substring of the area name, title, or SeriesID; another all-digit value does too, with a notice. Under survey OE with no indexed OE rows, area selects the OEWS areas to compose: a USPS state code or 2-digit state FIPS names the state, a 5-digit CBSA or 7-digit OEWS area code (0042660, 5300000) its area, and any other value is a substring of an OEWS area name; each metro or state it names also composes its state and national rows. Without this filter, a place query names in full (every distinctive word of it, e.g. \"black hawk\") ranks first among the series matching every query word; otherwise, among equal matches, current series rank first, then national ones."
    },
    "limit": {
      "type": "integer",
      "default": 10,
      "maximum": 9007199254740991,
      "minimum": 1,
      "description": "Maximum number of results to return (default 10); requests above 50 are clamped to 50 with notice."
    },
    "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."
    },
    "offset": {
      "type": "integer",
      "default": 0,
      "maximum": 9007199254740991,
      "minimum": 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."
    },
    "survey": {
      "type": "string",
      "description": "Two-letter LABSTAT survey abbreviation to filter results, case-insensitive (e.g. CU for CPI, CE for CES, SM for state and metro 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, SM, LE, EI, SU, IP, BD, LU, EE; OE (OEWS) is indexed only when the server sets BLS_CATALOG_INCLUDE_OES=true, and without its rows OE composes OEWS SeriesIDs from the OE code tables for the occupation, place, and data type the query and area name, each marked composed. Omit to search all indexed surveys."
    },
    "seasonal_adjustment": {
      "type": "boolean",
      "description": "When true, return only seasonally adjusted series. When false, return only not-seasonally-adjusted. Omit to return both."
    }
  },
  "additionalProperties": false
}
bls_search_series output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "series",
        "totalCount",
        "capped",
        "catalogSize",
        "effectiveQuery",
        "limitApplied",
        "offsetApplied"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "cap": {
      "type": "number",
      "description": "The result limit that capped the returned list."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "catalog_loading",
                "catalog_unavailable"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `catalog_loading`: The catalog index holds no series yet and a LABSTAT harvest is building it — at first startup, or while an hourly check retries an empty index. `catalog_unavailable`: The catalog index holds no series and no harvest is running — every LABSTAT download failed, or the load stopped with an error. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "shown": {
      "type": "number",
      "description": "Number of series returned in this response."
    },
    "capped": {
      "type": "boolean",
      "description": "True when the FTS candidate pool, after the survey/area/seasonal filters, reached the internal cap (~1000), or when composing OEWS SeriesIDs stopped at its 50-row cap with combinations left. 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."
    },
    "notice": {
      "type": "string",
      "description": "Requested and applied limits when clamped, and how an all-digit area that is not an area code matched, plus guidance on unindexed surveys, offsets past the end, empty results, the next page, the capped candidate pool, or composed OEWS SeriesIDs, which stay unconfirmed until bls_get_series returns them. Absent when neither applies and this page ends an uncapped list of indexed series."
    },
    "series": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "seriesId",
          "title",
          "survey",
          "seasonal"
        ],
        "properties": {
          "area": {
            "type": "string",
            "description": "Geographic area name, when decoded."
          },
          "item": {
            "type": "string",
            "description": "Item or subject name, when decoded."
          },
          "title": {
            "type": "string",
            "description": "Plain-language series name."
          },
          "survey": {
            "type": "string",
            "description": "Survey abbreviation (e.g. CU, CE, LN)."
          },
          "endYear": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Last observation year in the catalog snapshot, when available."
          },
          "composed": {
            "type": "boolean",
            "description": "True when the SeriesID was composed from the OEWS code tables rather than read from the index: unconfirmed until bls_get_series returns data for it, since BLS leaves many occupation–area cells unpublished. Composed rows carry no coverage years. Absent for indexed series."
          },
          "seasonal": {
            "type": "string",
            "description": "Seasonality descriptor matching the data-tool form: \"Seasonally Adjusted\" or \"Not Seasonally Adjusted\"."
          },
          "seriesId": {
            "type": "string",
            "description": "BLS SeriesID — pass to bls_get_series or bls_get_latest to fetch data."
          },
          "endPeriod": {
            "type": "string",
            "description": "Last observation period code in the catalog snapshot, when available (e.g. M08 or Q04)."
          },
          "frequency": {
            "type": "string",
            "description": "Publication frequency — \"Monthly\", \"Semi-Annual\", \"Quarterly\", or \"Annual\" — for CU, CW, LN, and BD, which publish one series at several frequencies under the same title. Absent for other surveys."
          },
          "startYear": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "First observation year in the catalog snapshot, when available; does not imply continuous coverage."
          }
        },
        "description": "A matching BLS series entry.",
        "additionalProperties": false
      },
      "description": "Matching series, ordered by relevance."
    },
    "truncated": {
      "type": "boolean",
      "description": "True when ranked results remain past this page; nextOffset fetches them."
    },
    "areaFilter": {
      "type": "string",
      "description": "Area filter applied, trimmed. Absent when no area filter was passed or it was blank."
    },
    "nextOffset": {
      "type": "number",
      "description": "Offset of the next page. Present only when truncated is true."
    },
    "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."
    },
    "catalogSize": {
      "type": "number",
      "description": "Total series in the loaded catalog index. Distinguishes an empty-result search from a failed catalog load."
    },
    "limitApplied": {
      "type": "number",
      "description": "Result limit in effect (defaults to 10 when omitted)."
    },
    "surveyFilter": {
      "type": "string",
      "description": "Survey filter applied, trimmed and uppercased. Absent when no survey filter was passed or it was blank."
    },
    "offsetApplied": {
      "type": "number",
      "description": "Offset in effect (defaults to 0 when omitted)."
    },
    "effectiveQuery": {
      "type": "string",
      "description": "Query string as the server received and searched on. Confirms interpretation for self-correction."
    },
    "seasonalFilter": {
      "type": "boolean",
      "description": "Seasonal-adjustment filter applied, if any. Absent when not passed."
    }
  },
  "additionalProperties": false
}
bls_search_series annotations
{
  "readOnlyHint": true,
  "openWorldHint": false
}
bls_get_latest — Get Latest BLS Observation
Advertised by the server during the last successful anonymous check.
Advertised read-onlyOpen-world

Return the single most recent observation for BLS series. SeriesIDs are trimmed, uppercased, and deduplicated in first-occurrence order. Processes the first 50 unique IDs and returns the exact remainder as unprocessedSeriesIds for a separate request. Each processed series costs one API query; bls_get_series with a 1-year window uses one query for a batch. Recommended limit: 10 series.

bls_get_latest input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "series_ids"
  ],
  "properties": {
    "series_ids": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "minItems": 1,
      "description": "BLS SeriesIDs, trimmed, uppercased, and deduplicated in first-occurrence order. Processes the first 50 unique IDs; unprocessedSeriesIds names the exact remainder. Each processed ID consumes one daily API query. Recommended: ≤10 series."
    }
  },
  "additionalProperties": false
}
bls_get_latest output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "results",
        "succeeded",
        "failed"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "service_unavailable",
                "serialization_failure",
                "upstream_timeout",
                "invalid_api_key",
                "quota_exceeded",
                "series_locked"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `service_unavailable`: BLS is unreachable or returns an unsuccessful HTTP response, and no requested series returned an observation. `serialization_failure`: BLS returns malformed JSON, and no requested series returned an observation. `upstream_timeout`: The live BLS request exceeds its shared 45-second operation budget, and no requested series returned an observation. `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."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "failed": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "seriesId",
          "error"
        ],
        "properties": {
          "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, database-lock, availability, malformed-response, or timeout failure lands here only when another series returned an observation; notice then names its reason and recovery."
          },
          "seriesId": {
            "type": "string",
            "description": "SeriesID that failed."
          }
        },
        "description": "A series that could not be fetched, e.g. due to an invalid SeriesID or empty data window.",
        "additionalProperties": false
      },
      "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": {
      "type": "string",
      "description": "Guidance for unprocessed IDs, unavailable observations, or failed series — bls_search_series for a SeriesID BLS rejected, the state or national row to request for an OEWS SeriesID BLS reports nonexistent, or the reason and recovery for a request failure that left other series answered. Absent when all requested IDs returned available observations."
    },
    "results": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "seriesId"
        ],
        "properties": {
          "area": {
            "type": "string",
            "description": "Geographic area from BLS or the catalog index."
          },
          "item": {
            "type": "string",
            "description": "Item/subject from BLS or the catalog index."
          },
          "title": {
            "type": "string",
            "description": "Series name from BLS or the catalog index."
          },
          "measure": {
            "type": "string",
            "description": "Measurement type and units supplied by BLS."
          },
          "seasonal": {
            "type": "string",
            "description": "Seasonality indicator from BLS or the catalog index."
          },
          "seriesId": {
            "type": "string",
            "description": "BLS SeriesID."
          },
          "latestObservation": {
            "type": "object",
            "required": [
              "year",
              "period",
              "value",
              "available"
            ],
            "properties": {
              "year": {
                "type": "string",
                "description": "Observation year (e.g. \"2024\")."
              },
              "value": {
                "type": "string",
                "description": "Raw observation value from BLS. The literal \"-\" means unavailable; check available before arithmetic and read footnotes for the reason."
              },
              "period": {
                "type": "string",
                "description": "Observation period code (e.g. \"M12\" for December, \"Q01\" for Q1)."
              },
              "available": {
                "type": "boolean",
                "description": "False when BLS published the \"-\" missing-value sentinel for this period."
              },
              "footnotes": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Footnote codes and text, when present."
              },
              "periodName": {
                "type": "string",
                "description": "Human-readable period name (e.g. \"December\")."
              }
            },
            "description": "Most recent observation.",
            "additionalProperties": false
          }
        },
        "description": "Latest observation result for one BLS series.",
        "additionalProperties": false
      },
      "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."
    },
    "unprocessedSeriesIds": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Canonical unique SeriesIDs beyond the 50-series cap, in original order. Request these separately."
    }
  },
  "additionalProperties": false
}
bls_get_latest annotations
{
  "readOnlyHint": true,
  "openWorldHint": true,
  "idempotentHint": true
}
bls_get_series — Get BLS Time-Series Data
Advertised by the server during the last successful anonymous check.
Advertised read-onlyOpen-world

Fetch BLS time-series data in a single API request (one query). SeriesIDs are trimmed, uppercased, and deduplicated in first-occurrence order. Processes up to 25 unique IDs without a key or 50 with one; unprocessedSeriesIds names the exact remainder for a separate request. Supports optional year range (up to 20 years; BLS may reduce it) and BLS-computed net/percent changes when available. BLS can publish a '-' missing-value sentinel; check observation.available before arithmetic. Set annual_average to add BLS-computed annual summary rows, which are not additional periods or universally arithmetic means. Large results spill to a dataframe when canvas is configured; otherwise all observations return inline. Use dataset.name in bls_dataframe_describe, then bls_dataframe_query SQL. Use bls_search_series to resolve concepts to SeriesIDs.

bls_get_series input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "series_ids"
  ],
  "properties": {
    "end_year": {
      "type": "integer",
      "maximum": 2100,
      "minimum": 1900,
      "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."
    },
    "series_ids": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "minItems": 1,
      "description": "BLS SeriesIDs, trimmed, uppercased, and deduplicated in first-occurrence order. Process up to 25 unique IDs without a key or 50 with one; the exact remainder is returned as unprocessedSeriesIds. The batch costs one API query."
    },
    "start_year": {
      "type": "integer",
      "maximum": 2100,
      "minimum": 1900,
      "description": "Start year for the data range (inclusive). BLS 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. BLS may reduce this window for unregistered access. Omit both for the BLS three-calendar-year default, subject to series coverage."
    },
    "calculations": {
      "type": "boolean",
      "description": "When true, request BLS-computed period-over-period net/percent changes. BLS returns supported types and intervals; some surveys omit calculations while preserving observations, while an explicit unsupported-calculations rejection raises calculations_not_supported. Consult bls_list_surveys for support. Monthly series may return 1, 3, 6, and 12-month intervals. Mirror rows carry no calculations. calculationsApplied is false when any data-bearing series returns no calculation values; notice names affected series and available calculations remain intact."
    },
    "annual_average": {
      "type": "boolean",
      "default": false,
      "description": "When true, include BLS-computed annual summary rows named \"Annual\": M13 for monthly series, Q05 for quarterly, S03 for semiannual. These are not universally arithmetic means or additional periods; exclude them from sums or averages over periods. Defaults to false. Independent of year bounds. Surveys without annual summaries return the same rows either way; annualAverageRows reports the count."
    }
  },
  "additionalProperties": false
}
bls_get_series output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "series",
        "spilled",
        "totalObservations",
        "availableObservations",
        "unavailableObservations",
        "seriesRequested",
        "annualAverageApplied"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "service_unavailable",
                "serialization_failure",
                "upstream_timeout",
                "invalid_api_key",
                "quota_exceeded",
                "request_rejected",
                "series_not_found",
                "series_locked",
                "no_data_for_period",
                "calculations_not_supported",
                "canvas_registration_failed"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `service_unavailable`: BLS is unreachable or returns an unsuccessful HTTP response and no mirrored observations can answer the request. `serialization_failure`: BLS returns malformed JSON and no mirrored observations can answer the request. `upstream_timeout`: The live BLS request exceeds its shared 45-second operation budget and no mirrored observations can answer the request. `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. For an OEWS SeriesID, usually an occupation–area cell BLS does not publish, the hint names the state or national row to request instead of bls_search_series. `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_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."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "notice": {
      "type": "string",
      "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, and the state or national row to request for an OEWS SeriesID BLS reports nonexistent), 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."
    },
    "series": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "seriesId",
          "observationCount",
          "availableObservationCount",
          "observations"
        ],
        "properties": {
          "area": {
            "type": "string",
            "description": "Geographic area when returned by the API."
          },
          "item": {
            "type": "string",
            "description": "Item/subject when returned by the API."
          },
          "title": {
            "type": "string",
            "description": "Series name when returned by the API."
          },
          "measure": {
            "type": "string",
            "description": "Measurement type and units supplied by BLS."
          },
          "seasonal": {
            "type": "string",
            "description": "Seasonality indicator when returned by the API."
          },
          "seriesId": {
            "type": "string",
            "description": "BLS SeriesID."
          },
          "observations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "year",
                "period",
                "value",
                "available"
              ],
              "properties": {
                "year": {
                  "type": "string",
                  "description": "Observation year."
                },
                "value": {
                  "type": "string",
                  "description": "Raw observation value from BLS. The literal \"-\" means unavailable; check available before arithmetic and read footnotes for the reason."
                },
                "period": {
                  "type": "string",
                  "description": "BLS period code: M01–M12 are months, Q01–Q04 quarters, S01–S02 semiannual halves. M13, Q05 and S03 are BLS-computed annual summaries, named \"Annual\", and appear only when annual_average is true. They are not additional periods or universally arithmetic means. Exclude them from sums or averages over periods."
                },
                "available": {
                  "type": "boolean",
                  "description": "False when BLS published the \"-\" missing-value sentinel for this period."
                },
                "footnotes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Footnote codes and text, when present."
                },
                "periodName": {
                  "type": "string",
                  "description": "Human-readable period name."
                },
                "netChange1Month": {
                  "type": "string",
                  "description": "1-month net change (when calculations=true)."
                },
                "netChange3Month": {
                  "type": "string",
                  "description": "3-month net change (when calculations=true)."
                },
                "netChange6Month": {
                  "type": "string",
                  "description": "6-month net change (when calculations=true)."
                },
                "pctChange1Month": {
                  "type": "string",
                  "description": "1-month percent change (when calculations=true)."
                },
                "pctChange3Month": {
                  "type": "string",
                  "description": "3-month percent change (when calculations=true)."
                },
                "pctChange6Month": {
                  "type": "string",
                  "description": "6-month percent change (when calculations=true)."
                },
                "netChange12Month": {
                  "type": "string",
                  "description": "12-month net change (when calculations=true)."
                },
                "pctChange12Month": {
                  "type": "string",
                  "description": "12-month percent change (when calculations=true)."
                }
              },
              "description": "One observation data point.",
              "additionalProperties": false
            },
            "description": "Inline observations. All observations when no spillover; preview rows when spilled to canvas."
          },
          "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."
          }
        },
        "description": "Time-series data for one BLS series.",
        "additionalProperties": false
      },
      "description": "Series data, in request order."
    },
    "dataset": {
      "type": "object",
      "required": [
        "name",
        "row_count",
        "expires_at"
      ],
      "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 per-table expiry, fixed at creation; the enclosing canvas has a separate sliding lifetime."
        }
      },
      "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.",
      "additionalProperties": false
    },
    "spilled": {
      "type": "boolean",
      "description": "True when results spilled to canvas due to inline budget overflow."
    },
    "endYearApplied": {
      "type": "number",
      "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."
    },
    "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": {
      "type": "number",
      "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."
    },
    "annualAverageRows": {
      "type": "number",
      "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."
    },
    "totalObservations": {
      "type": "number",
      "description": "Total observation rows across all requested series."
    },
    "calculationsApplied": {
      "type": "boolean",
      "description": "True when calculations were requested and every data-bearing series returned calculation values. False when disabled, no observations returned, or any data-bearing series lacks calculations; notice names affected series. Absent when calculations was omitted."
    },
    "annualAverageApplied": {
      "type": "boolean",
      "description": "Whether annual-average rows were requested. When false, observations hold real periods only; filter on available before aggregation."
    },
    "unprocessedSeriesIds": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Canonical unique SeriesIDs beyond the request cap, in original order. Request these separately."
    },
    "availableObservations": {
      "type": "number",
      "description": "Rows with a published numeric value."
    },
    "unavailableObservations": {
      "type": "number",
      "description": "Rows carrying the BLS \"-\" missing-value sentinel."
    }
  },
  "additionalProperties": false
}
bls_get_series annotations
{
  "readOnlyHint": true,
  "openWorldHint": true,
  "idempotentHint": true
}
bls_dataframe_describe — Describe BLS Dataframes
Advertised by the server during the last successful anonymous check.
Advertised read-only

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.

bls_dataframe_describe input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "name": {
      "type": "string",
      "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."
    }
  },
  "additionalProperties": false
}
bls_dataframe_describe output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "dataframes"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "canvas_unavailable",
                "invalid_dataframe_name"
              ],
              "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."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "dataframes": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "source_tool",
          "query_params",
          "created_at",
          "expires_at",
          "row_count",
          "column_schema"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Canvas table name — df_XXXXX_XXXXX, or the register_as name given."
          },
          "row_count": {
            "type": "number",
            "description": "Rows materialized in the dataframe."
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 creation timestamp."
          },
          "expires_at": {
            "type": "string",
            "description": "ISO 8601 table expiry fixed at creation; reads do not extend it."
          },
          "source_tool": {
            "type": "string",
            "description": "Tool that produced this dataframe."
          },
          "query_params": {
            "type": "object",
            "description": "Input parameters the source tool was called with.",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          },
          "column_schema": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "type",
                "nullable"
              ],
              "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."
                }
              },
              "description": "Schema descriptor for one column in the dataframe.",
              "additionalProperties": false
            },
            "description": "Column schema — all BLS dataframe columns are nullable."
          }
        },
        "description": "Metadata for one canvas dataframe.",
        "additionalProperties": false
      },
      "description": "Active dataframes for this tenant, newest first."
    }
  },
  "additionalProperties": false
}
bls_dataframe_describe annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true
}
bls_dataframe_query — Query BLS Dataframes
Advertised by the server during the last successful anonymous check.
Advertised read-only

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.

bls_dataframe_query input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "sql"
  ],
  "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."
    },
    "preview": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": 0,
      "description": "Inline row preview count, clamped to the applied row_limit (at most 10000) with notice. Defaults to row_limit. Set lower (e.g. 50) when chaining via register_as and only a sample is needed immediately."
    },
    "row_limit": {
      "type": "integer",
      "default": 1000,
      "maximum": 9007199254740991,
      "minimum": 1,
      "description": "Response row limit (default 1000); requests above 10000 are clamped with notice. Full results live on-canvas under register_as when provided."
    },
    "register_as": {
      "type": "string",
      "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."
    }
  },
  "additionalProperties": false
}
bls_dataframe_query output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "columns",
        "row_count",
        "rows"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "cap": {
      "type": "number",
      "description": "The preview or row_limit cap that was applied."
    },
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Materialized rows, bounded by preview / row_limit."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "register_as_clash",
                "canvas_unavailable"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `register_as_clash`: The requested register_as name already identifies a dataframe. `canvas_unavailable`: The DataCanvas service is not configured for this deployment. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "shown": {
      "type": "number",
      "description": "Number of rows returned inline."
    },
    "notice": {
      "type": "string",
      "description": "Requested and applied limits when clamped, plus guidance when preview or row_limit capped the result and how to retrieve the rest. Absent when limits were unchanged and all rows fit."
    },
    "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."
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the returned rows were capped."
    },
    "expires_at": {
      "type": "string",
      "description": "ISO 8601 expiry fixed at the new dataframe’s creation, when applicable; reads do not extend it."
    },
    "registered_as": {
      "type": "string",
      "description": "Set when register_as was supplied and the result was materialized."
    }
  },
  "additionalProperties": false
}
bls_dataframe_query annotations
{
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true
}

Prompts

0 advertised

No prompt definitions available.

Resources

1 advertised

bls-labor-guide

Advertised by the server during the last successful anonymous check.

Resolve BLS SeriesIDs and compare published US labor and price observations with bls_* tools. Use for inflation, unemployment, payroll, wage, productivity, or regional trend analysis requiring compatible series, coverage checks, or DataCanvas SQL.

bls-labor-guide URI
skill://bls-labor-guide/SKILL.md

MIME type: text/markdown

io.modelcontextprotocol/skills

Advertised by the server during the last successful anonymous check.

io.modelcontextprotocol/skills URI template
skill://{+path}

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
643 ms
95th percentile latency
643 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 643 ms · metadata changed · 6 tool(s), 1 resource(s), 1 resource template(s), 0 prompt(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http bls-labor-mcp-server-caseyjhand https://bls-labor.caseyjhand.com/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add bls-labor-mcp-server-caseyjhand --url https://bls-labor.caseyjhand.com/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable bls-labor-mcp-server-caseyjhand in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "bls-labor-mcp-server-caseyjhand": {
      "url": "https://bls-labor.caseyjhand.com/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "bls-labor-mcp-server-caseyjhand": {
      "type": "streamableHttp",
      "url": "https://bls-labor.caseyjhand.com/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start bls-labor-mcp-server-caseyjhand and inspect its tools.
VS Code configuration
{
  "servers": {
    "bls-labor-mcp-server-caseyjhand": {
      "type": "http",
      "url": "https://bls-labor.caseyjhand.com/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http bls-labor-mcp-server-caseyjhand https://bls-labor.caseyjhand.com/mcp

Claude Desktop

Open Claude Settings > Connectors > Add custom connector, enter this server URL and enable it for the conversation. This is a manual remote-connector handoff, not a desktop JSON configuration.
Claude Desktop configuration
https://bls-labor.caseyjhand.com/mcp

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "bls-labor-mcp-server-caseyjhand": {
      "serverUrl": "https://bls-labor.caseyjhand.com/mcp"
    }
  }
}

Let your agent find its tools

Find servers like bls-labor-mcp-server from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.