@secondlayer/mcp 11.0.0 → 11.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/dist/bin.js CHANGED
@@ -20,6 +20,63 @@ function isHostedArchiveUrl(url) {
20
20
  }
21
21
  }
22
22
 
23
+ // src/lib/tool.ts
24
+ var registeredToolNames = new Set;
25
+ var serverToolNames = new WeakMap;
26
+ var serverToolFilters = new WeakMap;
27
+ function getRegisteredToolNames(server) {
28
+ return server ? [...serverToolNames.get(server) ?? []] : [...registeredToolNames];
29
+ }
30
+ function restrictTools(server, allow) {
31
+ serverToolFilters.set(server, allow);
32
+ }
33
+ function envelopeFields(err) {
34
+ if (!err || typeof err !== "object")
35
+ return {};
36
+ const e = err;
37
+ const body = e.body && typeof e.body === "object" ? e.body : {};
38
+ const code = typeof e.code === "string" ? e.code : typeof body.code === "string" ? body.code : undefined;
39
+ const request_id = typeof body.request_id === "string" ? body.request_id : undefined;
40
+ const fb = body.feedback;
41
+ const feedback = fb && typeof fb === "object" && typeof fb.url === "string" ? { url: fb.url } : undefined;
42
+ return {
43
+ ...code ? { code } : {},
44
+ ...request_id ? { request_id } : {},
45
+ ...feedback ? { feedback } : {}
46
+ };
47
+ }
48
+ function defineTool(server, name, description, schema, handler) {
49
+ const wrappedHandler = async (args) => {
50
+ try {
51
+ return await handler(args);
52
+ } catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ const status = err instanceof Error && "status" in err ? err.status : 0;
55
+ const type = status === 401 ? "unauthorized" : status === 404 ? "not_found" : status === 429 ? "rate_limited" : status >= 500 ? "server_error" : "error";
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: JSON.stringify({
61
+ error: { type, status, message, ...envelopeFields(err) }
62
+ })
63
+ }
64
+ ],
65
+ isError: true
66
+ };
67
+ }
68
+ };
69
+ const allow = serverToolFilters.get(server);
70
+ if (allow && !allow(name))
71
+ return;
72
+ if (!allow)
73
+ registeredToolNames.add(name);
74
+ const names = serverToolNames.get(server) ?? new Set;
75
+ names.add(name);
76
+ serverToolNames.set(server, names);
77
+ server.tool(name, description, schema, wrappedHandler);
78
+ }
79
+
23
80
  // src/resources.ts
24
81
  import {
25
82
  ResourceTemplate
@@ -80,37 +137,40 @@ function getArchiveOpsClient() {
80
137
  function keyHint(baseUrl = resolveBaseUrl()) {
81
138
  return isHostedApiUrl(baseUrl) ? " — set SECONDLAYER_API_KEY (an sk-sl_ key from secondlayer.tools/account/keys)" : " — set INSTANCE_TOKEN from `sl init` for writes";
82
139
  }
83
- async function apiRequest(method, path, body) {
84
- const baseUrl = resolveBaseUrl();
85
- const apiKey = readApiKey();
86
- const res = await fetch(`${baseUrl}${path}`, {
87
- method,
88
- headers: {
89
- "Content-Type": "application/json",
90
- ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
91
- },
92
- body: body ? JSON.stringify(body) : undefined
93
- });
94
- if (!res.ok) {
95
- const text = await res.text().catch(() => "");
96
- const needsKey = !apiKey && (res.status === 401 || res.status === 403);
97
- let parsed;
98
- try {
99
- parsed = text ? JSON.parse(text) : undefined;
100
- } catch {
101
- parsed = undefined;
102
- }
103
- const code = parsed && typeof parsed === "object" && typeof parsed.code === "string" ? parsed.code : undefined;
104
- throw Object.assign(new Error((text || `HTTP ${res.status}`) + (needsKey ? keyHint(baseUrl) : "")), {
105
- status: res.status,
106
- ...parsed !== undefined ? { body: parsed } : {},
107
- ...code ? { code } : {}
140
+ function createApiRequest(config) {
141
+ const doFetch = config.fetchImpl ?? ((...args) => fetch(...args));
142
+ return async (method, path, body) => {
143
+ const { baseUrl, apiKey } = config;
144
+ const res = await doFetch(`${baseUrl}${path}`, {
145
+ method,
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
149
+ },
150
+ body: body ? JSON.stringify(body) : undefined
108
151
  });
109
- }
110
- if (res.status === 204)
111
- return;
112
- return res.json();
152
+ if (!res.ok) {
153
+ const text = await res.text().catch(() => "");
154
+ const needsKey = !apiKey && (res.status === 401 || res.status === 403);
155
+ let parsed;
156
+ try {
157
+ parsed = text ? JSON.parse(text) : undefined;
158
+ } catch {
159
+ parsed = undefined;
160
+ }
161
+ const code = parsed && typeof parsed === "object" && typeof parsed.code === "string" ? parsed.code : undefined;
162
+ throw Object.assign(new Error((text || `HTTP ${res.status}`) + (needsKey ? keyHint(baseUrl) : "")), {
163
+ status: res.status,
164
+ ...parsed !== undefined ? { body: parsed } : {},
165
+ ...code ? { code } : {}
166
+ });
167
+ }
168
+ if (res.status === 204)
169
+ return;
170
+ return res.json();
171
+ };
113
172
  }
173
+ var apiRequest = (method, path, body) => createApiRequest({ baseUrl: resolveBaseUrl(), apiKey: readApiKey() })(method, path, body);
114
174
 
115
175
  // src/lib/format.ts
116
176
  function formatSubgraphSummary(s) {
@@ -141,51 +201,6 @@ function textResponse(text, isError) {
141
201
  };
142
202
  }
143
203
 
144
- // src/lib/tool.ts
145
- var registeredToolNames = new Set;
146
- function getRegisteredToolNames() {
147
- return [...registeredToolNames];
148
- }
149
- function envelopeFields(err) {
150
- if (!err || typeof err !== "object")
151
- return {};
152
- const e = err;
153
- const body = e.body && typeof e.body === "object" ? e.body : {};
154
- const code = typeof e.code === "string" ? e.code : typeof body.code === "string" ? body.code : undefined;
155
- const request_id = typeof body.request_id === "string" ? body.request_id : undefined;
156
- const fb = body.feedback;
157
- const feedback = fb && typeof fb === "object" && typeof fb.url === "string" ? { url: fb.url } : undefined;
158
- return {
159
- ...code ? { code } : {},
160
- ...request_id ? { request_id } : {},
161
- ...feedback ? { feedback } : {}
162
- };
163
- }
164
- function defineTool(server, name, description, schema, handler) {
165
- const wrappedHandler = async (args) => {
166
- try {
167
- return await handler(args);
168
- } catch (err) {
169
- const message = err instanceof Error ? err.message : String(err);
170
- const status = err instanceof Error && "status" in err ? err.status : 0;
171
- const type = status === 401 ? "unauthorized" : status === 404 ? "not_found" : status === 429 ? "rate_limited" : status >= 500 ? "server_error" : "error";
172
- return {
173
- content: [
174
- {
175
- type: "text",
176
- text: JSON.stringify({
177
- error: { type, status, message, ...envelopeFields(err) }
178
- })
179
- }
180
- ],
181
- isError: true
182
- };
183
- }
184
- };
185
- registeredToolNames.add(name);
186
- server.tool(name, description, schema, wrappedHandler);
187
- }
188
-
189
204
  // src/resources.ts
190
205
  var TRAIT_BLURBS = {
191
206
  "sip-009": "Non-fungible token (NFT) standard",
@@ -237,9 +252,9 @@ var PRODUCT_ORDER = [
237
252
  "archive",
238
253
  "credits"
239
254
  ];
240
- function buildCapabilities() {
255
+ function buildCapabilities(server) {
241
256
  const byPrefix = new Map;
242
- for (const name of getRegisteredToolNames()) {
257
+ for (const name of getRegisteredToolNames(server)) {
243
258
  const sep = name.indexOf("_");
244
259
  const prefix = sep === -1 ? name : name.slice(0, sep);
245
260
  const tools = byPrefix.get(prefix) ?? [];
@@ -260,7 +275,11 @@ function buildCapabilities() {
260
275
  discoverFirst: "Call index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
261
276
  };
262
277
  }
263
- function readAuthTiers(credential) {
278
+ function readAuthTiers(credential, hosted) {
279
+ if (hosted) {
280
+ const reads = `keyed: every read uses ${credential} and is metered to the account's credits (the first 1M rows each month are free)`;
281
+ return { index: reads, streams: reads };
282
+ }
264
283
  const reads = `keyless while the API is only reachable on loopback; ${credential} is required once it is published past loopback`;
265
284
  return {
266
285
  index: reads,
@@ -271,8 +290,9 @@ function readAuthTiers(credential) {
271
290
  function credentialLabel() {
272
291
  return isHostedApiUrl2(resolveBaseUrl2()) ? "SECONDLAYER_API_KEY (an sk-sl_ account key)" : "INSTANCE_TOKEN";
273
292
  }
293
+ var HOSTED_CREDENTIAL = "the caller's OAuth sign-in (or sk-sl_ account key)";
274
294
  async function buildContext(deps = { clientProvider: getClient }) {
275
- const credential = credentialLabel();
295
+ const credential = deps.hosted ? HOSTED_CREDENTIAL : credentialLabel();
276
296
  const unavailable = `unavailable: set ${credential}`;
277
297
  const orNull = (field) => {
278
298
  if (!field)
@@ -285,7 +305,7 @@ async function buildContext(deps = { clientProvider: getClient }) {
285
305
  const instance = snap?.instance?.value ? snap.instance.value : orNull(snap?.instance);
286
306
  return {
287
307
  authState: {
288
- apiKeySet: Boolean(readApiKey())
308
+ apiKeySet: deps.hosted ? true : Boolean(readApiKey())
289
309
  },
290
310
  whatExists: {
291
311
  account: orNull(snap?.account),
@@ -296,12 +316,13 @@ async function buildContext(deps = { clientProvider: getClient }) {
296
316
  activeOperations: orNull(snap?.activeOperations),
297
317
  instance
298
318
  },
299
- whatYouCanDo: buildCapabilities(),
300
- readAuthTiers: readAuthTiers(credential),
319
+ whatYouCanDo: buildCapabilities(deps.server),
320
+ readAuthTiers: readAuthTiers(credential, Boolean(deps.hosted)),
301
321
  ...snap?.instance?.value?.state === "empty-index" ? { nextStep: BOOTSTRAP_STEP } : {}
302
322
  };
303
323
  }
304
- function registerResources(server) {
324
+ function registerResources(server, deps = { clientProvider: getClient }) {
325
+ const contextDeps = deps.hosted ? { ...deps, server } : deps;
305
326
  server.resource("context", "secondlayer://context", {
306
327
  description: "Live agent context — what exists (your subgraphs, webhooks, account), what you can do, and read-auth tiers. Read this first."
307
328
  }, async () => ({
@@ -309,7 +330,7 @@ function registerResources(server) {
309
330
  {
310
331
  uri: "secondlayer://context",
311
332
  mimeType: "application/json",
312
- text: JSON.stringify(await buildContext(), null, 2)
333
+ text: JSON.stringify(await buildContext(contextDeps), null, 2)
313
334
  }
314
335
  ]
315
336
  }));
@@ -347,24 +368,26 @@ function registerResources(server) {
347
368
  }
348
369
  ]
349
370
  }));
350
- server.resource("chain-triggers", "secondlayer://chain-triggers", {
351
- description: "Chain-webhook trigger types and the filter fields each accepts (for webhooks_create triggers)."
352
- }, async () => ({
353
- contents: [
354
- {
355
- uri: "secondlayer://chain-triggers",
356
- mimeType: "application/json",
357
- text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
358
- }
359
- ]
360
- }));
371
+ if (!deps.hosted) {
372
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
373
+ description: "Chain-webhook trigger types and the filter fields each accepts (for webhooks_create triggers)."
374
+ }, async () => ({
375
+ contents: [
376
+ {
377
+ uri: "secondlayer://chain-triggers",
378
+ mimeType: "application/json",
379
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
380
+ }
381
+ ]
382
+ }));
383
+ }
361
384
  server.resource("samples", new ResourceTemplate("secondlayer://samples/{contractId}", {
362
385
  list: undefined
363
386
  }), {
364
387
  description: "Decoded camelCase print samples for a contract (schema + up to 5 events). Use before writing a print_event handler."
365
388
  }, async (uri, variables) => {
366
389
  const contractId = String(Array.isArray(variables.contractId) ? variables.contractId[0] : variables.contractId ?? "");
367
- const body = await buildSamplesResource(contractId);
390
+ const body = await buildSamplesResource(contractId, deps.clientProvider);
368
391
  return {
369
392
  contents: [
370
393
  {
@@ -429,14 +452,14 @@ async function buildSamplesResource(contractId, clientProvider = getClient) {
429
452
 
430
453
  // src/tools/account.ts
431
454
  import { z } from "zod";
432
- function registerAccountTools(server) {
455
+ function registerAccountTools(server, request = apiRequest) {
433
456
  defineTool(server, "account_whoami", "Show the archive account email at api.secondlayer.tools, not the self-host instance.", {}, async () => {
434
- const result = await apiRequest("GET", "/api/accounts/me");
457
+ const result = await request("GET", "/api/accounts/me");
435
458
  return jsonResponse(result);
436
459
  });
437
460
  defineTool(server, "account_create_key", "Mint an account key (sk-sl_*) for archive credits, quote, and fetch at api.secondlayer.tools. The returned `key` is shown ONCE.", {
438
461
  name: z.string().optional().describe("Optional label for the key")
439
- }, async ({ name }) => jsonResponse(await apiRequest("POST", "/api/keys", { name })));
462
+ }, async ({ name }) => jsonResponse(await request("POST", "/api/keys", { name })));
440
463
  }
441
464
 
442
465
  // src/tools/archive-cli.ts
@@ -1141,35 +1164,103 @@ function registerWebhookTools(server, clientProvider = getClient) {
1141
1164
  }
1142
1165
 
1143
1166
  // src/tool-groups.ts
1167
+ var ENV_TOOL_DEPS = {
1168
+ clientProvider: getClient,
1169
+ archiveOpsClientProvider: getArchiveOpsClient,
1170
+ apiRequest
1171
+ };
1144
1172
  var TOOL_GROUPS = [
1145
- { group: "scaffold", register: registerScaffoldTools },
1146
- { group: "subgraphs", register: registerSubgraphTools },
1147
- { group: "webhooks", register: registerWebhookTools },
1148
- { group: "index", register: registerIndexTools },
1149
- { group: "streams", register: registerStreamsTools },
1150
- { group: "contracts", register: registerContractTools },
1151
- { group: "codegen", register: registerCodegenTools },
1152
- { group: "instance", register: registerInstanceTools },
1153
- { group: "archive", register: registerArchiveTools },
1154
- { group: "archive", register: registerArchiveCliTools },
1155
- { group: "account", register: registerAccountTools, hostedOnly: true }
1173
+ {
1174
+ group: "scaffold",
1175
+ register: (s, d = ENV_TOOL_DEPS) => registerScaffoldTools(s, d.clientProvider),
1176
+ hosted: true
1177
+ },
1178
+ {
1179
+ group: "subgraphs",
1180
+ register: (s, d = ENV_TOOL_DEPS) => registerSubgraphTools(s, d.clientProvider),
1181
+ hosted: false
1182
+ },
1183
+ {
1184
+ group: "webhooks",
1185
+ register: (s, d = ENV_TOOL_DEPS) => registerWebhookTools(s, d.clientProvider),
1186
+ hosted: false
1187
+ },
1188
+ {
1189
+ group: "index",
1190
+ register: (s, d = ENV_TOOL_DEPS) => registerIndexTools(s, d.clientProvider),
1191
+ hosted: true
1192
+ },
1193
+ {
1194
+ group: "streams",
1195
+ register: (s, d = ENV_TOOL_DEPS) => registerStreamsTools(s, d.clientProvider),
1196
+ hosted: true
1197
+ },
1198
+ {
1199
+ group: "contracts",
1200
+ register: (s, d = ENV_TOOL_DEPS) => registerContractTools(s, d.clientProvider),
1201
+ hosted: true
1202
+ },
1203
+ { group: "codegen", register: (s) => registerCodegenTools(s), hosted: false },
1204
+ {
1205
+ group: "instance",
1206
+ register: (s, d = ENV_TOOL_DEPS) => registerInstanceTools(s, d.clientProvider),
1207
+ hosted: false
1208
+ },
1209
+ {
1210
+ group: "archive",
1211
+ register: (s, d = ENV_TOOL_DEPS) => registerArchiveTools(s, d.clientProvider, d.archiveOpsClientProvider),
1212
+ hosted: false
1213
+ },
1214
+ {
1215
+ group: "archive",
1216
+ register: (s) => registerArchiveCliTools(s),
1217
+ hosted: false
1218
+ },
1219
+ {
1220
+ group: "account",
1221
+ register: (s, d = ENV_TOOL_DEPS) => registerAccountTools(s, d.apiRequest),
1222
+ hostedOnly: true,
1223
+ hosted: ["account_whoami"]
1224
+ }
1156
1225
  ];
1226
+ function isHostedTool(group, tool) {
1227
+ return group.hosted === true || Array.isArray(group.hosted) && group.hosted.includes(tool);
1228
+ }
1157
1229
 
1158
1230
  // src/server.ts
1159
1231
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
1160
1232
  var pkg = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
1161
- function createServer() {
1233
+ function createServer(options = {}) {
1162
1234
  const server = new McpServer({
1163
1235
  name: "secondlayer",
1164
1236
  version: pkg.version
1165
1237
  });
1166
- const hosted = isHostedArchiveUrl(resolveBaseUrl3());
1238
+ const deps = {
1239
+ clientProvider: options.clientProvider ?? ENV_TOOL_DEPS.clientProvider,
1240
+ archiveOpsClientProvider: options.archiveOpsClientProvider ?? ENV_TOOL_DEPS.archiveOpsClientProvider,
1241
+ apiRequest: options.apiRequest ?? ENV_TOOL_DEPS.apiRequest
1242
+ };
1243
+ if (options.hosted) {
1244
+ for (const group of TOOL_GROUPS) {
1245
+ if (group.hosted === false)
1246
+ continue;
1247
+ restrictTools(server, (name) => isHostedTool(group, name));
1248
+ group.register(server, deps);
1249
+ }
1250
+ restrictTools(server, () => false);
1251
+ registerResources(server, {
1252
+ clientProvider: deps.clientProvider,
1253
+ hosted: true
1254
+ });
1255
+ return server;
1256
+ }
1257
+ const hostedApi = isHostedArchiveUrl(resolveBaseUrl3());
1167
1258
  for (const { register, hostedOnly } of TOOL_GROUPS) {
1168
- if (hostedOnly && !hosted)
1259
+ if (hostedOnly && !hostedApi)
1169
1260
  continue;
1170
- register(server);
1261
+ register(server, deps);
1171
1262
  }
1172
- registerResources(server);
1263
+ registerResources(server, { clientProvider: deps.clientProvider });
1173
1264
  return server;
1174
1265
  }
1175
1266
 
@@ -1178,5 +1269,5 @@ var server = createServer();
1178
1269
  var transport = new StdioServerTransport;
1179
1270
  await server.connect(transport);
1180
1271
 
1181
- //# debugId=7D6D11FBC95E490C64756E2164756E21
1272
+ //# debugId=B633445D908DEC9264756E2164756E21
1182
1273
  //# sourceMappingURL=bin.js.map