@lenso/mcp 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +150 -2
- package/dist/index.d.ts +20 -0
- package/dist/index.js +243 -0
- package/package.json +33 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LioRael
|
|
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
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Local MCP adapter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@lenso/mcp` is an optional local stdio adapter for **existing, explicitly
|
|
4
|
+
declared Lenso operations**. It uses the official MCP SDK's `Server` and
|
|
5
|
+
`StdioServerTransport`. It does not start a remote server, accept credentials,
|
|
6
|
+
implement business handlers, load Engine config, or expose a shell.
|
|
7
|
+
|
|
8
|
+
## Trusted launch configuration
|
|
9
|
+
|
|
10
|
+
Install the optional package in the application and create a dedicated entry:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { serveStdio } from "@lenso/mcp";
|
|
14
|
+
|
|
15
|
+
const adapter = await serveStdio({
|
|
16
|
+
root: import.meta.dir,
|
|
17
|
+
allow: [{ pluginId: "greeting", method: "greet" }],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// For explicit host shutdown, await adapter.close().
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Configure an MCP host to launch `bun /absolute/path/to/mcp.ts`. The application
|
|
24
|
+
root and allowlist belong to trusted host configuration, not model/tool input.
|
|
25
|
+
There is deliberately no general-purpose command-line operation selector or
|
|
26
|
+
dynamic config-module loader. Use a fresh process when changing source or the
|
|
27
|
+
allowlist: normal trusted module caching still applies.
|
|
28
|
+
|
|
29
|
+
Startup calls `inspect(root)` from `@lenso/cli` without application setup. Every
|
|
30
|
+
allowlisted `(pluginId, method)` must already be declared. Duplicate, absent,
|
|
31
|
+
runtime-validation-only, and non-object input schemas reject startup before
|
|
32
|
+
protocol readiness. The adapter preserves Engine's converted input schema; it
|
|
33
|
+
does not invent an empty schema or infer one from runtime service methods.
|
|
34
|
+
Schemas must also satisfy the SDK's tool representation.
|
|
35
|
+
|
|
36
|
+
Tool names are `operation_0`, `operation_1`, etc., in allowlist order. Their
|
|
37
|
+
titles identify the canonical plugin/method. Clients should discover names
|
|
38
|
+
through `tools/list`, not persist an index across launch configuration changes.
|
|
39
|
+
Only those names resolve to fixed bindings; business arguments cannot select
|
|
40
|
+
the root, module, method, or shell. Applications still own the meaning and
|
|
41
|
+
safety of their business input.
|
|
42
|
+
|
|
43
|
+
## Invocation, authorization, and output
|
|
44
|
+
|
|
45
|
+
Each call delegates to `call(root, pluginId, method, input)` from `@lenso/cli`. The
|
|
46
|
+
existing shared Standard Schema validator runs before setup; the existing
|
|
47
|
+
service method runs with its service as `this`; CLI awaits app shutdown,
|
|
48
|
+
including ordered business/cleanup diagnostics. No alternate execution graph,
|
|
49
|
+
retry loop, HTTP actor, or authorization bypass exists here. Service
|
|
50
|
+
authorization must remain in the application. A host allowlist is additional
|
|
51
|
+
exposure policy, **not permission to bypass service authorization**.
|
|
52
|
+
|
|
53
|
+
All requests in one stdio process use the trusted launch environment's identity.
|
|
54
|
+
There is no per-request remote login or serialized-actor adapter. Run separate
|
|
55
|
+
processes with separately authorized credentials when local callers require
|
|
56
|
+
identity isolation. A developer who can change application code/config or use
|
|
57
|
+
the raw DB has administrative authority outside the tool surface; this adapter
|
|
58
|
+
does not grant that authority to its client. Remote credentials, audiences and
|
|
59
|
+
scopes require a separately designed trusted ingress, not this stdio entry.
|
|
60
|
+
|
|
61
|
+
Successful tool content contains one text block with Engine's deterministic,
|
|
62
|
+
redacted JSON result. No output schema is fabricated. Undefined, cyclic,
|
|
63
|
+
nonfinite, and nonplain output fails through the existing serialization policy.
|
|
64
|
+
Business failures use `isError: true` with safe Engine/CLI diagnostic JSON,
|
|
65
|
+
without raw input, arbitrary exception text, or stacks. Unknown tool names
|
|
66
|
+
produce a protocol `InvalidParams` error; malformed messages are SDK-owned.
|
|
67
|
+
Explicit application `CliError` messages must themselves be safe.
|
|
68
|
+
|
|
69
|
+
Descriptions come from canonical operation metadata. MCP hints default
|
|
70
|
+
conservatively: read-only only for an explicit read effect, destructive unless
|
|
71
|
+
explicitly false, idempotent only for declared safe retry, open-world true.
|
|
72
|
+
Hints are descriptive, not authorization or execution guarantees. Output
|
|
73
|
+
descriptions are included when declared. Cancellation is always request-only
|
|
74
|
+
at this adapter boundary, even if application metadata says otherwise.
|
|
75
|
+
The full canonical description, including instance, source, schema availability
|
|
76
|
+
and semantic metadata, is retained in `_meta["lenso/operation"]`.
|
|
77
|
+
|
|
78
|
+
## Limits and shutdown
|
|
79
|
+
|
|
80
|
+
Defaults are a 1 MiB SDK incoming read buffer, 256 KiB serialized business
|
|
81
|
+
input, and 1 MiB redacted serialized result. Trusted options `maxFrameBytes`,
|
|
82
|
+
`maxInputBytes`, and `maxOutputBytes` must be positive safe integers. These
|
|
83
|
+
bound incoming framing and business payloads, not arbitrary allocations inside
|
|
84
|
+
trusted application code or tool-discovery metadata.
|
|
85
|
+
The output budget also bounds failed tool content. Oversized diagnostics retain
|
|
86
|
+
their code and phase with `truncated: true`, omitting paths, details, causes and
|
|
87
|
+
source to fit the limit. If even that header cannot fit, a fixed
|
|
88
|
+
`output-too-large` diagnostic is returned. `maxOutputBytes` must be at least 91
|
|
89
|
+
bytes so that fallback always fits. Serialization failures likewise use a safe
|
|
90
|
+
bounded diagnostic, never arbitrary error text.
|
|
91
|
+
|
|
92
|
+
Only one business call is admitted at a time; concurrent calls fail with
|
|
93
|
+
`adapter-busy`, without a queue. The adapter checks SDK request cancellation
|
|
94
|
+
before and after the awaited CLI call. The existing service API has no signal:
|
|
95
|
+
cancelling a request does **not** stop in-flight service work, undo effects, or
|
|
96
|
+
promise rollback. The client/SDK may discard the response. The adapter still
|
|
97
|
+
awaits the call and cleanup before admitting another operation.
|
|
98
|
+
|
|
99
|
+
`close()`, stdin EOF, SIGINT, and SIGTERM stop admission and drain the admitted
|
|
100
|
+
call before closing transport and restoring console. No forced-termination or
|
|
101
|
+
cleanup timeout promise is made. Hanging application work can hang shutdown;
|
|
102
|
+
a host that force-kills the process cannot rely on cleanup completion.
|
|
103
|
+
Detached tasks and acquired resources remain application-owned.
|
|
104
|
+
|
|
105
|
+
## stdout and trust
|
|
106
|
+
|
|
107
|
+
SDK protocol messages are the adapter's only stdout output. During startup,
|
|
108
|
+
operation execution, and drain, console methods (including Bun's
|
|
109
|
+
`console.write`) route to stderr. Common logging methods use the existing
|
|
110
|
+
redaction policy and omit direct Error text. This is a **trusted in-process
|
|
111
|
+
adapter, not a sandbox**: direct `process.stdout.write`, native logging,
|
|
112
|
+
captured pre-start console references, or deliberately unsafe code can still
|
|
113
|
+
corrupt protocol output or disclose secrets. Redaction cannot identify every
|
|
114
|
+
secret under innocuous keys. Do not embed secrets in schema constraints or
|
|
115
|
+
metadata, and do not print them.
|
|
116
|
+
|
|
117
|
+
## SDK and specification verification
|
|
118
|
+
|
|
119
|
+
Primary sources checked for this implementation:
|
|
120
|
+
|
|
121
|
+
- [npm registry: official SDK latest 1.x](https://registry.npmjs.org/@modelcontextprotocol/sdk/latest),
|
|
122
|
+
pinned here to `1.32.1` (not a floating range).
|
|
123
|
+
- [Official SDK source, v1.x](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x):
|
|
124
|
+
its protocol latest constant is `2025-11-25`; the SDK owns negotiation.
|
|
125
|
+
- [Current MCP specification](https://modelcontextprotocol.io/specification/latest)
|
|
126
|
+
resolves to `2026-07-28`. The current SDK v2 uses split packages.
|
|
127
|
+
- [2025-11-25 stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)
|
|
128
|
+
and [tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
|
|
129
|
+
|
|
130
|
+
This package deliberately uses the mature official monolithic v1 SDK. It does
|
|
131
|
+
**not** claim implementation of the newer 2026-07-28 stateless protocol or
|
|
132
|
+
v2-only features. No resources, prompts, tasks, elicitation, remote auth, or
|
|
133
|
+
credentials are advertised.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
The integration owner installs dependencies and owns the workspace lockfile.
|
|
138
|
+
Rebuild `@lenso/core`, `@lenso/engine`, and `@lenso/cli` before consuming their exports.
|
|
139
|
+
Then run from this package:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
bun run typecheck
|
|
143
|
+
bun run build
|
|
144
|
+
bun test
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Tests launch a real subprocess and communicate using the official SDK client
|
|
148
|
+
and stdio transport. They cover discovery without setup, allowlisting, shared
|
|
149
|
+
validation, service lifecycle, redaction, safe authorization/runtime
|
|
150
|
+
diagnostics, unsupported output, payload limits, cancellation, and rejected
|
|
151
|
+
concurrent requests.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type OperationBinding } from "@lenso/engine/application";
|
|
2
|
+
export interface StdioOptions {
|
|
3
|
+
/** Trusted launch configuration, never supplied by an MCP tool. */
|
|
4
|
+
root: string;
|
|
5
|
+
allow: readonly {
|
|
6
|
+
pluginId: string;
|
|
7
|
+
method: string;
|
|
8
|
+
}[];
|
|
9
|
+
maxInputBytes?: number;
|
|
10
|
+
maxOutputBytes?: number;
|
|
11
|
+
maxFrameBytes?: number;
|
|
12
|
+
binding?: OperationBinding;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Owns a dedicated stdio process. Tools translate only to CLI call; no app or
|
|
16
|
+
* service lifecycle is implemented here. Close drains the admitted call.
|
|
17
|
+
*/
|
|
18
|
+
export declare function serveStdio(options: StdioOptions): Promise<{
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/index.ts
|
|
3
|
+
import { Console } from "console";
|
|
4
|
+
import { join, resolve } from "path";
|
|
5
|
+
import { Writable } from "stream";
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import {
|
|
9
|
+
CallToolRequestSchema,
|
|
10
|
+
ErrorCode,
|
|
11
|
+
ListToolsRequestSchema,
|
|
12
|
+
McpError,
|
|
13
|
+
ToolSchema
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import { invoke, CliError, diagnostic } from "@lenso/cli";
|
|
16
|
+
import { readApplication } from "@lenso/engine/application";
|
|
17
|
+
import {
|
|
18
|
+
describeOperation,
|
|
19
|
+
redactOperationDescription,
|
|
20
|
+
validateOperations
|
|
21
|
+
} from "@lenso/engine/operations";
|
|
22
|
+
import { environmentSecrets, redact, stableJson } from "@lenso/engine/diagnostics";
|
|
23
|
+
var serving = false;
|
|
24
|
+
function failure(code, phase, message) {
|
|
25
|
+
return new CliError({ code, phase, message });
|
|
26
|
+
}
|
|
27
|
+
function limit(value, fallback) {
|
|
28
|
+
const bytes = value ?? fallback;
|
|
29
|
+
if (!Number.isSafeInteger(bytes) || bytes < 1)
|
|
30
|
+
throw failure("invalid-arguments", "arguments", "Limits must be positive safe integers.");
|
|
31
|
+
return bytes;
|
|
32
|
+
}
|
|
33
|
+
function result(data, maxBytes) {
|
|
34
|
+
stableJson(data);
|
|
35
|
+
const text = stableJson(redact(data, environmentSecrets()));
|
|
36
|
+
if (Buffer.byteLength(text) > maxBytes)
|
|
37
|
+
throw failure("output-too-large", "output", "Operation output exceeds the host limit.");
|
|
38
|
+
return { content: [{ type: "text", text }] };
|
|
39
|
+
}
|
|
40
|
+
var oversizedDiagnostic = stableJson({
|
|
41
|
+
code: "output-too-large",
|
|
42
|
+
phase: "output",
|
|
43
|
+
message: "Diagnostic exceeds the host limit."
|
|
44
|
+
});
|
|
45
|
+
function errorResult(error, maxBytes) {
|
|
46
|
+
let text;
|
|
47
|
+
try {
|
|
48
|
+
const detail = diagnostic(error);
|
|
49
|
+
text = stableJson(redact(detail, environmentSecrets()));
|
|
50
|
+
if (Buffer.byteLength(text) > maxBytes) {
|
|
51
|
+
text = stableJson(redact({
|
|
52
|
+
code: detail.code,
|
|
53
|
+
phase: detail.phase,
|
|
54
|
+
message: "Diagnostic truncated to the host output limit.",
|
|
55
|
+
truncated: true
|
|
56
|
+
}, environmentSecrets()));
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
text = stableJson({
|
|
60
|
+
code: "serialization-failed",
|
|
61
|
+
phase: "output",
|
|
62
|
+
message: "Diagnostic is not JSON data."
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (Buffer.byteLength(text) > maxBytes)
|
|
66
|
+
text = oversizedDiagnostic;
|
|
67
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
68
|
+
}
|
|
69
|
+
async function serveStdio(options) {
|
|
70
|
+
if (serving)
|
|
71
|
+
throw failure("invalid-arguments", "arguments", "Only one stdio adapter may own this process.");
|
|
72
|
+
const root = resolve(options.root);
|
|
73
|
+
const maxInput = limit(options.maxInputBytes, 256 * 1024);
|
|
74
|
+
const maxOutput = limit(options.maxOutputBytes, 1024 * 1024);
|
|
75
|
+
if (maxOutput < Buffer.byteLength(oversizedDiagnostic))
|
|
76
|
+
throw failure("invalid-arguments", "arguments", "Output limit must fit a bounded diagnostic.");
|
|
77
|
+
const maxFrame = limit(options.maxFrameBytes, 1024 * 1024);
|
|
78
|
+
serving = true;
|
|
79
|
+
const originalConsole = globalThis.console;
|
|
80
|
+
const logs = new Writable({
|
|
81
|
+
write(chunk, _encoding, done) {
|
|
82
|
+
process.stderr.write(String(redact(chunk.toString(), environmentSecrets())), done);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
globalThis.console = Object.assign(new Console({ stdout: logs, stderr: logs }), {
|
|
86
|
+
write(...values) {
|
|
87
|
+
const text = values.map((value) => typeof value === "string" ? value : Buffer.from(ArrayBuffer.isView(value) ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength) : new Uint8Array(value)).toString()).join("");
|
|
88
|
+
logs.write(text);
|
|
89
|
+
return Buffer.byteLength(text);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
for (const level of [
|
|
93
|
+
"log",
|
|
94
|
+
"info",
|
|
95
|
+
"warn",
|
|
96
|
+
"error",
|
|
97
|
+
"debug",
|
|
98
|
+
"dir",
|
|
99
|
+
"dirxml",
|
|
100
|
+
"table",
|
|
101
|
+
"trace"
|
|
102
|
+
]) {
|
|
103
|
+
globalThis.console[level] = (...values) => {
|
|
104
|
+
const safe = values.map((value) => value instanceof Error ? "[Application error text omitted]" : redact(value, environmentSecrets()));
|
|
105
|
+
logs.write(`${safe.map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(" ")}
|
|
106
|
+
`);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
globalThis.console.assert = (condition, ...values) => {
|
|
110
|
+
if (!condition)
|
|
111
|
+
globalThis.console.error("Assertion failed:", ...values);
|
|
112
|
+
};
|
|
113
|
+
let server;
|
|
114
|
+
let active;
|
|
115
|
+
let closing = false;
|
|
116
|
+
let closePromise;
|
|
117
|
+
const restore = () => {
|
|
118
|
+
globalThis.console = originalConsole;
|
|
119
|
+
serving = false;
|
|
120
|
+
};
|
|
121
|
+
const close = () => {
|
|
122
|
+
if (closePromise)
|
|
123
|
+
return closePromise;
|
|
124
|
+
closing = true;
|
|
125
|
+
closePromise = (async () => {
|
|
126
|
+
try {
|
|
127
|
+
await active;
|
|
128
|
+
await server?.close();
|
|
129
|
+
} finally {
|
|
130
|
+
process.stdin.off("end", onEnd);
|
|
131
|
+
process.off("SIGINT", onEnd);
|
|
132
|
+
process.off("SIGTERM", onEnd);
|
|
133
|
+
restore();
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
return closePromise;
|
|
137
|
+
};
|
|
138
|
+
const onEnd = () => {
|
|
139
|
+
close().catch(() => {
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
const { app, configPath } = await readApplication(root, join(root, "lenso.config.ts"));
|
|
145
|
+
const selected = validateOperations(app.plugins, app.mcpOperations ?? app.operations ?? []);
|
|
146
|
+
const tools = [];
|
|
147
|
+
const bindings = new Map;
|
|
148
|
+
const seen = new Set;
|
|
149
|
+
for (const allowed of options.allow) {
|
|
150
|
+
const key = stableJson([allowed.pluginId, allowed.method]);
|
|
151
|
+
if (seen.has(key))
|
|
152
|
+
throw failure("duplicate-operation", "discovery", "Duplicate MCP allowlist entry.");
|
|
153
|
+
seen.add(key);
|
|
154
|
+
const ref = selected.find((item) => item.plugin.id === allowed.pluginId && item.method === allowed.method);
|
|
155
|
+
const operation = ref ? redactOperationDescription(describeOperation(ref, configPath)) : undefined;
|
|
156
|
+
if (!operation)
|
|
157
|
+
throw failure("unknown-operation", "discovery", "MCP allowlist operation is not declared.");
|
|
158
|
+
const schema = operation.inputSchema;
|
|
159
|
+
if (!schema || schema.type !== "object")
|
|
160
|
+
throw failure("unsupported-input-schema", "discovery", "MCP tools require a convertible object input schema.");
|
|
161
|
+
stableJson(schema);
|
|
162
|
+
const name = `operation_${tools.length}`;
|
|
163
|
+
const tool = {
|
|
164
|
+
name,
|
|
165
|
+
title: `${operation.pluginId}.${operation.method}`,
|
|
166
|
+
description: [
|
|
167
|
+
operation.description,
|
|
168
|
+
...operation.outputDescription ? [`Output: ${operation.outputDescription}`] : [],
|
|
169
|
+
"Cancellation is request-only: in-flight work and cleanup are awaited; no rollback or automatic retry."
|
|
170
|
+
].join(`
|
|
171
|
+
`),
|
|
172
|
+
inputSchema: schema,
|
|
173
|
+
_meta: { "lenso/operation": operation },
|
|
174
|
+
execution: { taskSupport: "forbidden" },
|
|
175
|
+
annotations: {
|
|
176
|
+
readOnlyHint: operation.effect === "read",
|
|
177
|
+
destructiveHint: operation.destructive ?? true,
|
|
178
|
+
idempotentHint: operation.retry === "safe",
|
|
179
|
+
openWorldHint: true
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
if (!ToolSchema.safeParse(tool).success)
|
|
183
|
+
throw failure("unsupported-input-schema", "discovery", "Operation schema cannot be represented as an MCP tool.");
|
|
184
|
+
tools.push(tool);
|
|
185
|
+
bindings.set(name, { pluginId: allowed.pluginId, method: allowed.method });
|
|
186
|
+
}
|
|
187
|
+
server = new Server({ name: "lenso", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
188
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
189
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
190
|
+
const binding = bindings.get(request.params.name);
|
|
191
|
+
if (!binding)
|
|
192
|
+
throw new McpError(ErrorCode.InvalidParams, "Tool is not allowlisted.", {
|
|
193
|
+
code: "unknown-operation"
|
|
194
|
+
});
|
|
195
|
+
if (request.params.task)
|
|
196
|
+
throw new McpError(ErrorCode.InvalidParams, "Task-augmented calls are not supported.");
|
|
197
|
+
if (closing)
|
|
198
|
+
return errorResult(failure("adapter-closed", "invoke", "Adapter is shutting down."), maxOutput);
|
|
199
|
+
if (active)
|
|
200
|
+
return errorResult(failure("adapter-busy", "invoke", "One operation is already running; no request was queued."), maxOutput);
|
|
201
|
+
const execute = async () => {
|
|
202
|
+
try {
|
|
203
|
+
if (extra.signal.aborted)
|
|
204
|
+
throw failure("request-cancelled", "invoke", "Request cancelled before invocation.");
|
|
205
|
+
const input = request.params.arguments ?? {};
|
|
206
|
+
if (Buffer.byteLength(stableJson(input)) > maxInput)
|
|
207
|
+
throw failure("input-too-large", "input", "Operation input exceeds the host limit.");
|
|
208
|
+
const data = await invoke({ ...app, operations: selected, operationBinding: undefined }, binding.pluginId, binding.method, input, async (operation, validatedInput, running) => ({
|
|
209
|
+
...options.binding ? await options.binding(operation, validatedInput, running) : {},
|
|
210
|
+
maxOutputBytes: maxOutput
|
|
211
|
+
}));
|
|
212
|
+
if (extra.signal.aborted)
|
|
213
|
+
throw failure("request-cancelled", "invoke", "Request cancelled; operation may have completed. Cleanup was awaited.");
|
|
214
|
+
return result(data, maxOutput);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return errorResult(error, maxOutput);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
active = execute();
|
|
220
|
+
try {
|
|
221
|
+
return await active;
|
|
222
|
+
} finally {
|
|
223
|
+
active = undefined;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
server.onerror = () => {
|
|
227
|
+
process.stderr.write(`MCP protocol error (details omitted).
|
|
228
|
+
`);
|
|
229
|
+
};
|
|
230
|
+
server.onclose = onEnd;
|
|
231
|
+
process.stdin.once("end", onEnd);
|
|
232
|
+
process.on("SIGINT", onEnd);
|
|
233
|
+
process.on("SIGTERM", onEnd);
|
|
234
|
+
await server.connect(new StdioServerTransport(process.stdin, process.stdout, { maxBufferSize: maxFrame }));
|
|
235
|
+
return { close };
|
|
236
|
+
} catch (error) {
|
|
237
|
+
await close();
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
export {
|
|
242
|
+
serveStdio
|
|
243
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenso/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/LioRael/lenso.git",
|
|
8
|
+
"directory": "packages/mcp"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target bun --packages external && tsc -p tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
24
|
+
"test": "bun test"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lenso/cli": "^0.19.0",
|
|
28
|
+
"@lenso/engine": "^0.2.0",
|
|
29
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
30
|
+
"zod": "4.6.5"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@lenso/core": "^0.2.0"
|
|
34
|
+
}
|
|
35
|
+
}
|