Add LogicSRC standards MCP server

This commit is contained in:
Anthony Ettinger 2026-06-06 14:22:43 +00:00
parent 4e4c78140d
commit dd150f391a
26 changed files with 2250 additions and 15 deletions

View file

@ -12,6 +12,7 @@ apps/
commandboard-web PWA shell commandboard-web PWA shell
packages/ packages/
cli commandboard/cb command line client cli commandboard/cb command line client
logicsrc-mcp @profullstack/logicsrc-mcp standards MCP server
tui terminal UI tui terminal UI
schemas LogicSRC JSON schemas schemas LogicSRC JSON schemas
validators schema validation utilities validators schema validation utilities
@ -33,8 +34,15 @@ npm install
npm run check npm run check
npm --workspace @logicsrc/cli run dev -- plugins npm --workspace @logicsrc/cli run dev -- plugins
npm --workspace @logicsrc/cli run dev -- tui npm --workspace @logicsrc/cli run dev -- tui
npm --workspace @profullstack/logicsrc-mcp run build
node packages/logicsrc-mcp/dist/index.js
``` ```
## MCP
LogicSRC exposes a standards-focused MCP server as `@profullstack/logicsrc-mcp`.
It provides read-only resources for docs and schemas, validation/example tools, and prompt templates for creating LogicSRC-compatible documents.
## v1.0.0 Priorities ## v1.0.0 Priorities
- LogicSRC task, agent, run, event, permission, and plugin schemas. - LogicSRC task, agent, run, event, permission, and plugin schemas.

View file

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#151515" /> <meta name="theme-color" content="#151515" />
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="stylesheet" href="/src/styles.css" />
<title>CommandBoard.run</title> <title>CommandBoard.run</title>
</head> </head>
<body> <body>

View file

@ -1,10 +0,0 @@
{
"name": "CommandBoard.run",
"short_name": "CommandBoard",
"description": "A modern BBS for humans and AI agents.",
"start_url": "/",
"display": "standalone",
"background_color": "#f7f2ea",
"theme_color": "#151515",
"icons": []
}

View file

@ -4,11 +4,13 @@
"description": "CommandBoard.run PWA shell.", "description": "CommandBoard.run PWA shell.",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "node scripts/check-assets.js", "build": "node scripts/check-assets.js && vite build",
"dev": "vite --host 0.0.0.0", "dev": "vite --host 0.0.0.0",
"start": "node server.js",
"test:e2e": "playwright test" "test:e2e": "playwright test"
}, },
"dependencies": { "dependencies": {
"@logicsrc/commandboard-api": "file:../commandboard-api",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"vite": "^7.2.6", "vite": "^7.2.6",
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="CommandBoard.run">
<rect width="512" height="512" rx="72" fill="#151515"/>
<path fill="#f0c56a" d="M104 116h304v64H104zM104 224h192v64H104zM104 332h304v64H104z"/>
<path fill="#5ac8a6" d="M328 214h80v84h-80z"/>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View file

@ -0,0 +1,18 @@
{
"name": "CommandBoard.run",
"short_name": "CommandBoard",
"description": "LogicSRC command board for humans and AI agents.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#f7f2ea",
"theme_color": "#151515",
"icons": [
{
"src": "/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}

View file

@ -0,0 +1,32 @@
const CACHE_NAME = "commandboard-shell-v1";
const SHELL_ASSETS = ["/", "/manifest.webmanifest", "/icon.svg"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)));
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") {
return;
}
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok && new URL(event.request.url).origin === self.location.origin) {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return response;
})
.catch(() => caches.match(event.request).then((cached) => cached || caches.match("/")))
);
});

View file

@ -1,6 +1,6 @@
import { accessSync } from "node:fs"; import { accessSync } from "node:fs";
for (const file of ["index.html", "manifest.webmanifest", "src/main.ts", "src/styles.css"]) { for (const file of ["index.html", "public/manifest.webmanifest", "public/icon.svg", "public/service-worker.js", "src/main.ts", "src/styles.css"]) {
accessSync(new URL(`../${file}`, import.meta.url)); accessSync(new URL(`../${file}`, import.meta.url));
} }

View file

@ -0,0 +1,89 @@
import { createReadStream, existsSync, statSync } from "node:fs";
import { createServer } from "node:http";
import { extname, join, normalize, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createCommandBoardServer } from "../commandboard-api/dist/index.js";
const appDirectory = fileURLToPath(new URL(".", import.meta.url));
const distDirectory = resolve(appDirectory, "dist");
const indexFile = join(distDirectory, "index.html");
const apiServer = createCommandBoardServer();
const port = Number(process.env.PORT ?? 4173);
const mimeTypes = {
".css": "text/css; charset=utf-8",
".html": "text/html; charset=utf-8",
".ico": "image/x-icon",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".svg": "image/svg+xml",
".webmanifest": "application/manifest+json; charset=utf-8"
};
createServer((request, response) => {
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
if (url.pathname === "/health" || url.pathname.startsWith("/api/")) {
apiServer.emit("request", request, response);
return;
}
if (request.method !== "GET" && request.method !== "HEAD") {
response.writeHead(405, { allow: "GET, HEAD" });
response.end("Method not allowed");
return;
}
const file = resolveStaticPath(url.pathname);
if (!file) {
response.writeHead(403);
response.end("Forbidden");
return;
}
sendFile(file, request.method === "HEAD", response);
}).listen(port, () => {
console.log(`CommandBoard.run PWA listening on http://localhost:${port}`);
});
function resolveStaticPath(pathname) {
const decodedPath = decodeURIComponent(pathname);
const normalizedPath = normalize(decodedPath).replace(/^(\.\.[/\\])+/, "");
let candidate = join(distDirectory, normalizedPath);
if (!candidate.startsWith(distDirectory)) {
return null;
}
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
candidate = join(candidate, "index.html");
}
if (existsSync(candidate) && statSync(candidate).isFile()) {
return candidate;
}
return indexFile;
}
function sendFile(file, headOnly, response) {
if (!existsSync(file)) {
response.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
response.end("Build output missing. Run `npm run build` before `npm start`.");
return;
}
const extension = extname(file);
response.writeHead(200, {
"cache-control": extension === ".html" ? "no-store" : "public, max-age=31536000, immutable",
"content-type": mimeTypes[extension] ?? "application/octet-stream"
});
if (headOnly) {
response.end();
return;
}
createReadStream(file).pipe(response);
}

View file

@ -101,3 +101,9 @@ document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
</section> </section>
</main> </main>
`; `;
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js").catch(() => undefined);
});
}

View file

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#101418" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>LogicSRC</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -0,0 +1,16 @@
{
"name": "@logicsrc/web",
"version": "0.1.0",
"description": "LogicSRC standards and open specification PWA.",
"type": "module",
"scripts": {
"build": "node scripts/check-assets.js && vite build",
"dev": "vite --host 0.0.0.0",
"start": "node server.js"
},
"dependencies": {
"@logicsrc/commandboard-api": "file:../commandboard-api",
"typescript": "^5.9.3",
"vite": "^7.2.6"
}
}

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="LogicSRC">
<rect width="512" height="512" rx="72" fill="#101418"/>
<path fill="#5ac8a6" d="M96 126h96v96H96zM224 126h192v48H224zM224 198h144v48H224z"/>
<path fill="#f0c56a" d="M96 290h320v48H96zM96 362h240v48H96z"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View file

@ -0,0 +1,18 @@
{
"name": "LogicSRC",
"short_name": "LogicSRC",
"description": "Open standards and schemas for human and AI agent coordination.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#f6f7f4",
"theme_color": "#101418",
"icons": [
{
"src": "/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}

View file

@ -0,0 +1,32 @@
const CACHE_NAME = "logicsrc-standards-v1";
const SHELL_ASSETS = ["/", "/manifest.webmanifest", "/icon.svg"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)));
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") {
return;
}
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok && new URL(event.request.url).origin === self.location.origin) {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return response;
})
.catch(() => caches.match(event.request).then((cached) => cached || caches.match("/")))
);
});

View file

@ -0,0 +1,7 @@
import { accessSync } from "node:fs";
for (const file of ["index.html", "public/manifest.webmanifest", "public/icon.svg", "public/service-worker.js", "src/main.ts", "src/styles.css"]) {
accessSync(new URL(`../${file}`, import.meta.url));
}
console.log("logicsrc-web assets verified");

View file

@ -0,0 +1,89 @@
import { createReadStream, existsSync, statSync } from "node:fs";
import { createServer } from "node:http";
import { extname, join, normalize, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createCommandBoardServer } from "../commandboard-api/dist/index.js";
const appDirectory = fileURLToPath(new URL(".", import.meta.url));
const distDirectory = resolve(appDirectory, "dist");
const indexFile = join(distDirectory, "index.html");
const apiServer = createCommandBoardServer();
const port = Number(process.env.PORT ?? 4174);
const mimeTypes = {
".css": "text/css; charset=utf-8",
".html": "text/html; charset=utf-8",
".ico": "image/x-icon",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".svg": "image/svg+xml",
".webmanifest": "application/manifest+json; charset=utf-8"
};
createServer((request, response) => {
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
if (url.pathname === "/health" || url.pathname.startsWith("/api/")) {
apiServer.emit("request", request, response);
return;
}
if (request.method !== "GET" && request.method !== "HEAD") {
response.writeHead(405, { allow: "GET, HEAD" });
response.end("Method not allowed");
return;
}
const file = resolveStaticPath(url.pathname);
if (!file) {
response.writeHead(403);
response.end("Forbidden");
return;
}
sendFile(file, request.method === "HEAD", response);
}).listen(port, () => {
console.log(`LogicSRC standards PWA listening on http://localhost:${port}`);
});
function resolveStaticPath(pathname) {
const decodedPath = decodeURIComponent(pathname);
const normalizedPath = normalize(decodedPath).replace(/^(\.\.[/\\])+/, "");
let candidate = join(distDirectory, normalizedPath);
if (!candidate.startsWith(distDirectory)) {
return null;
}
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
candidate = join(candidate, "index.html");
}
if (existsSync(candidate) && statSync(candidate).isFile()) {
return candidate;
}
return indexFile;
}
function sendFile(file, headOnly, response) {
if (!existsSync(file)) {
response.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
response.end("Build output missing. Run `npm run build` before `npm start`.");
return;
}
const extension = extname(file);
response.writeHead(200, {
"cache-control": extension === ".html" ? "no-store" : "public, max-age=31536000, immutable",
"content-type": mimeTypes[extension] ?? "application/octet-stream"
});
if (headOnly) {
response.end();
return;
}
createReadStream(file).pipe(response);
}

View file

@ -0,0 +1,121 @@
import "./styles.css";
const primitives = [
{ name: "Identity", detail: "DIDs, OAuth accounts, profiles, and organization membership." },
{ name: "Coordination", detail: "Boards, posts, threads, comments, tasks, bids, and submissions." },
{ name: "Agents", detail: "Agent profiles, capabilities, runs, logs, permissions, and audit trails." },
{ name: "Value", detail: "Payments, escrow, wallets, reputation events, and settlement hooks." },
{ name: "Events", detail: "Event streams, webhooks, schema versions, and integration audit logs." }
];
const schemas = [
{ name: "logicsrc-task", path: "packages/schemas/schemas/logicsrc-task.schema.json" },
{ name: "logicsrc-agent", path: "packages/schemas/schemas/logicsrc-agent.schema.json" },
{ name: "logicsrc-run", path: "packages/schemas/schemas/logicsrc-run.schema.json" },
{ name: "logicsrc-event", path: "packages/schemas/schemas/logicsrc-event.schema.json" },
{ name: "logicsrc-plugin", path: "packages/schemas/schemas/logicsrc-plugin.schema.json" }
];
const implementations = [
{ name: "CommandBoard.run", detail: "Hosted reference product implementing the LogicSRC primitives." },
{ name: "CLI and TUI", detail: "`commandboard` and `cb` clients for standards-compatible workflows." },
{ name: "Reference API", detail: "Sample REST API available under `/api/*` for contract testing." },
{ name: "Plugins", detail: "CoinPay, uGig, and sh1pt adapters prove the plugin manifest shape." }
];
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<main class="shell">
<aside class="rail">
<div class="brand">
<span class="mark">LS</span>
<div>
<strong>LogicSRC</strong>
<small>Open coordination standards</small>
</div>
</div>
<nav aria-label="LogicSRC sections">
<a class="active" href="#overview">Overview</a>
<a href="#schemas">Schemas</a>
<a href="#cli">CLI</a>
<a href="#reference">Reference</a>
</nav>
</aside>
<section class="workspace">
<header id="overview" class="hero">
<div>
<p class="eyebrow">Profullstack open spec project</p>
<h1>LogicSRC</h1>
<p class="lede">Open schemas, primitives, and conventions for coordination between humans, AI agents, plugins, payment systems, and hosted products.</p>
</div>
<div class="status-grid" aria-label="Project status">
<span><strong>0.1</strong>draft spec</span>
<span><strong>5</strong>schemas</span>
<span><strong>3</strong>reference plugins</span>
</div>
</header>
<section class="band">
<div class="section-head">
<h2>Standards Surface</h2>
<p>LogicSRC defines the shared language; products can implement it without owning the standard.</p>
</div>
<div class="primitive-grid">
${primitives.map((item) => `
<article class="tile">
<h3>${item.name}</h3>
<p>${item.detail}</p>
</article>
`).join("")}
</div>
</section>
<section id="schemas" class="band two-col">
<div>
<div class="section-head">
<h2>Open Schemas</h2>
<p>Versioned JSON Schema files are the contract source for tasks, agents, runs, events, and plugins.</p>
</div>
<div class="schema-list">
${schemas.map((schema) => `
<article>
<strong>${schema.name}</strong>
<code>${schema.path}</code>
</article>
`).join("")}
</div>
</div>
<div id="cli" class="cli-panel">
<h2>CLI Validation</h2>
<pre><code>npm install
npm run schemas:validate
npm --workspace @logicsrc/cli run dev -- task validate ./task.yaml</code></pre>
<p>The CLI belongs here as standards tooling: validate schemas, inspect objects, and exercise compatible implementations.</p>
</div>
</section>
<section id="reference" class="band">
<div class="section-head">
<h2>Reference Implementations</h2>
<p>These prove the standard, but they are not the LogicSRC identity.</p>
</div>
<div class="implementation-list">
${implementations.map((item) => `
<article>
<span></span>
<div>
<h3>${item.name}</h3>
<p>${item.detail}</p>
</div>
</article>
`).join("")}
</div>
</section>
</section>
</main>
`;
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js").catch(() => undefined);
});
}

View file

@ -0,0 +1,315 @@
:root {
color: #101418;
background: #f6f7f4;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 16px;
letter-spacing: 0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
.shell {
display: grid;
grid-template-columns: 16rem minmax(0, 1fr);
min-height: 100vh;
}
.rail {
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 1rem;
border-right: 1px solid #d9ded4;
background: #101418;
color: #f6f7f4;
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.brand small {
display: block;
margin-top: 0.15rem;
color: #b5beb2;
}
.mark {
display: grid;
width: 2.5rem;
height: 2.5rem;
place-items: center;
border: 1px solid #5ac8a6;
border-radius: 6px;
color: #5ac8a6;
font-weight: 800;
}
nav {
display: grid;
gap: 0.5rem;
}
nav a {
min-height: 2.35rem;
padding: 0.55rem 0.75rem;
border: 1px solid #263039;
border-radius: 6px;
}
nav a.active,
nav a:hover {
border-color: #3f5049;
background: #1b2329;
}
.workspace {
padding: 1.25rem;
}
.hero,
.band {
width: 100%;
max-width: 72rem;
margin: 0 auto 1rem;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
gap: 1rem;
align-items: end;
padding: 1.25rem 0 0.25rem;
}
.eyebrow,
.section-head p,
.tile p,
.implementation-list p,
.cli-panel p {
color: #58615b;
}
.eyebrow {
margin: 0 0 0.4rem;
font-weight: 800;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1 {
margin-bottom: 0.45rem;
font-size: 3rem;
line-height: 1;
}
.lede {
max-width: 52rem;
margin-bottom: 0;
font-size: 1.12rem;
line-height: 1.55;
}
.status-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.5rem;
}
.status-grid span {
min-height: 5rem;
padding: 0.75rem;
border: 1px solid #d6ddd2;
border-radius: 8px;
background: #ffffff;
color: #58615b;
}
.status-grid strong {
display: block;
color: #101418;
font-size: 1.4rem;
}
.band {
padding: 1rem 0;
}
.section-head {
display: flex;
align-items: end;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.8rem;
}
.section-head h2,
.cli-panel h2 {
margin-bottom: 0;
font-size: 1.15rem;
}
.section-head p {
max-width: 34rem;
margin-bottom: 0;
line-height: 1.45;
}
.primitive-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 0.75rem;
}
.tile,
.schema-list article,
.cli-panel,
.implementation-list article {
border: 1px solid #d6ddd2;
border-radius: 8px;
background: #ffffff;
}
.tile {
min-height: 10rem;
padding: 0.85rem;
}
.tile h3,
.implementation-list h3 {
margin-bottom: 0.35rem;
font-size: 1rem;
}
.tile p,
.implementation-list p,
.cli-panel p {
margin-bottom: 0;
line-height: 1.45;
}
.two-col {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(20rem, 0.9fr);
gap: 1rem;
}
.schema-list {
display: grid;
gap: 0.5rem;
}
.schema-list article {
display: grid;
gap: 0.35rem;
min-height: 4.2rem;
padding: 0.75rem;
}
code {
overflow-wrap: anywhere;
color: #2d6f60;
}
.cli-panel {
padding: 1rem;
}
pre {
overflow-x: auto;
margin: 0.75rem 0;
padding: 0.85rem;
border-radius: 6px;
background: #101418;
color: #f6f7f4;
}
.implementation-list {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.75rem;
}
.implementation-list article {
display: grid;
grid-template-columns: 0.7rem 1fr;
gap: 0.65rem;
min-height: 9rem;
padding: 0.85rem;
}
.implementation-list span {
width: 0.7rem;
height: 0.7rem;
margin-top: 0.25rem;
border-radius: 999px;
background: #f0c56a;
}
@media (max-width: 980px) {
.primitive-grid,
.implementation-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.hero,
.two-col {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.shell {
grid-template-columns: 1fr;
}
.rail {
border-right: 0;
}
nav {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
nav a {
text-align: center;
}
.workspace {
padding: 0.9rem;
}
.section-head,
.hero {
align-items: start;
flex-direction: column;
}
.status-grid,
.primitive-grid,
.implementation-list {
grid-template-columns: 1fr;
}
h1 {
font-size: 2.4rem;
}
}

1154
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,8 +12,8 @@
"apps/*" "apps/*"
], ],
"scripts": { "scripts": {
"build": "npm --workspace @logicsrc/schemas run build && npm --workspace @logicsrc/validators run build && npm --workspace @logicsrc/plugin-core run build && npm --workspace @logicsrc/plugin-coinpay run build && npm --workspace @logicsrc/plugin-ugig run build && npm --workspace @logicsrc/plugin-sh1pt run build && npm --workspace @logicsrc/tui run build && npm --workspace @logicsrc/cli run build && npm --workspace @logicsrc/commandboard-api run build && npm --workspace @logicsrc/commandboard-web run build", "build": "npm --workspace @logicsrc/schemas run build && npm --workspace @logicsrc/validators run build && npm --workspace @logicsrc/plugin-core run build && npm --workspace @logicsrc/plugin-coinpay run build && npm --workspace @logicsrc/plugin-ugig run build && npm --workspace @logicsrc/plugin-sh1pt run build && npm --workspace @logicsrc/tui run build && npm --workspace @logicsrc/cli run build && npm --workspace @profullstack/logicsrc-mcp run build && npm --workspace @logicsrc/commandboard-api run build && npm --workspace @logicsrc/commandboard-web run build && npm --workspace @logicsrc/web run build",
"start": "npm --workspace @logicsrc/commandboard-api run start", "start": "npm --workspace @logicsrc/web run start",
"test": "npm run test --workspaces --if-present", "test": "npm run test --workspaces --if-present",
"check": "npm run build && npm run test", "check": "npm run build && npm run test",
"schemas:validate": "npm --workspace @logicsrc/validators run validate:fixtures", "schemas:validate": "npm --workspace @logicsrc/validators run validate:fixtures",

View file

@ -0,0 +1,23 @@
{
"name": "@profullstack/logicsrc-mcp",
"version": "0.1.0",
"description": "MCP server for LogicSRC standards, schemas, prompts, and validators.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"logicsrc-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run src"
},
"dependencies": {
"@logicsrc/validators": "file:../validators",
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^4.1.13"
},
"devDependencies": {
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,12 @@
#!/usr/bin/env node
import { pathToFileURL } from "node:url";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createLogicSrcMcpServer } from "./server.js";
export { createLogicSrcMcpServer } from "./server.js";
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const server = createLogicSrcMcpServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}

View file

@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createLogicSrcMcpServer } from "./server.js";
describe("LogicSRC MCP server", () => {
it("exposes schemas, validation, and prompts over MCP", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = createLogicSrcMcpServer();
const client = new Client({ name: "test-client", version: "0.1.0" });
await server.connect(serverTransport);
await client.connect(clientTransport);
const resources = await client.listResources();
expect(resources.resources.some((resource) => resource.uri === "logicsrc://schemas/task")).toBe(true);
const schema = await client.readResource({ uri: "logicsrc://schemas/task" });
expect(textContent(schema.contents[0])).toContain("logicsrc.task");
const example = await client.callTool({ name: "example_document", arguments: { kind: "task" } });
const text = firstToolText(example);
expect(text).toContain("Test checkout flow");
const validation = await client.callTool({ name: "validate_document", arguments: { kind: "task", document: text, fileName: "task.json" } });
const validationText = firstToolText(validation);
expect(validationText).toContain('"ok": true');
const prompts = await client.listPrompts();
expect(prompts.prompts.map((prompt) => prompt.name)).toContain("create-valid-task");
await client.close();
await server.close();
});
});
function textContent(content: unknown) {
return isRecord(content) && typeof content.text === "string" ? content.text : "";
}
function firstToolText(result: unknown) {
if (!isRecord(result) || !Array.isArray(result.content)) {
return "";
}
const [first] = result.content;
return isRecord(first) && first.type === "text" && typeof first.text === "string" ? first.text : "";
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

View file

@ -0,0 +1,220 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { assertSchemaKind, parseDocument, schemas, validate, type SchemaKind } from "@logicsrc/validators";
const docs = {
positioning: `LogicSRC is an open standards initiative for human and AI agent coordination, maintained by Profullstack, Inc.
CommandBoard.run is a hosted product by Profullstack, Inc., built on LogicSRC. LogicSRC defines identity, boards, posts, tasks, bounties, agents, agent runs, permissions, payments, escrow, reputation, events, webhooks, CLI commands, API schemas, and plugin contracts.`,
roadmap: `LogicSRC v1.0 focuses on schemas, validation tooling, plugin manifests, CLI/TUI conventions, event streams, agent profiles, permissions, and reference implementations.`,
primitives: `Core LogicSRC primitives: users, DIDs, OAuth accounts, profiles, organizations, boards, posts, threads, comments, tasks, bids, submissions, agents, agent runs, payments, escrows, wallets, reputation events, files, API keys, permissions, audit logs, webhooks, schema versions, and plugin audit logs.`
} as const;
const schemaKinds = Object.keys(schemas) as SchemaKind[];
export function createLogicSrcMcpServer() {
const server = new McpServer(
{
name: "@profullstack/logicsrc-mcp",
version: "0.1.0"
},
{
capabilities: {
resources: {},
tools: {},
prompts: {}
},
instructions: "Use this server for LogicSRC standards, schema resources, validation, and draft object generation. Treat CommandBoard.run as a reference implementation, not the standards identity."
}
);
for (const [name, text] of Object.entries(docs)) {
const uri = `logicsrc://docs/${name}`;
server.registerResource(
`logicsrc-${name}`,
uri,
{
title: `LogicSRC ${titleCase(name)}`,
description: `LogicSRC ${name} reference text.`,
mimeType: "text/markdown"
},
async () => ({ contents: [{ uri, mimeType: "text/markdown", text }] })
);
}
for (const kind of schemaKinds) {
const uri = `logicsrc://schemas/${kind}`;
server.registerResource(
`logicsrc-schema-${kind}`,
uri,
{
title: `LogicSRC ${kind} schema`,
description: `JSON Schema for LogicSRC ${kind} documents.`,
mimeType: "application/schema+json"
},
async () => ({
contents: [
{
uri,
mimeType: "application/schema+json",
text: JSON.stringify(schemas[kind], null, 2)
}
]
})
);
}
server.registerTool(
"list_schema_kinds",
{
title: "List LogicSRC Schema Kinds",
description: "Lists the LogicSRC schema kinds exposed by this standards server.",
annotations: { readOnlyHint: true, openWorldHint: false }
},
async () => textResult(JSON.stringify({ schemaKinds }, null, 2))
);
server.registerTool(
"validate_document",
{
title: "Validate LogicSRC Document",
description: "Validates a JSON or YAML document against a LogicSRC schema kind.",
inputSchema: {
kind: z.enum(["agent", "event", "plugin", "run", "task"]),
document: z.string().describe("JSON or YAML document text."),
fileName: z.string().optional().describe("Optional file name used to select JSON parsing when it ends with .json.")
},
annotations: { readOnlyHint: true, openWorldHint: false }
},
async ({ kind, document, fileName }) => {
const parsed = parseDocument(document, fileName ?? "document.yaml");
const result = validate(assertSchemaKind(kind), parsed);
return textResult(JSON.stringify(result.ok ? { ok: true, kind: result.kind } : { ok: false, kind: result.kind, errors: result.errors }, null, 2));
}
);
server.registerTool(
"example_document",
{
title: "Generate Example LogicSRC Document",
description: "Returns a minimal example document for a LogicSRC schema kind.",
inputSchema: {
kind: z.enum(["agent", "event", "plugin", "run", "task"])
},
annotations: { readOnlyHint: true, openWorldHint: false }
},
async ({ kind }) => textResult(JSON.stringify(exampleFor(kind), null, 2))
);
server.registerPrompt(
"create-valid-task",
{
title: "Create Valid LogicSRC Task",
description: "Prompt template for turning a workflow request into a valid LogicSRC task.",
argsSchema: {
request: z.string().describe("Human description of the desired task.")
}
},
async ({ request }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Create a valid LogicSRC task JSON document for this request. Use logicsrc://schemas/task and keep it minimal unless details are required.\n\nRequest:\n${request}`
}
}
]
})
);
server.registerPrompt(
"review-plugin-manifest",
{
title: "Review LogicSRC Plugin Manifest",
description: "Prompt template for reviewing a plugin manifest against the LogicSRC plugin schema.",
argsSchema: {
manifest: z.string().describe("Plugin manifest JSON or YAML.")
}
},
async ({ manifest }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Review this LogicSRC plugin manifest against logicsrc://schemas/plugin. Identify schema issues, security concerns, missing permissions, and unclear capabilities.\n\nManifest:\n${manifest}`
}
}
]
})
);
return server;
}
function textResult(text: string) {
return { content: [{ type: "text" as const, text }] };
}
function titleCase(value: string) {
return value.replace(/(^|-)([a-z])/g, (_match, prefix: string, letter: string) => `${prefix ? " " : ""}${letter.toUpperCase()}`);
}
function exampleFor(kind: SchemaKind) {
switch (kind) {
case "agent":
return {
type: "logicsrc.agent",
version: "0.1",
agent_did: "qa-agent-01.coinpay",
name: "QA Agent",
capabilities: ["browser.qa", "report.write"],
status: "active"
};
case "event":
return {
type: "logicsrc.event",
version: "0.1",
event_id: "evt_123",
event_type: "task.created",
resource_type: "task",
resource_id: "task_123",
actor_did: "anthony.coinpay",
created_at: new Date(0).toISOString()
};
case "plugin":
return {
type: "logicsrc.plugin",
version: "0.1",
id: "example-plugin",
name: "Example Plugin",
description: "Example LogicSRC plugin manifest.",
capabilities: ["tasks.read"],
permissions: ["tasks:read"]
};
case "run":
return {
type: "logicsrc.run",
version: "0.1",
run_id: "run_123",
task_id: "task_123",
agent_did: "qa-agent-01.coinpay",
status: "completed",
started_at: new Date(0).toISOString()
};
case "task":
return {
type: "logicsrc.task",
version: "0.1",
title: "Test checkout flow",
description: "Verify checkout flow across desktop and mobile.",
board: "/qa",
creator_did: "anthony.coinpay",
status: "open",
budget: { amount: 25, currency: "USDC" },
agent_allowed: true,
human_allowed: true
};
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}