logicsrc/packages/cli/src/mcp.ts
Anthony Ettinger 8951e824d5
logicsrc CLI 0.2.0: every LogicSRC tool is a word after logicsrc (#157)
OpenContext, OpenCreds, OpenOntology and OpenPRD were already registered
under the umbrella. Three were not:

- `logicsrc openmcp …`: OpenMCP lives in its own repository, so it is a
  dependency (@logicsrc/openmcp ^0.3.1, which exports ./cli for this) and
  every argument goes untouched to the same main the standalone binary runs.
  Imported on first use, because the catalog is node:sqlite (Node 24) while
  the rest of the CLI runs on 18; below the floor that one word says so and
  offers the standalone installer, which brings its own Node.
- `logicsrc openspec <anything else>`: import, export and change stay ours;
  any other word (init, list, validate, archive, show) runs OpenSpec.dev's
  own CLI (@fission-ai/openspec) as the group's default subcommand, flags
  intact. One command for a repo in compatibility mode, and the OpenSpec.dev
  half is upstream itself rather than a copy that would drift.
- `logicsrc mcp`: the LogicSRC MCP server (@profullstack/logicsrc-mcp) over
  stdio, spawned as a child because it owns the process's stdio. build:cli
  now builds that workspace.

README gets a table of every word and the standalone name it mirrors; the
OpenMCP spec page and doc mention the umbrella form.


Claude-Session: https://claude.ai/code/session_01Qh2dieNyPZ4Hx5g3XNJ1Eo

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:16:40 -07:00

50 lines
2.1 KiB
TypeScript

import { spawn } from "node:child_process";
import { createRequire } from "node:module";
import type { Command } from "commander";
/**
* `logicsrc mcp`
*
* The LogicSRC MCP server (`@profullstack/logicsrc-mcp`, the standalone
* `logicsrc-mcp` binary): schemas, prompts, validators, OpenOntology and
* OpenPRD over stdio, for an agent client's config. It is the last LogicSRC
* command that was only reachable under its own name; now every one of them
* is a word after `logicsrc`.
*
* Spawned as a child on stdio rather than imported, because the server owns
* the process's stdin and stdout for the lifetime of the session, and that is
* not something a subcommand should do inside the CLI process that also
* prints help and errors.
*/
export const LOGICSRC_MCP_PACKAGE = "@profullstack/logicsrc-mcp";
/** The server's entry, which is also its bin. */
export function logicsrcMcpBin(resolveEntry: (id: string) => string = createRequire(import.meta.url).resolve): string {
return resolveEntry(LOGICSRC_MCP_PACKAGE);
}
export function runLogicsrcMcp(args: string[], bin: string = logicsrcMcpBin()): Promise<number> {
return new Promise((resolve) => {
const child = spawn(process.execPath, [bin, ...args], { stdio: "inherit" });
child.on("error", (error) => {
console.error(`Could not start the LogicSRC MCP server at ${bin}: ${error.message}`);
resolve(1);
});
child.on("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
});
}
export function registerMcpCommands(program: Command, run: (args: string[]) => Promise<number> = runLogicsrcMcp): void {
program
.command("mcp")
.description(
"Run the LogicSRC MCP server over stdio: schemas, prompts, validators, OpenOntology and OpenPRD for an " +
"agent client. The same server as the standalone `logicsrc-mcp` command.",
)
.argument("[args...]", "Arguments for logicsrc-mcp, exactly as the standalone command takes them")
.allowUnknownOption()
.passThroughOptions()
.action(async (args: string[]) => {
process.exitCode = await run(args);
});
}