@lenso/mcp 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +33 -22
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -37,6 +37,16 @@ service still enforces its tenant/object/owner rules. Manage/`createAgentTools`
37
37
  are the canonical schema/metadata source; all selected schemas must convert to
38
38
  SDK-compatible object schemas before readiness, including currently hidden tools.
39
39
 
40
+ The borrowed adapter prepares one immutable Manage selection for its lifetime.
41
+ Discovery, invocation policies and bindings receive the same frozen declaration
42
+ snapshots, retaining exact plugin/schema/function identities. Do not key policies
43
+ by the original declaration object's identity; use the exact plugin and declared
44
+ method. Original declaration edits do not change admission or dispatch. Replace
45
+ the adapter when changing its selection. Existing `operation_N` tool names remain
46
+ entry-local, translated internally to Manage's selection-scoped opaque keys.
47
+ Close revokes admission, waits for actual work to drain, then releases the
48
+ selection's runtime references; policy closures retain any resources they capture.
49
+
40
50
  For a **dedicated local process**, replace the adapter creation above with:
41
51
 
42
52
  ```ts
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  import { environmentSecrets, redact } from "@lenso/engine/diagnostics";
38
38
 
39
39
  // src/adapter.ts
40
- import { createAgentTools, createManageAdapter } from "@lenso/manage";
40
+ import { createAgentTools, createManageSelection } from "@lenso/manage";
41
41
  import {
42
42
  boundedJson
43
43
  } from "@lenso/engine/operations";
@@ -100,19 +100,24 @@ function failure(code) {
100
100
  };
101
101
  }
102
102
  async function createMcpAdapter(options) {
103
+ const { running, plugins, operations, ...policies } = options;
104
+ const selection = createManageSelection({ running, plugins, operations });
105
+ try {
106
+ return await adapterForSelection(selection, policies);
107
+ } catch (error) {
108
+ selection.close();
109
+ throw error;
110
+ }
111
+ }
112
+ async function adapterForSelection(selection, options) {
103
113
  const maxInput = positive(options.maxInputBytes, 256 * 1024);
104
114
  const maxOutput = positive(options.maxOutputBytes, 1024 * 1024, 256);
105
115
  const maxCatalog = positive(options.maxCatalogBytes, 256 * 1024);
106
116
  const maxConcurrent = positive(options.maxConcurrentCalls, 4);
107
117
  const timeout = positive(options.requestTimeoutMs, 30000);
108
- const operations = Object.freeze([...options.operations]);
109
- const plugins = Object.freeze([...options.plugins]);
110
118
  if ([options.binding, options.canList, options.authorize].some((fn) => typeof fn !== "function"))
111
119
  throw new TypeError("MCP requires trusted binding, catalog and invocation policies.");
112
- const canonical = createManageAdapter({
113
- running: options.running,
114
- plugins,
115
- operations,
120
+ const canonical = selection.createAdapter({
116
121
  canList: () => true,
117
122
  binding: () => {
118
123
  throw new Error("Metadata adapter cannot invoke.");
@@ -126,7 +131,7 @@ async function createMcpAdapter(options) {
126
131
  const tools = agentTools.map((item, index) => {
127
132
  const operation = catalog[index];
128
133
  const tool = {
129
- name: item.name,
134
+ name: `operation_${index}`,
130
135
  title: `${operation.pluginId}.${operation.method}`,
131
136
  description: [
132
137
  item.description,
@@ -149,6 +154,7 @@ async function createMcpAdapter(options) {
149
154
  return tool;
150
155
  });
151
156
  boundedJson({ tools }, maxCatalog);
157
+ const toolByKey = new Map(catalog.map((operation, index) => [operation.key, tools[index]]));
152
158
  let closed = false;
153
159
  let closePromise;
154
160
  const pending = new Set;
@@ -196,12 +202,18 @@ async function createMcpAdapter(options) {
196
202
  throw new McpError(ErrorCode.InternalError, "Adapter is busy.");
197
203
  try {
198
204
  return await tracked(request, async (context) => {
199
- const visible = [];
200
- for (const [index, operation] of operations.entries()) {
201
- if (await options.canList(operation, context) === true)
202
- visible.push(tools[index]);
203
- context.signal.throwIfAborted();
204
- }
205
+ const scoped = selection.createAdapter({
206
+ maxOutputBytes: maxCatalog,
207
+ binding: () => {
208
+ throw new Error("Metadata adapter cannot invoke.");
209
+ },
210
+ canList: async (operation) => {
211
+ const visible = await options.canList(operation, context);
212
+ context.signal.throwIfAborted();
213
+ return visible;
214
+ }
215
+ });
216
+ const visible = (await scoped.catalog()).map((operation) => toolByKey.get(operation.key));
205
217
  return JSON.parse(boundedJson({ tools: visible }, maxCatalog));
206
218
  });
207
219
  } catch {
@@ -210,7 +222,7 @@ async function createMcpAdapter(options) {
210
222
  },
211
223
  async callTool(name, input, request) {
212
224
  const match = /^operation_(0|[1-9][0-9]*)$/.exec(name);
213
- if (!match || !operations[Number(match[1])])
225
+ if (!match || !catalog[Number(match[1])])
214
226
  throw new McpError(ErrorCode.InvalidParams, "Unknown tool.");
215
227
  if (closed)
216
228
  return failure("adapter-closed");
@@ -225,10 +237,7 @@ async function createMcpAdapter(options) {
225
237
  }
226
238
  try {
227
239
  const value = await tracked(request, async (context) => {
228
- const scoped = createManageAdapter({
229
- running: options.running,
230
- plugins,
231
- operations,
240
+ const scoped = selection.createAdapter({
232
241
  maxOutputBytes: maxOutput,
233
242
  canList: async (operation) => {
234
243
  const visible = await options.canList(operation, context);
@@ -245,13 +254,13 @@ async function createMcpAdapter(options) {
245
254
  };
246
255
  }
247
256
  });
248
- const result = await scoped.invokeEntry(name, input);
257
+ const result = await scoped.invokeEntry(catalog[Number(match[1])].key, input);
249
258
  context.signal.throwIfAborted();
250
259
  return result;
251
260
  });
252
261
  return { content: [{ type: "text", text: boundedJson(value, maxOutput) }] };
253
262
  } catch (error) {
254
- return failure(error instanceof EngineError ? error.diagnostic.code : request.signal.aborted ? "request-cancelled" : "invocation-failed");
263
+ return failure(error instanceof EngineError ? error.diagnostic.code : "invocation-failed");
255
264
  }
256
265
  },
257
266
  close() {
@@ -260,7 +269,9 @@ async function createMcpAdapter(options) {
260
269
  closed = true;
261
270
  for (const controller of controllers)
262
271
  controller.abort();
263
- closePromise = Promise.allSettled(pending).then(() => {});
272
+ closePromise = Promise.allSettled(pending).then(() => {
273
+ selection.close();
274
+ });
264
275
  return closePromise;
265
276
  }
266
277
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenso/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,10 +24,10 @@
24
24
  "test": "bun test"
25
25
  },
26
26
  "dependencies": {
27
- "@lenso/cli": "^0.19.2",
28
- "@lenso/core": "^0.3.0",
29
- "@lenso/engine": "^0.4.0",
30
- "@lenso/manage": "^0.4.0",
27
+ "@lenso/cli": "^0.20.1",
28
+ "@lenso/core": "^0.3.1",
29
+ "@lenso/engine": "^0.6.0",
30
+ "@lenso/manage": "^0.5.0",
31
31
  "@modelcontextprotocol/sdk": "1.32.1",
32
32
  "zod": "4.6.5"
33
33
  }