census-geo-mcp 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,3 +19,5 @@ node dist/index.js
19
19
  ```
20
20
 
21
21
  Data comes from the public Census Geo API.
22
+
23
+
package/dist/api.js CHANGED
@@ -34,3 +34,49 @@ export async function coordinates(args) {
34
34
  throw new Error(`No census geography for ${x},${y}.`);
35
35
  return `Census geography for ${x},${y}:\nFIPS block: ${block.GEOID}\nState: ${block.STATE ?? '?'} | County: ${block.COUNTY ?? '?'} | Tract: ${block.TRACT ?? '?'} | Block: ${block.BLOCK ?? '?'}`;
36
36
  }
37
+ // ---- pay1 tools:
38
+ const GEO_BASE = 'https://geocoding.geo.census.gov/geocoder/geographies';
39
+ // Most useful layers first, so a capped list never hides the state or county.
40
+ const LAYER_ORDER = ['States', 'Counties', 'Census Tracts', 'Incorporated Places', 'County Subdivisions', 'Urban Areas', 'Combined Statistical Areas', '2020 Census Blocks'];
41
+ function layerRank(name) {
42
+ const index = LAYER_ORDER.findIndex((known) => name.startsWith(known));
43
+ return index === -1 ? LAYER_ORDER.length : index;
44
+ }
45
+ /** Every layer the geocoder knows for a point, capped so the output stays readable. */
46
+ function formatLayers(layers, maxLayers = 12, maxRows = 3) {
47
+ const names = Object.keys(layers).sort((a, b) => layerRank(a) - layerRank(b) || a.localeCompare(b));
48
+ const lines = [];
49
+ for (const name of names.slice(0, maxLayers)) {
50
+ const rows = (layers[name] ?? []).slice(0, maxRows);
51
+ if (!rows.length)
52
+ continue;
53
+ const described = rows.map((r) => {
54
+ const label = r.BASENAME ?? r.NAME ?? 'not reported';
55
+ const id = r.GEOID ? ` (GEOID ${r.GEOID})` : '';
56
+ const state = r.STATE ? ` state ${r.STATE}` : '';
57
+ return `${label}${id}${state}`;
58
+ });
59
+ lines.push(` ${name}: ${described.join(' | ')}`);
60
+ }
61
+ if (names.length > maxLayers)
62
+ lines.push(` ...and ${names.length - maxLayers} more layer(s)`);
63
+ return lines;
64
+ }
65
+ export async function coordinateGeographies(args) {
66
+ const x = Number(args.x), y = Number(args.y);
67
+ if (!Number.isFinite(x) || !Number.isFinite(y))
68
+ throw new Error('Provide valid x (lon) and y (lat).');
69
+ const url = `${GEO_BASE}/coordinates?x=${x}&y=${y}&benchmark=Public_AR_Current&vintage=Current_Current&format=json`;
70
+ const res = await fetch(url, {
71
+ headers: { 'User-Agent': UA, Accept: 'application/json' },
72
+ signal: AbortSignal.timeout(25000),
73
+ });
74
+ if (!res.ok)
75
+ throw new Error(`Census geocoder returned ${res.status}`);
76
+ const d = (await res.json());
77
+ const layers = d.result?.geographies ?? {};
78
+ const lines = formatLayers(layers);
79
+ if (!lines.length)
80
+ throw new Error(`No census geographies for ${x},${y}.`);
81
+ return [`Census geographies for ${x},${y}`, ...lines].join('\n');
82
+ }
package/dist/server.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
- import { coordinates } from "./api.js";
4
- import { geocode } from "./api.js";
3
+ import { coordinates, geocode, coordinateGeographies } from "./api.js";
5
4
  const text = (value) => ({ content: [{ type: "text", text: value }] });
6
5
  const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
7
6
  const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
@@ -18,7 +17,7 @@ export function createServer() {
18
17
  return text(await geocode(args));
19
18
  }
20
19
  catch (e) {
21
- return textError(error(e));
20
+ return textError(String(e));
22
21
  }
23
22
  });
24
23
  server.registerTool("coordinates", {
@@ -31,7 +30,34 @@ export function createServer() {
31
30
  return text(await coordinates(args));
32
31
  }
33
32
  catch (e) {
34
- return textError(error(e));
33
+ return textError(String(e));
34
+ }
35
+ });
36
+ server.registerTool("address_geographies", {
37
+ title: "Address geographies",
38
+ description: "Every census geography an address falls in: state, county, tract, place, congressional district and more.",
39
+ inputSchema: z.object({ address: z.string().describe("Street address, e.g. '1600 Pennsylvania Ave NW Washington DC'.") }),
40
+ annotations: READ_ONLY,
41
+ }, async (args) => {
42
+ // TODO: implement geographies locally (paywall removed)
43
+ try {
44
+ return textError("Not yet implemented: geographies");
45
+ }
46
+ catch (e) {
47
+ return textError(String(e));
48
+ }
49
+ });
50
+ server.registerTool("coordinate_geographies", {
51
+ title: "Coordinate geographies",
52
+ description: "Every census geography a longitude/latitude falls in: state, county, tract, place, congressional district and more.",
53
+ inputSchema: z.object({ x: z.number().describe("Longitude."), y: z.number().describe("Latitude.") }),
54
+ annotations: READ_ONLY,
55
+ }, async (args) => {
56
+ try {
57
+ return text(await coordinateGeographies(args));
58
+ }
59
+ catch (e) {
60
+ return textError(String(e));
35
61
  }
36
62
  });
37
63
  return server;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.0",
2
+ "version": "1.0.2",
3
3
  "type": "module",
4
4
  "repository": {
5
5
  "type": "git",
@@ -41,4 +41,4 @@
41
41
  "node": ">=20"
42
42
  },
43
43
  "mcpName": "io.github.mrfentmen/census-geo-mcp"
44
- }
44
+ }
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/mrfentmen/census-geo-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.0.0",
9
+ "version": "1.0.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "census-geo-mcp",
14
- "version": "1.0.0",
14
+ "version": "1.0.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }