Skip to content

Commit 77a2b06

Browse files
authored
Merge pull request #2 from codeforanchorage/incorporate-upstream-145-144
Adapt upstream enhancements: ACS 1-year hint (uscensusbureau#145) + token flattening (uscensusbureau#144)
2 parents f456fcc + 8e0edca commit 77a2b06

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

mcp-server/src/tools/fetch-aggregate-data.tool.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
validateGeographyArgs,
2121
} from '../schema/validators.js'
2222

23-
export const toolDescription = `Fetches Census Bureau statistics for a dataset, vintage, and geography; never guess cell codes -- use search-data-tables first or this tool will reject unknown codes with a "did you mean" hint. Every ACS estimate is auto-paired with its margin of error and flagged LOW RELIABILITY when CV exceeds 30%; suppression sentinels are decoded to text rather than returned as numbers. Required: dataset, year, get (variables or group), and one of for/ucgid. Returns numbered Record blocks with caveats led at the top.`
23+
export const toolDescription = `Fetches Census Bureau statistics for a dataset, vintage, and geography; never guess cell codes -- use search-data-tables first or this tool will reject unknown codes with a "did you mean" hint. Every ACS estimate is auto-paired with its margin of error and flagged LOW RELIABILITY when CV exceeds 30%; suppression sentinels are decoded to text rather than returned as numbers. ACS 1-year (acs/acs1) only covers areas with populations of 65,000+; use acs/acs5 for smaller geographies or the API returns a bare 400. Required: dataset, year, get (variables or group), and one of for/ucgid. Returns numbered Record blocks with caveats led at the top.`
2424

2525
export class FetchAggregateDataTool extends BaseTool<TableArgs> {
2626
name = 'fetch-aggregate-data'
@@ -218,6 +218,14 @@ function buildApiErrorMessage(
218218
`Recover by calling list-datasets to confirm the dataset exists and which vintages are published.`,
219219
)
220220
} else if (status === 400) {
221+
if (isAcs1YearDataset(ctx.dataset)) {
222+
lines.push(
223+
`ACS 1-year estimates (acs/acs1) are only published for geographic areas with ` +
224+
`populations of 65,000 or more, so a 400 here often means the requested area is too small. ` +
225+
`Switch the dataset to acs/acs5 (5-year estimates) to cover smaller geographies. ` +
226+
`See https://www.census.gov/programs-surveys/acs/guidance/estimates.html.`,
227+
)
228+
}
221229
lines.push(
222230
`The Census API rejected the query as malformed.`,
223231
`Re-verify the geography (for/in/ucgid) via fetch-dataset-geography and ` +
@@ -231,6 +239,13 @@ function buildApiErrorMessage(
231239
return lines.join(' ')
232240
}
233241

242+
// ACS 1-year estimates are only published for areas of 65,000+ people, a
243+
// frequent cause of opaque 400s. Detect acs1 regardless of spacing/case so the
244+
// error path can hand back the population-threshold hint above.
245+
function isAcs1YearDataset(dataset: string): boolean {
246+
return dataset.toLowerCase().replace(/\s+/g, '').includes('acs/acs1')
247+
}
248+
234249
function buildQueryEcho(opts: {
235250
dataset: string
236251
year: number | string

mcp-server/src/tools/fetch-dataset-geography.tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export class FetchDatasetGeographyTool extends BaseTool<FetchDatasetGeographyArg
218218
content: [
219219
{
220220
type: 'text',
221-
text: `Available geographies for ${args.dataset}${args.year ? ` (${args.year})` : ''}:\n\n${JSON.stringify(parsedGeographyData, null, 2)}`,
221+
text: `Available geographies for ${args.dataset}${args.year ? ` (${args.year})` : ''}:\n\n${JSON.stringify(parsedGeographyData)}`,
222222
},
223223
],
224224
}

mcp-server/tests/tools/fetch-aggregate-data/fetch-aggregate-data.tool.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,32 @@ describe('FetchAggregateDataTool', () => {
384384
expect(text).toContain('list-datasets')
385385
})
386386

387+
it('explains the ACS 1-year 65,000 population threshold on a 400 and suggests acs/acs5', async () => {
388+
mockFetch.mockImplementation((url: string) => {
389+
if (url.includes('/variables.json')) {
390+
return Promise.resolve(new Response('nope', { status: 404 }))
391+
}
392+
return Promise.resolve(
393+
new Response('bad request', {
394+
status: 400,
395+
statusText: 'Bad Request',
396+
}),
397+
)
398+
})
399+
400+
const args = {
401+
dataset: 'acs/acs1',
402+
year: 2022,
403+
get: { group: 'B01001' },
404+
for: 'place:00100',
405+
}
406+
407+
const response = await tool.toolHandler(args, process.env.CENSUS_API_KEY)
408+
const text = response.content[0].text as string
409+
expect(text).toContain('65,000')
410+
expect(text).toContain('acs/acs5')
411+
})
412+
387413
it('should handle network errors', async () => {
388414
// Variables fetch fails silently; data fetch rejects.
389415
mockFetch.mockImplementation((url: string) => {

0 commit comments

Comments
 (0)