gov-data-mcp 1.0.4 → 1.1.0

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/src/tools.js CHANGED
@@ -29,10 +29,29 @@ export const FEATURED = [
29
29
  'license-verifier',
30
30
  ];
31
31
 
32
+ /**
33
+ * Meta tools are hyphen-case for one reason: so that EVERY tool this server
34
+ * exposes follows a single rule, and for catalog tools the rule is stronger
35
+ * still — the tool name IS the Apify slug, which is also the value you pass to
36
+ * describe/run and the tail of the Store URL. The old snake_case meta names
37
+ * split the surface arbitrarily in two, which an independent review scored 2/5,
38
+ * and the alternative (snake_case everywhere) would have made 12 tool names
39
+ * differ from the slug they resolve to, trading one inconsistency for another.
40
+ *
41
+ * Renamed in v1.1.0. The pre-1.1 names are recognised in resolveCall and
42
+ * answered with an explicit rename notice rather than silently aliased — a
43
+ * caller with a hardcoded name deserves to be told, not quietly patched.
44
+ */
32
45
  export const META_TOOLS = {
33
- SEARCH: 'search_gov_data_tools',
34
- DESCRIBE: 'describe_gov_data_tool',
35
- RUN: 'run_gov_data_tool',
46
+ SEARCH: 'search-gov-data-tools',
47
+ DESCRIBE: 'describe-gov-data-tool',
48
+ RUN: 'run-gov-data-tool',
49
+ };
50
+
51
+ export const RENAMED_IN_1_1 = {
52
+ search_gov_data_tools: 'search-gov-data-tools',
53
+ describe_gov_data_tool: 'describe-gov-data-tool',
54
+ run_gov_data_tool: 'run-gov-data-tool',
36
55
  };
37
56
 
38
57
  /** MCP tool names must match ^[a-zA-Z0-9_-]{1,64}$. Slugs already do. */
@@ -78,9 +97,30 @@ export const ANNOTATIONS = {
78
97
  BILLED_LIVE_READ: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
79
98
  };
80
99
 
100
+ /** Format a price so an agent can compare tools without doing arithmetic. */
101
+ export function priceLine(pricing) {
102
+ if (!pricing) return 'Price unavailable for this tool — check its Store page before calling.';
103
+ const per1k = pricing.usdPer1000;
104
+ const cheapest = pricing.tierDiscountsUsdPerUnit
105
+ ? Math.min(...Object.values(pricing.tierDiscountsUsdPerUnit))
106
+ : pricing.usdPerUnit;
107
+ const discount = cheapest < pricing.usdPerUnit
108
+ ? ` Lower on paid Apify plans, down to $${(cheapest * 1000).toFixed(2)} per 1,000.`
109
+ : '';
110
+ return `$${pricing.usdPerUnit} per ${pricing.unit} ($${per1k} per 1,000).${discount}`;
111
+ }
112
+
113
+ export function costNote(pricing) {
114
+ return 'COST AND SIDE EFFECTS: read-only with respect to the government source — it never writes to any external system — '
115
+ + `but each call starts a metered run on YOUR Apify account, billed ${priceLine(pricing)} `
116
+ + 'Nothing is charged when a run fails.';
117
+ }
118
+
119
+ /** The generic, price-free wording used by the run tool, which can run anything. */
81
120
  export const COST_NOTE =
82
121
  'COST AND SIDE EFFECTS: read-only with respect to the government source — it never writes to any external system — '
83
- + 'but each call starts a metered run on YOUR Apify account and is billed per result row at the rate published on the Store page. '
122
+ + 'but each call starts a metered run on YOUR Apify account, billed per result row at the rate this tool reports. '
123
+ + 'Call describe-gov-data-tool first to see the exact price before running anything. '
84
124
  + 'Nothing is charged when a run fails.';
85
125
 
86
126
  /**
@@ -117,7 +157,7 @@ export function featuredToolDefinitions(index) {
117
157
  const routing = ROUTING[slug] ? ` ${ROUTING[slug]}` : '';
118
158
  return {
119
159
  name: toolNameFor(slug),
120
- description: `${a.title}. ${truncate(a.description, 400)}${routing} Reads live from the official government source. ${COST_NOTE} Store page: ${a.storeUrl}`,
160
+ description: `${a.title}. ${truncate(a.description, 400)}${routing} Reads live from the official government source. ${costNote(a.pricing)} Store page: ${a.storeUrl}`,
121
161
  inputSchema: a.inputSchema,
122
162
  annotations: { title: a.title, ...ANNOTATIONS.BILLED_LIVE_READ },
123
163
  };
@@ -161,7 +201,7 @@ export function metaToolDefinitions(index) {
161
201
  type: 'object',
162
202
  properties: {
163
203
  tool: { type: 'string', description: 'The tool name to run, e.g. "usgs-seismic-design-screener".' },
164
- input: { type: 'object', description: 'Input object matching the schema returned by describe_gov_data_tool.' },
204
+ input: { type: 'object', description: `Input object matching the schema returned by ${META_TOOLS.DESCRIBE}.` },
165
205
  maxItems: { type: 'integer', description: 'Maximum rows to return. Default 200.', minimum: 1, maximum: 1000 },
166
206
  },
167
207
  required: ['tool', 'input'],
@@ -193,6 +233,9 @@ export function searchCatalog(index, query, limit = 10) {
193
233
  title: x.actor.title,
194
234
  description: truncate(x.actor.description, 300),
195
235
  categories: x.actor.categories,
236
+ // Price rides along on every search hit so an agent can weigh cost while
237
+ // choosing, rather than discovering it only after it has already called.
238
+ usdPer1000Results: x.actor.pricing ? x.actor.pricing.usdPer1000 : null,
196
239
  storeUrl: x.actor.storeUrl,
197
240
  }));
198
241
  }
@@ -212,6 +255,9 @@ export function describeTool(index, name) {
212
255
  title: actor.title,
213
256
  description: actor.description,
214
257
  categories: actor.categories,
258
+ pricing: actor.pricing
259
+ ? { ...actor.pricing, summary: priceLine(actor.pricing) }
260
+ : null,
215
261
  storeUrl: actor.storeUrl,
216
262
  inputSchema: actor.inputSchema,
217
263
  };
@@ -229,7 +275,12 @@ export function resolveCall(index, toolName, args) {
229
275
  }
230
276
  return { ok: true, slug, input: (args && args.input) || {}, maxItems: args && args.maxItems };
231
277
  }
232
- return { ok: false, error: `Unknown tool "${toolName}".` };
278
+ // A caller with a hardcoded pre-1.1 name gets told what happened rather than
279
+ // silently aliased, so the hardcode gets fixed instead of quietly persisting.
280
+ if (RENAMED_IN_1_1[toolName]) {
281
+ return { ok: false, error: `"${toolName}" was renamed to "${RENAMED_IN_1_1[toolName]}" in gov-data-mcp v1.1.0, so that every tool on this server uses one naming convention. Call "${RENAMED_IN_1_1[toolName]}" instead — the arguments are unchanged.` };
282
+ }
283
+ return { ok: false, error: `Unknown tool "${toolName}". Call ${META_TOOLS.SEARCH} to list what this server offers.` };
233
284
  }
234
285
 
235
286
  /** Shape a run result into MCP text content. Never collapses a failure into an empty answer. */