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