@forgemeshlabs/utility-grid-mcp 0.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/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ .git
2
+ node_modules
3
+ npm-debug.log*
4
+ *.log
5
+ .env
6
+ .env.*
7
+ *.tgz
8
+ coverage
9
+ .DS_Store
package/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ FROM node:20-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package.json package-lock.json ./
6
+ RUN npm ci --omit=dev
7
+
8
+ COPY index.js README.md LICENSE server.json glama.json GLAMA.md smithery.yaml ./
9
+ RUN chmod +x /app/index.js
10
+
11
+ ENV NODE_ENV=production \
12
+ UTILITY_GRID_BASE_URL=https://x402.forgemesh.io
13
+
14
+ USER node
15
+
16
+ ENTRYPOINT ["node", "index.js"]
package/GLAMA.md ADDED
@@ -0,0 +1,65 @@
1
+ # Glama Build Notes
2
+
3
+ Durable source for Glama admin fields. Keep this in the public MCP repo so the
4
+ next refresh does not depend on chat history.
5
+
6
+ ## Repository
7
+
8
+ - GitHub: https://github.com/forgemeshlabs/utility-grid-mcp
9
+ - npm: @forgemeshlabs/utility-grid-mcp
10
+ - Hosted API: https://x402.forgemesh.io
11
+
12
+ ## Build Steps
13
+
14
+ Plain JavaScript with lockfile:
15
+
16
+ ```json
17
+ ["npm ci --omit=dev"]
18
+ ```
19
+
20
+ `package.json` also defines a no-op `"build"` script (`echo "no build step required"`).
21
+ Glama's generic build pipeline runs `npm install && npm run build` regardless of
22
+ the custom steps above — without this no-op script the build fails with
23
+ `npm error Missing script: "build"`. Keep this script in every future MCP package
24
+ even when there is nothing to compile.
25
+
26
+ ## Command Arguments
27
+
28
+ ```json
29
+ ["node", "index.js"]
30
+ ```
31
+
32
+ If Glama shows a full proxy wrapper, it may render something like:
33
+
34
+ ```json
35
+ ["mcp-proxy", "--", "node", "index.js"]
36
+ ```
37
+
38
+ In the manual command field, enter only the server startup command unless Glama
39
+ explicitly asks for the wrapped command.
40
+
41
+ ## Placeholder Arguments
42
+
43
+ ```json
44
+ {}
45
+ ```
46
+
47
+ ## Environment Variables
48
+
49
+ Canonical env schema belongs in `server.json`, not `glama.json`.
50
+
51
+ - `WALLET_PRIVATE_KEY` - optional, only required for the 3 paid tools (`call_endpoint`, `daily_402`, `agent_service_directory`). A dedicated low-balance Base wallet.
52
+ - `UTILITY_GRID_BASE_URL` - optional hosted API base URL override.
53
+
54
+ ## Dockerfile
55
+
56
+ Glama generates its own server runtime from admin fields. The repo Dockerfile is
57
+ a reference and local smoke-test target only.
58
+
59
+ ```bash
60
+ docker build -t utility-grid-mcp .
61
+ docker run --rm -i utility-grid-mcp
62
+ ```
63
+
64
+ Do not bake wallet private keys, API keys, `.env`, runtime databases, or test
65
+ wallet files into the image.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ForgeMesh Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # ForgeMesh Utility Grid — Agent Utility APIs via x402
2
+
3
+ *A [ForgeMesh Labs](https://forgemesh.io) product.*
4
+
5
+ **415+ small agent utility jobs behind 6 tools, not 415.** OCR, vision, audio, embeddings, web extraction, dictionary and slang lookups, chess/legal-move logic, math, conversions, time, geo/space data, and x402 ecosystem intelligence — all paid per call in USDC on Base via [x402](https://x402.org). No API key, no account.
6
+
7
+ Rather than exposing one MCP tool per route (unmanageable at this scale and growing), this server exposes a handful of **meta-tools**: browse/search the live catalog, fetch a route's exact call spec, then call any route generically. New routes on the upstream service show up automatically — nothing here is hardcoded to today's catalog.
8
+
9
+ ## Quick start (Claude Desktop / Claude Code / any MCP client)
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "utility-grid": {
15
+ "command": "npx",
16
+ "args": ["-y", "@forgemeshlabs/utility-grid-mcp"],
17
+ "env": {
18
+ "WALLET_PRIVATE_KEY": "0x..."
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ `WALLET_PRIVATE_KEY` is only needed for the 3 paid tools. Discovery works with **no wallet at all** — skip the `env` block entirely to browse and plan calls for free.
26
+
27
+ ## Tools
28
+
29
+ | Tool | Cost | What it does |
30
+ |---|---|---|
31
+ | `list_capabilities` | **free** | Category overview with route counts, or every route in one category |
32
+ | `search_capabilities` | **free** | Keyword search across every route's path, id, and description |
33
+ | `get_endpoint_spec` | **free** | A route's exact price, input schema, and worked request/response examples |
34
+ | `call_endpoint` | $0.001-$0.05 | Call any route by path + JSON body — handles the x402 payment flow automatically |
35
+ | `daily_402` | $0.001 | The Daily 402: one featured endpoint from the whole ForgeMesh fleet, rotated by UTC date |
36
+ | `agent_service_directory` | $0.05 | Registry of every ForgeMesh x402 service — category, route count, price range |
37
+
38
+ ## Categories in the catalog (illustrative, not exhaustive)
39
+
40
+ geo (airports, zip codes, geocoding, weather), lookups (dictionary, Bible, country info, holidays), math (statistics, matrices, equations, geometry), utilities (QR codes, hashing, UUIDs, phone parsing), vision (OCR, image captioning, background removal), audio (TTS, transcription, format conversion), time (timezones, cron, sun/moon), space (NASA APOD, asteroids, solar weather), web (content extraction, robots.txt/Content-Signal checks), registry (fleet catalog, daily spotlight), documents (PDF handling), text (language detection, readability), economy (fx rates, financial calculators), science, developer, domains, ai, and fun (fortunes, chess, trivia).
41
+
42
+ Call `list_capabilities` with no arguments for the live, current breakdown — the catalog grows over time and this README will drift; the tool won't.
43
+
44
+ ## Typical flow
45
+
46
+ 1. `search_capabilities({ query: "background removal" })` — free, finds `/remove-background`
47
+ 2. `get_endpoint_spec({ path: "remove-background" })` — free, shows the exact input schema and price
48
+ 3. `call_endpoint({ path: "remove-background", body: { image_url: "https://..." } })` — paid, does the x402 dance and returns the result
49
+
50
+ Or skip straight to `call_endpoint` if you already know the route and its input shape.
51
+
52
+ ## How payment works
53
+
54
+ No signup, no API key, no subscription. `call_endpoint`, `daily_402`, and `agent_service_directory` each trigger the same flow: the first request returns an HTTP 402 challenge, this MCP server signs a USDC payment authorization (EIP-3009) on Base and retries, and the result lands in the same response — including settlement details under `_payment` when available.
55
+
56
+ ## Direct API
57
+
58
+ Prefer raw HTTP? The full agent-readable surface:
59
+
60
+ - `https://x402.forgemesh.io/llms.txt` — one-page summary for agents
61
+ - `https://x402.forgemesh.io/openapi.json` — OpenAPI 3.1 with x402 payment metadata, full input schemas, and worked examples for every route (this is what `list_capabilities`/`search_capabilities`/`get_endpoint_spec` read from, live, every call)
62
+ - `https://x402.forgemesh.io/.well-known/x402.json` — x402 discovery manifest
63
+
64
+ ## FAQ
65
+
66
+ **Do I need an account or API key?** No. x402 payments are the only credential, and only for the 3 paid tools.
67
+
68
+ **What chain and token?** USDC on Base mainnet (`eip155:8453`).
69
+
70
+ **Will this break when the upstream catalog changes?** No — `list_capabilities`, `search_capabilities`, and `get_endpoint_spec` all read `/openapi.json` live (cached in-memory for 5 minutes). `call_endpoint` never needs a hardcoded schema at all; it just forwards your JSON body.
71
+
72
+ ---
73
+
74
+ Built by [ForgeMesh Labs](https://forgemesh.io) · Powered by the [x402 protocol](https://x402.org) · MIT License
package/glama.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "name": "@forgemeshlabs/utility-grid-mcp",
4
+ "description": "415+ agent utility jobs (OCR, vision, audio, math, conversions, geo/space, x402 intelligence) behind 6 meta-tools with free discovery and generic paid calling via x402.",
5
+ "maintainers": [
6
+ "clawdbotworker"
7
+ ],
8
+ "build": {
9
+ "steps": [
10
+ "npm ci --omit=dev"
11
+ ],
12
+ "cmd": [
13
+ "node",
14
+ "index.js"
15
+ ]
16
+ },
17
+ "placeholderArguments": {},
18
+ "tags": [
19
+ "x402",
20
+ "usdc",
21
+ "base",
22
+ "utility-grid",
23
+ "ocr",
24
+ "vision",
25
+ "tts",
26
+ "geo",
27
+ "math",
28
+ "mcp",
29
+ "autonomous-agents"
30
+ ]
31
+ }
package/index.js ADDED
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
7
+ const { x402Client, x402HTTPClient } = require("@x402/core/client");
8
+ const { ExactEvmScheme } = require("@x402/evm/exact/client");
9
+ const { toClientEvmSigner } = require("@x402/evm");
10
+ const { privateKeyToAccount } = require("viem/accounts");
11
+ const { createPublicClient, http } = require("viem");
12
+ const { base } = require("viem/chains");
13
+
14
+ const VERSION = "0.1.0";
15
+ const BASE_URL = (process.env.UTILITY_GRID_BASE_URL || "https://x402.forgemesh.io").replace(/\/$/, "");
16
+ const BASE_RPC_URL = process.env.BASE_RPC_URL || "https://mainnet.base.org";
17
+
18
+ // Example upstream capabilities, grouped by category, to orient an agent
19
+ // before it calls list_capabilities/search_capabilities. This is illustrative
20
+ // only — the live catalog (415 routes at build time, and growing) is always
21
+ // fetched fresh from /openapi.json, never hardcoded here.
22
+ const CATEGORY_EXAMPLES =
23
+ "geo (airports, zip codes, geocoding, weather), lookups (dictionary, bible, country info, holidays), " +
24
+ "math (statistics, matrices, equations, geometry), utilities (QR codes, hashing, UUIDs, phone parsing), " +
25
+ "vision (OCR, image captioning, background removal), audio (TTS, transcription, format conversion), " +
26
+ "time (timezones, cron, sun/moon), space (NASA APOD, asteroids, solar weather), " +
27
+ "web (content extraction, robots.txt/Content-Signal checks), registry (fleet catalog, daily spotlight), " +
28
+ "documents (PDF handling), text (language detection, readability), economy (fx rates, financial calculators), " +
29
+ "science, developer, domains, ai, conversions, and fun (fortunes, chess, trivia)";
30
+
31
+ // --- discovery (free, no wallet needed) -----------------------------------
32
+
33
+ let discoveryCache = null; // { at: number, spec: object }
34
+ const DISCOVERY_TTL_MS = 5 * 60 * 1000;
35
+
36
+ async function fetchOpenApiSpec() {
37
+ if (discoveryCache && Date.now() - discoveryCache.at < DISCOVERY_TTL_MS) return discoveryCache.spec;
38
+ const res = await fetch(`${BASE_URL}/openapi.json`);
39
+ if (!res.ok) throw new Error(`Failed to fetch discovery doc: HTTP ${res.status}`);
40
+ const spec = await res.json();
41
+ discoveryCache = { at: Date.now(), spec };
42
+ return spec;
43
+ }
44
+
45
+ function normalizePath(input) {
46
+ let p = String(input || "").trim();
47
+ if (!p) return "";
48
+ if (!p.startsWith("/")) p = "/" + p;
49
+ return p.replace(/\/+$/, "") || "/";
50
+ }
51
+
52
+ function routeEntries(spec) {
53
+ const paths = spec.paths || {};
54
+ return Object.entries(paths).map(([path, methods]) => {
55
+ const post = methods.post || {};
56
+ const price = post["x-payment-info"]?.price?.amount;
57
+ return {
58
+ path,
59
+ category: (post.tags || [])[0] || "uncategorized",
60
+ price: price ? `$${price}` : undefined,
61
+ description: post.description || post.summary || "",
62
+ operationId: post.operationId,
63
+ };
64
+ });
65
+ }
66
+
67
+ function truncate(s, n) {
68
+ if (!s) return s;
69
+ return s.length > n ? s.slice(0, n - 1).trimEnd() + "…" : s;
70
+ }
71
+
72
+ async function listCapabilities(args) {
73
+ const spec = await fetchOpenApiSpec();
74
+ const entries = routeEntries(spec);
75
+ const category = args.category ? String(args.category).toLowerCase() : null;
76
+
77
+ if (!category) {
78
+ const counts = {};
79
+ for (const e of entries) counts[e.category] = (counts[e.category] || 0) + 1;
80
+ return {
81
+ total_routes: entries.length,
82
+ categories: Object.entries(counts)
83
+ .sort((a, b) => b[1] - a[1])
84
+ .map(([name, count]) => ({ category: name, route_count: count })),
85
+ hint: "Pass a category to list its routes, e.g. { \"category\": \"math\" }. Or use search_capabilities with a keyword.",
86
+ full_catalog: `${BASE_URL}/openapi.json`,
87
+ };
88
+ }
89
+
90
+ const matches = entries.filter((e) => e.category.toLowerCase() === category);
91
+ if (matches.length === 0) {
92
+ const available = [...new Set(entries.map((e) => e.category))].sort();
93
+ throw new Error(`No routes found in category "${args.category}". Available categories: ${available.join(", ")}`);
94
+ }
95
+ return {
96
+ category,
97
+ route_count: matches.length,
98
+ routes: matches.map((e) => ({ path: e.path, price: e.price, description: truncate(e.description, 200) })),
99
+ };
100
+ }
101
+
102
+ async function searchCapabilities(args) {
103
+ const spec = await fetchOpenApiSpec();
104
+ const entries = routeEntries(spec);
105
+ const q = String(args.query || "").toLowerCase().trim();
106
+ if (!q) throw new Error("query is required");
107
+ const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
108
+ const matches = entries
109
+ .filter(
110
+ (e) =>
111
+ e.path.toLowerCase().includes(q) ||
112
+ (e.operationId || "").toLowerCase().includes(q) ||
113
+ e.description.toLowerCase().includes(q)
114
+ )
115
+ .slice(0, limit);
116
+ return {
117
+ query: args.query,
118
+ match_count: matches.length,
119
+ routes: matches.map((e) => ({
120
+ path: e.path,
121
+ category: e.category,
122
+ price: e.price,
123
+ description: truncate(e.description, 200),
124
+ })),
125
+ };
126
+ }
127
+
128
+ async function getEndpointSpec(args) {
129
+ const spec = await fetchOpenApiSpec();
130
+ const path = normalizePath(args.path);
131
+ const post = spec.paths?.[path]?.post;
132
+ if (!post) {
133
+ // fall back to a loose search so a slightly-off name still resolves
134
+ const entries = routeEntries(spec);
135
+ const norm = path.replace(/^\//, "").toLowerCase();
136
+ const candidates = entries.filter(
137
+ (e) => e.path.toLowerCase().includes(norm) || (e.operationId || "").toLowerCase().includes(norm.replace(/-/g, "_"))
138
+ );
139
+ if (candidates.length === 1) {
140
+ return getEndpointSpec({ path: candidates[0].path });
141
+ }
142
+ throw new Error(
143
+ candidates.length > 1
144
+ ? `"${args.path}" is ambiguous. Candidates: ${candidates.map((c) => c.path).join(", ")}`
145
+ : `No route matching "${args.path}". Use list_capabilities or search_capabilities to find the exact path.`
146
+ );
147
+ }
148
+ const schema = post.requestBody?.content?.["application/json"]?.schema;
149
+ const requestExample = post.requestBody?.content?.["application/json"]?.example;
150
+ const responseExample = post.responses?.["200"]?.content?.["application/json"]?.example;
151
+ return {
152
+ path,
153
+ method: "POST",
154
+ category: (post.tags || [])[0],
155
+ price: post["x-payment-info"]?.price?.amount ? `$${post["x-payment-info"].price.amount}` : undefined,
156
+ description: post.description || post.summary,
157
+ input_schema: schema,
158
+ request_example: requestExample,
159
+ response_example: responseExample,
160
+ };
161
+ }
162
+
163
+ // --- payment client ---------------------------------------------------------
164
+
165
+ function buildBaseHttpClient() {
166
+ const key = process.env.WALLET_PRIVATE_KEY;
167
+ if (!key) {
168
+ throw new Error(
169
+ "WALLET_PRIVATE_KEY is not set. Paid utility-grid calls cost $0.001-$0.05 via x402 — set a dedicated low-balance Base wallet private key (never your primary wallet) with a small amount of USDC on Base mainnet. Discovery tools (list_capabilities, search_capabilities, get_endpoint_spec) work without one."
170
+ );
171
+ }
172
+ const pk = key.startsWith("0x") ? key : "0x" + key;
173
+ const account = privateKeyToAccount(pk);
174
+ const coreClient = new x402Client().register("eip155:*", new ExactEvmScheme(toClientEvmSigner(account)));
175
+ return { httpClient: new x402HTTPClient(coreClient), account };
176
+ }
177
+
178
+ // x402 derives EIP-3009 validity windows from Date.now; choose a timestamp
179
+ // valid for both Base block time and facilitator wall-clock checks (clock-skew fix).
180
+ async function createChainTimedPaymentPayload(httpClient, paymentRequired) {
181
+ try {
182
+ const publicClient = createPublicClient({ chain: base, transport: http(BASE_RPC_URL) });
183
+ const block = await publicClient.getBlock();
184
+ const chainNow = Number(block.timestamp);
185
+ const originalNow = Date.now;
186
+ const localNow = Math.floor(originalNow() / 1000);
187
+ const timeout = Number(paymentRequired.accepts?.[0]?.maxTimeoutSeconds || 300);
188
+ const lowerBound = localNow + 30 - timeout;
189
+ const upperBound = chainNow + 600;
190
+ const signingNow = Math.min(Math.max(chainNow, lowerBound), upperBound);
191
+ Date.now = () => signingNow * 1000;
192
+ try {
193
+ return await httpClient.createPaymentPayload(paymentRequired);
194
+ } finally {
195
+ Date.now = originalNow;
196
+ }
197
+ } catch (_) {
198
+ return httpClient.createPaymentPayload(paymentRequired);
199
+ }
200
+ }
201
+
202
+ async function paidPost(ctx, path, body) {
203
+ const { httpClient } = ctx;
204
+ const url = BASE_URL + path;
205
+ const init = { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body || {}) };
206
+ const res = await fetch(url, init);
207
+
208
+ if (res.status === 402) {
209
+ let challengeBody;
210
+ try {
211
+ challengeBody = await res.clone().json();
212
+ } catch (_) {}
213
+ const paymentRequired = httpClient.getPaymentRequiredResponse((name) => res.headers.get(name), challengeBody);
214
+ const paymentPayload = await createChainTimedPaymentPayload(httpClient, paymentRequired);
215
+ const paidRes = await fetch(url, {
216
+ ...init,
217
+ headers: { ...init.headers, ...httpClient.encodePaymentSignatureHeader(paymentPayload) },
218
+ });
219
+ if (!paidRes.ok) {
220
+ const errBody = await paidRes.text().catch(() => paidRes.statusText);
221
+ throw new Error(`HTTP ${paidRes.status}: ${errBody.slice(0, 300)}`);
222
+ }
223
+ const data = await paidRes.json();
224
+ try {
225
+ const settleResponse = httpClient.getPaymentSettleResponse((name) => paidRes.headers.get(name));
226
+ if (settleResponse && data && typeof data === "object" && !Array.isArray(data)) {
227
+ return { ...data, _payment: settleResponse };
228
+ }
229
+ } catch (_) {}
230
+ return data;
231
+ }
232
+
233
+ if (!res.ok) {
234
+ const errBody = await res.text().catch(() => res.statusText);
235
+ throw new Error(`HTTP ${res.status}: ${errBody.slice(0, 300)}`);
236
+ }
237
+ return res.json();
238
+ }
239
+
240
+ const TOOLS = [
241
+ {
242
+ name: "list_capabilities",
243
+ description:
244
+ `FREE — no wallet needed. Browse the ForgeMesh Utility Grid catalog (415+ POST routes and growing, covering ${CATEGORY_EXAMPLES}). Call with no arguments for a category overview with route counts, or pass a category to list every route in it with price and description. Always reads the live /openapi.json — never a stale/hardcoded list.`,
245
+ inputSchema: {
246
+ type: "object",
247
+ properties: {
248
+ category: { type: "string", description: "Optional category to list routes for, e.g. 'math' or 'vision'" },
249
+ },
250
+ },
251
+ },
252
+ {
253
+ name: "search_capabilities",
254
+ description:
255
+ "FREE — no wallet needed. Keyword search across every route's path, operation id, and description (e.g. 'chess', 'timezone', 'background removal'). Use this when you don't know the exact route name or category.",
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: {
259
+ query: { type: "string", description: "Keyword to search for" },
260
+ limit: { type: "integer", description: "Max results (default 20, max 50)" },
261
+ },
262
+ required: ["query"],
263
+ },
264
+ },
265
+ {
266
+ name: "get_endpoint_spec",
267
+ description:
268
+ "FREE — no wallet needed. Full call spec for one route: price, input JSON schema, a worked request example, and a worked response example, straight from the live OpenAPI discovery doc. Pass the route path (with or without a leading slash, e.g. 'chess-moves' or '/chess-moves'). Use this before call_endpoint to know exactly what body to send.",
269
+ inputSchema: {
270
+ type: "object",
271
+ properties: {
272
+ path: { type: "string", description: "Route path, e.g. 'chess-moves' or '/geocode-city'" },
273
+ },
274
+ required: ["path"],
275
+ },
276
+ },
277
+ {
278
+ name: "call_endpoint",
279
+ description:
280
+ "PAID (price varies by route, $0.001-$0.05) — the generic way to call ANY route in the Utility Grid. Pass the route path and a JSON body matching its input schema (use get_endpoint_spec first if unsure). Handles the full x402 payment flow automatically: fetches the 402 challenge, signs a USDC payment on Base, retries, and returns the result. Requires WALLET_PRIVATE_KEY.",
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ path: { type: "string", description: "Route path, e.g. 'chess-moves' or '/geocode-city'" },
285
+ body: { type: "object", description: "JSON body matching the route's input schema (see get_endpoint_spec)" },
286
+ },
287
+ required: ["path"],
288
+ },
289
+ },
290
+ {
291
+ name: "daily_402",
292
+ description:
293
+ "PAID ($0.001) — the Daily 402: one featured x402 endpoint per UTC day, rotated deterministically through every paid route in the 12-service ForgeMesh fleet (500+ routes). Returns what it does, its price, input schema, and a worked example. Optional date override to replay a past day's pick. Requires WALLET_PRIVATE_KEY.",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ date: { type: "string", description: "Optional ISO date (YYYY-MM-DD) to replay a past day's featured endpoint" },
298
+ },
299
+ },
300
+ },
301
+ {
302
+ name: "agent_service_directory",
303
+ description:
304
+ "PAID ($0.05) — machine-readable registry of every production x402 service in the ForgeMesh fleet: name, category, live route count, and price range per service. Useful for an agent deciding which paid tool/service to reach for next. Optional category filter (onchain-intel, voice, travel, econ-intel, commerce, infra, media, utility-grid). Requires WALLET_PRIVATE_KEY.",
305
+ inputSchema: {
306
+ type: "object",
307
+ properties: {
308
+ category: { type: "string", description: "Optional: onchain-intel, voice, travel, econ-intel, commerce, infra, media, utility-grid" },
309
+ },
310
+ },
311
+ },
312
+ ];
313
+
314
+ async function main() {
315
+ let ctxPromise;
316
+ async function getPaymentContext() {
317
+ if (!ctxPromise) ctxPromise = Promise.resolve().then(buildBaseHttpClient);
318
+ return ctxPromise;
319
+ }
320
+
321
+ const server = new Server({ name: "utility-grid-mcp", version: VERSION }, { capabilities: { tools: {} } });
322
+
323
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
324
+
325
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
326
+ const { name, arguments: args = {} } = req.params;
327
+ try {
328
+ let data;
329
+ switch (name) {
330
+ case "list_capabilities":
331
+ data = await listCapabilities(args);
332
+ break;
333
+ case "search_capabilities":
334
+ data = await searchCapabilities(args);
335
+ break;
336
+ case "get_endpoint_spec":
337
+ data = await getEndpointSpec(args);
338
+ break;
339
+ case "call_endpoint":
340
+ data = await paidPost(await getPaymentContext(), normalizePath(args.path), args.body || {});
341
+ break;
342
+ case "daily_402":
343
+ data = await paidPost(await getPaymentContext(), "/daily", { date: args.date });
344
+ break;
345
+ case "agent_service_directory":
346
+ data = await paidPost(await getPaymentContext(), "/agent-service-directory", { category: args.category });
347
+ break;
348
+ default:
349
+ throw new Error(`Unknown tool: ${name}`);
350
+ }
351
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
352
+ } catch (e) {
353
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
354
+ }
355
+ });
356
+
357
+ const transport = new StdioServerTransport();
358
+ await server.connect(transport);
359
+ console.error(`utility-grid-mcp v${VERSION} ready — ${BASE_URL}`);
360
+ }
361
+
362
+ if (require.main === module) {
363
+ main().catch((e) => {
364
+ console.error("Fatal:", e.message);
365
+ process.exit(1);
366
+ });
367
+ }
368
+
369
+ module.exports = {
370
+ TOOLS,
371
+ normalizePath,
372
+ routeEntries,
373
+ listCapabilities,
374
+ searchCapabilities,
375
+ getEndpointSpec,
376
+ buildBaseHttpClient,
377
+ fetchOpenApiSpec,
378
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@forgemeshlabs/utility-grid-mcp",
3
+ "version": "0.1.0",
4
+ "description": "ForgeMesh Utility Grid, by ForgeMesh Labs — 415+ small agent utility jobs (OCR, vision, audio, embeddings, web extraction, dictionary/slang, chess, math, conversions, time, geo/space data, and x402 ecosystem intelligence) behind 6 meta-tools instead of one tool per route. Free discovery (browse/search the live catalog, fetch a route's full call spec), paid generic call via x402 USDC micropayments on Base. No API key, no account.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "utility-grid-mcp": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "README.md",
12
+ "server.json",
13
+ "glama.json",
14
+ "GLAMA.md",
15
+ "Dockerfile",
16
+ ".dockerignore",
17
+ "smithery.yaml",
18
+ "LICENSE"
19
+ ],
20
+ "mcpName": "io.github.forgemeshlabs/utility-grid-mcp",
21
+ "scripts": {
22
+ "start": "node index.js",
23
+ "build": "echo \"no build step required\"",
24
+ "test": "node --test test/"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "x402",
30
+ "utility-grid",
31
+ "ocr",
32
+ "vision",
33
+ "tts",
34
+ "geo",
35
+ "math",
36
+ "usdc",
37
+ "base",
38
+ "micropayments",
39
+ "autonomous-agents",
40
+ "agent-tools"
41
+ ],
42
+ "author": "ForgeMesh Labs (https://forgemesh.io)",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/forgemeshlabs/utility-grid-mcp.git"
47
+ },
48
+ "homepage": "https://x402.forgemesh.io",
49
+ "engines": {
50
+ "node": ">=20"
51
+ },
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.10.1",
54
+ "@x402/core": "^2.17.0",
55
+ "@x402/evm": "^2.17.0",
56
+ "viem": "^2.0.0"
57
+ }
58
+ }
package/server.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.forgemeshlabs/utility-grid-mcp",
4
+ "description": "415+ agent utility jobs (OCR, vision, audio, math, conversions, geo/space, x402 intelligence) behind 6 meta-tools with free discovery and generic paid calling via x402.",
5
+ "repository": {
6
+ "url": "https://github.com/forgemeshlabs/utility-grid-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "0.1.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@forgemeshlabs/utility-grid-mcp",
14
+ "version": "0.1.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ },
18
+ "environmentVariables": [
19
+ {
20
+ "name": "WALLET_PRIVATE_KEY",
21
+ "description": "Dedicated low-balance Base wallet private key for x402 USDC micropayments. Only required for the 3 paid tools (call_endpoint, daily_402, agent_service_directory) — the 3 discovery tools work without it. Never use your primary wallet.",
22
+ "isRequired": false,
23
+ "isSecret": true,
24
+ "format": "string"
25
+ },
26
+ {
27
+ "name": "UTILITY_GRID_BASE_URL",
28
+ "description": "Utility Grid API origin override. Defaults to https://x402.forgemesh.io.",
29
+ "isRequired": false,
30
+ "isSecret": false,
31
+ "format": "string"
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "title": "ForgeMesh Utility Grid MCP",
37
+ "websiteUrl": "https://x402.forgemesh.io",
38
+ "icons": [
39
+ {
40
+ "src": "https://x402.forgemesh.io/favicon.ico",
41
+ "mimeType": "image/png"
42
+ }
43
+ ]
44
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,30 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ properties:
6
+ utilityGridBaseUrl:
7
+ type: string
8
+ description: >-
9
+ Utility Grid API origin override. Defaults to
10
+ https://x402.forgemesh.io.
11
+ walletPrivateKey:
12
+ type: string
13
+ description: >-
14
+ Dedicated low-balance Base wallet private key for x402 USDC
15
+ micropayments. Optional — the 3 discovery tools (list_capabilities,
16
+ search_capabilities, get_endpoint_spec) work without any wallet.
17
+ Only the 3 paid tools (call_endpoint, daily_402,
18
+ agent_service_directory) need it. Never use your primary wallet.
19
+ commandFunction: |-
20
+ (config) => ({
21
+ command: 'npx',
22
+ args: ['-y', '@forgemeshlabs/utility-grid-mcp'],
23
+ env: {
24
+ ...(config.utilityGridBaseUrl ? { UTILITY_GRID_BASE_URL: config.utilityGridBaseUrl } : {}),
25
+ ...(config.walletPrivateKey ? { WALLET_PRIVATE_KEY: config.walletPrivateKey } : {})
26
+ }
27
+ })
28
+ exampleConfig:
29
+ utilityGridBaseUrl: 'https://x402.forgemesh.io'
30
+ walletPrivateKey: '0x...'