MCP and coding agents
Expose a reviewed set of existing operations to a local runtime agent, and give coding agents version-matched development references.
A coding agent edits source and uses development tools. A runtime agent invokes explicitly exposed business operations. Loading a development skill gives neither identity nor business permission, and does not start an MCP server.
This page follows the published @lenso/mcp, @lenso/core and @lenso/manage 0.2.0 with CLI 0.19.0, audited against the MCP implementation and CLI development guide at 549b987. Use the coherent pinned versions in installation, rather than older installed declarations or floating tags.
Expose existing operations through local stdio
@lenso/mcp exports serveStdio. It uses the official MCP SDK 1.32.1 and delegates to the CLI operation boundary. It does not expose runtime methods automatically, implement business handlers, load Engine config, or open a remote listener.
The existing Tasks MCP entry fixes its root and four authorized operations: submit, query, cancel and retry. report remains a CLI declaration and is not in its MCP selection. A host wanting only query access can create this narrower trusted entry at examples/tasks/src/mcp-read.ts:
import { serveStdio } from "@lenso/mcp";import { fileURLToPath } from "node:url";
const adapter = await serveStdio({ root: fileURLToPath(new URL("../", import.meta.url)), allow: [ { pluginId: "tasks", method: "query" }, ],});// For an explicit host shutdown path, await adapter.close().Configure the MCP host to launch bun <absolute-path-to-entry> with the same verified TASK_SESSION, trusted TASK_AUTH_SOURCE_MODULE, database and queue settings used by that application. The absolute path belongs to host configuration; do not put a developer's personal path into shared documentation/configuration.
The root and allowlist are trusted launch decisions. Tool arguments cannot select another module, application, method or shell. Changing source/allowlist requires a fresh process because normal module caching applies. The example starts no task worker.
Require declarations and representable schemas
The app's trusted lenso.config.ts declares CLI operations and, optionally, a separate named mcpOperations export, bound to exact installed plugin objects and shared input schemas. MCP selects mcpOperations ?? operations ?? [], then applies the trusted launch allow list. An explicit mcpOperations = [] disables fallback; an operation selected only for CLI cannot be added through tool arguments. Startup loads the canonical application with readApplication and describes selected operations without app setup. Trusted imports and schema converters still execute; discovery is not a sandbox.
Duplicate or absent bindings reject startup. Input must have a converted object JSON Schema representable by the SDK. runtime-validation-only, scalar input or a missing converter cannot be replaced with a guessed empty schema. The method description and semantic metadata come from the canonical operation.
Tools are named operation_0, operation_1, etc. in allowlist order. Discover with tools/list; do not persist an index across host configuration changes. Titles identify the canonical plugin/method, and _meta["lenso/operation"] retains source/schema/semantic information.
Preserve identity and authorization
Each admitted tool call validates through the shared schema, starts a fresh app, invokes the existing method with its service as this, and awaits app cleanup. Application policies still authenticate and authorize. An allowlist controls exposure; it grants no owner, tenant or administrator authority.
All requests in one stdio process use the trusted launch identity. There is no remote per-request login or serialized-actor adapter. Separate local identities require separate authorized processes. Never accept actors, session credentials or trusted module paths from business JSON.
Notes and Tasks demonstrate the shared boundary:
- Notes operations obtain audience-specific actors from trusted session evidence and call the same owner-enforcing Notes service.
- Tasks authorized service checks durable ownership before private query/cancel/retry/report access. A job ID is not authority.
The Notes default MCP entry selects only list, read and remove on notes-operations, not create/update, session issuance or binary Files. Its trusted launch binding reads NOTES_MCP_SESSION, independently of CLI's NOTES_SESSION. Do not broaden an allowlist merely because a service method exists.
Bind trusted context at the entry
An operation declaring context: true takes trusted context as its real service method's second parameter. Business JSON remains the first, schema-validated argument. MCP's optional serveStdio({ binding }) receives (operation, validatedInput, running) after input validation and full app setup, once per admitted call. It may return context and trusted confirm/approve callbacks. This is the public field name; there is no invocationContext option.
The real Notes entry uses:
import { serveStdio } from "@lenso/mcp";import { fileURLToPath } from "node:url";
await serveStdio({ root: fileURLToPath(new URL("../", import.meta.url)), allow: [ { pluginId: "notes-operations", method: "list" }, { pluginId: "notes-operations", method: "read" }, { pluginId: "notes-operations", method: "remove" }, ], binding: () => ({ context: { evidence: process.env.NOTES_MCP_SESSION ?? null }, }),});Place this entry in examples/notes/src, as in the existing Notes MCP source. The Notes operation wrapper obtains its audience-specific actor from this evidence and the shared service still revalidates the session and owner. A context object or its TypeScript shape does not itself prove identity.
MCP deliberately clears the config's CLI operationBinding; it never borrows that binding when its own is absent. Missing required context fails with missing-context-binding. For declarations requiring confirmation or approval, missing/false trusted callbacks fail with confirmation-required or approval-required. Input flags, a fabricated actor and destructive/retry metadata cannot satisfy these gates. Ordinary single-input operations, such as the existing Tasks service, remain supported without a context binding. See CLI and Auth.
Tools for an already running application
@lenso/manage/agent provides SDK-independent tools for an explicitly configured ManageAdapter:
import { createAgentTools } from "@lenso/manage/agent";import type { ManageAdapter } from "@lenso/manage";
export async function toolsFor(adapter: ManageAdapter) { return createAgentTools(adapter);}The adapter borrows an existing RunningApp; it does not start or stop it. Its mandatory canList filters the current caller's catalog, and its per-call binding supplies trusted context. Tools contain name, description, object inputSchema and invoke(input); invocation rechecks admission. Missing/non-object JSON Schema converters reject tool creation. Their adapter-scoped catalog keys are not durable receipts.
This helper neither registers an agent SDK nor opens an MCP server. Connect the descriptions/invocation functions through your own agent host, preserving identity isolation, confirmation/approval and shared service authorization. Manage covers exact instance selection, adapters and HTTP mounting.
Treat output and hints as contracts
Successful content is one text block containing deterministic redacted JSON. Unsupported output, including undefined, cycles and nonfinite/nonplain values, fails serialization. Business errors return isError: true with safe code/phase diagnostics, not raw input, exception messages or stacks. outputDescription is prose; it is not an output schema.
MCP hints use conservative defaults. Declared effect, destructive, retry and cancellation metadata describe intent; they do not grant permission, idempotency, abort propagation or rollback. Application CliError messages must themselves be safe.
Default limits are a 1 MiB SDK incoming read buffer, 256 KiB serialized business input and 1 MiB redacted output. Trusted maxFrameBytes, maxInputBytes and maxOutputBytes options adjust these budgets; output must allow at least the 91-byte safe fallback. They do not bound arbitrary allocations inside trusted code.
Cancellation and shutdown
Only one business call is admitted at a time. Another call gets adapter-busy; there is no request queue. The adapter checks SDK cancellation before and after the awaited shared invocation. It does not automatically pass the SDK signal to the operation or binding. An application's context may contain its own signal, but that does not turn MCP request cancellation into automatic business abort, durable-job cancellation or rollback. A cancelled request can discard its response while admitted work and cleanup continue.
close(), stdin EOF, SIGINT and SIGTERM stop admission, then drain the admitted call and cleanup before closing transport. There is no forced handler termination or cleanup deadline. Detached work still needs explicit application ownership.
MCP cancellation is not tasks.cancel. Connection loss, timeout or an unread response does not prove a business write failed. Query authorized business/task state before deciding whether to repeat it. See durable tasks.
SDK protocol frames are the intended stdout output; ordinary console methods are redirected to stderr during startup/call/drain. Direct process.stdout.write, native logging or unsafe trusted code can still corrupt the stream or leak secrets. This adapter is not a sandbox. Keep discovery metadata, URLs and free-form logs safe.
Equip a coding agent
Give the coding agent the application's instructions, manifest/lockfile and the source-matched public references. Its development path is:
- Identify the host and actual installed exports; find the exact assembly/plugin/schema being changed.
- Use the CLI operation contract to inspect existing declarations before editing shared schemas/services.
- Preserve exact instance dependencies, resource ownership and verified service authorization. Keep business implementations ordinary async.
- Run the application's focused typecheck/tests and, when authorized, the existing call entry. Rebuild changed source packages before consuming
dist; avoid internal source imports.
The source revision now includes project-local lenso-develop and lenso-diagnose. They cover consumer development and focused diagnosis, including current Manage/MCP boundaries. They are committed repository references; package file lists do not establish automatic npm skill installation. Review separately supplied skills and match their references to installed artifacts, since repository main can advance.
Development instructions grant no identity, business permission or release authority. Optional Manage tools require their explicit caller policy and binding; no Observe query CLI or generic management command is supplied. See testing and troubleshooting for the supported diagnostic loop.
For adapter-busy, wait for the existing call to drain. For discovery failure, inspect the binding/converter rather than bypassing checks. For authorization denial, fix verified evidence or policy. The real stdio tests cover official SDK discovery, lifecycle, redaction, bounds, cancellation and concurrency. The baseline does not advertise resources, prompts, tasks, elicitation or newer SDK-v2-only protocol features.