@miadi/stateloom-mcp 0.2.6 → 0.3.1
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/dist/atomicWrite.d.ts +1 -0
- package/dist/atomicWrite.js +28 -0
- package/dist/erd.d.ts +2 -0
- package/dist/erd.js +5 -2
- package/dist/live.d.ts +26 -0
- package/dist/live.js +131 -0
- package/dist/projectSwitch.d.ts +6 -2
- package/dist/projectSwitch.js +18 -6
- package/dist/sequence.d.ts +55 -0
- package/dist/sequence.js +337 -0
- package/dist/server.d.ts +21 -5
- package/dist/server.js +208 -75
- package/dist/system.d.ts +88 -0
- package/dist/system.js +813 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeFileAtomic(path: string, text: string): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write a document whole or not at all: to a temp file beside it, then rename.
|
|
3
|
+
*
|
|
4
|
+
* A reader (the hub's file watch, a canvas, another tool) never sees half a
|
|
5
|
+
* file. An existing file that is not writable stays refused — a rename would
|
|
6
|
+
* replace it regardless of its mode, and a read-only document is a person's
|
|
7
|
+
* word that it should not change — and the new file keeps the old one's mode.
|
|
8
|
+
*/
|
|
9
|
+
import { accessSync, chmodSync, constants, existsSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
10
|
+
import { basename, dirname, join } from "path";
|
|
11
|
+
export function writeFileAtomic(path, text) {
|
|
12
|
+
let mode;
|
|
13
|
+
if (existsSync(path)) {
|
|
14
|
+
accessSync(path, constants.W_OK);
|
|
15
|
+
mode = statSync(path).mode & 0o7777;
|
|
16
|
+
}
|
|
17
|
+
const temp = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}.tmp`);
|
|
18
|
+
try {
|
|
19
|
+
writeFileSync(temp, text, "utf8");
|
|
20
|
+
if (mode !== undefined)
|
|
21
|
+
chmodSync(temp, mode);
|
|
22
|
+
renameSync(temp, path);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
rmSync(temp, { force: true });
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/erd.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface ErdHost {
|
|
|
25
25
|
emitFull(def: EntityRelationshipDefinition): void;
|
|
26
26
|
/** The refusal for a path outside the permitted document root, if any. */
|
|
27
27
|
outsideRoot(path: string): string | undefined;
|
|
28
|
+
/** check_links from a sequence or a system: the whole system's check (check_system). */
|
|
29
|
+
checkSystem?(): Promise<ToolResult>;
|
|
28
30
|
}
|
|
29
31
|
type ToolResult = {
|
|
30
32
|
content: {
|
package/dist/erd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
|
-
import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, } from "@miadi/stateloom-protocol";
|
|
4
|
+
import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, isSqdfPath, isSysdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, } from "@miadi/stateloom-protocol";
|
|
5
5
|
import { stampedOutputPath } from "@miadi/stateloom-cli/render";
|
|
6
6
|
const ok = (text) => ({ content: [{ type: "text", text }] });
|
|
7
7
|
const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
@@ -193,8 +193,11 @@ export function registerErdTools(server, host) {
|
|
|
193
193
|
? fail(`${errors.length} problem(s) in '${def.settings?.name ?? ""}':\n${formatErrors(errors)}\n\n${body}`)
|
|
194
194
|
: ok(`ERD '${def.settings?.name ?? ""}' is valid.\n${body}`);
|
|
195
195
|
});
|
|
196
|
-
server.tool("check_links", "Check the names a state machine uses for its data against an ERD (rules L001–L004): the class of each object a guard reads a field of must be an entity, each <instance>.<field> in a guard must be an attribute of it, and an attribute's stateOf must point at a machine built with that entity. With no arguments: when the active document is an ERD, every .smdf.json beside it is checked; when it is a state machine, pass erd_path.", { erd_path: z.string().optional(), smdf_paths: z.array(z.string()).optional() }, async ({ erd_path, smdf_paths }) => {
|
|
196
|
+
server.tool("check_links", "Check the names a state machine uses for its data against an ERD (rules L001–L004): the class of each object a guard reads a field of must be an entity, each <instance>.<field> in a guard must be an attribute of it, and an attribute's stateOf must point at a machine built with that entity. With no arguments: when the active document is an ERD, every .smdf.json beside it is checked; when it is a state machine, pass erd_path; when it is a sequence or a system, the whole system is checked (check_system).", { erd_path: z.string().optional(), smdf_paths: z.array(z.string()).optional() }, async ({ erd_path, smdf_paths }) => {
|
|
197
197
|
const active = host.projectFile();
|
|
198
|
+
if (!erd_path && !smdf_paths?.length && host.checkSystem && (isSqdfPath(active) || isSysdfPath(active))) {
|
|
199
|
+
return host.checkSystem();
|
|
200
|
+
}
|
|
198
201
|
const erdPath = erd_path ? resolve(erd_path) : isErdfPath(active) ? active : undefined;
|
|
199
202
|
if (!erdPath) {
|
|
200
203
|
return fail(`The active document ${active} is a state machine — pass erd_path, the .erdf.json to check it against.`);
|
package/dist/live.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type PatchOp, type ViewEnvelope } from "@miadi/stateloom-protocol";
|
|
2
|
+
export interface LiveOutcome {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
text: string;
|
|
5
|
+
/** No bridge is configured: nothing was attempted. */
|
|
6
|
+
off?: true;
|
|
7
|
+
}
|
|
8
|
+
export interface Live {
|
|
9
|
+
/** Granular ops to a machine's room. `def` is what the file now holds. */
|
|
10
|
+
patch(docId: string, ops: PatchOp[], def: unknown, mtime: number): Promise<LiveOutcome>;
|
|
11
|
+
/** A whole document (ERD, sequence, system) to its room. */
|
|
12
|
+
full(docId: string, def: unknown, mtime: number): Promise<LiveOutcome>;
|
|
13
|
+
/** A view to a system's room; needs a connection, not a join. */
|
|
14
|
+
view(view: Omit<ViewEnvelope, "origin"> & {
|
|
15
|
+
origin?: string;
|
|
16
|
+
}): Promise<LiveOutcome>;
|
|
17
|
+
}
|
|
18
|
+
export interface LiveTarget {
|
|
19
|
+
/** The hub. Undefined: every push reports that the bridge is not configured. */
|
|
20
|
+
url?: string;
|
|
21
|
+
token?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
/** How long to wait for the hub at each step. Default 3000 ms. */
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function liveBridge(target: LiveTarget): Live;
|
package/dist/live.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot pushes to rooms other than the active document's (Spec 82).
|
|
3
|
+
*
|
|
4
|
+
* The server's own bridge client is joined to the active document's room, and
|
|
5
|
+
* the hub makes a socket that re-joins leave its old room. So when a scenario
|
|
6
|
+
* upgrades a machine, or an agent asks the system's canvas to show something,
|
|
7
|
+
* the push goes through a short-lived client of its own: join that room, send,
|
|
8
|
+
* wait for the hub's answer, disconnect.
|
|
9
|
+
*
|
|
10
|
+
* Every push is best-effort and comes AFTER the file is written: an unreachable
|
|
11
|
+
* hub leaves the file standing, and the outcome says so in words.
|
|
12
|
+
*
|
|
13
|
+
* Joining a room the hub has not opened yet seeds it from disk, which already
|
|
14
|
+
* holds the change; applying the ops on top would add them twice. So a push is
|
|
15
|
+
* skipped when the room's snapshot already equals what was written.
|
|
16
|
+
*/
|
|
17
|
+
import { createBridgeClient } from "@miadi/stateloom-client";
|
|
18
|
+
import { hashDef, } from "@miadi/stateloom-protocol";
|
|
19
|
+
const message = (e) => (e instanceof Error ? e.message : String(e));
|
|
20
|
+
function withTimeout(p, ms, what) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const timer = setTimeout(() => reject(new Error(`${what} took longer than ${ms} ms`)), ms);
|
|
23
|
+
p.then((v) => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
resolve(v);
|
|
26
|
+
}, (e) => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
reject(e);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** The hub's answer to a patch or a full: an ack with its seq, or an error frame. */
|
|
33
|
+
function answer(client, ms) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const offs = [];
|
|
36
|
+
const done = (r) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
for (const off of offs)
|
|
39
|
+
off();
|
|
40
|
+
resolve(r);
|
|
41
|
+
};
|
|
42
|
+
const timer = setTimeout(() => done({ error: `no answer from the hub in ${ms} ms` }), ms);
|
|
43
|
+
offs.push(client.on("ack", (a) => done({ seq: a.seq })));
|
|
44
|
+
offs.push(client.on("error", (e) => done({ error: e.message || e.code })));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** The hub said no: a refused token, or a frame it could not take. */
|
|
48
|
+
class Refused extends Error {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The first error frame the hub sends this client, as a rejection. A failed
|
|
52
|
+
* connection is left to `connect()`, which rejects on it by itself.
|
|
53
|
+
*/
|
|
54
|
+
function refusal(client) {
|
|
55
|
+
let off = () => { };
|
|
56
|
+
const promise = new Promise((_, reject) => {
|
|
57
|
+
off = client.on("error", (e) => {
|
|
58
|
+
if (e.code !== "connect_error")
|
|
59
|
+
reject(new Refused(e.message || e.code));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
promise.catch(() => { });
|
|
63
|
+
return { promise, off };
|
|
64
|
+
}
|
|
65
|
+
export function liveBridge(target) {
|
|
66
|
+
const ms = target.timeoutMs ?? 3000;
|
|
67
|
+
const unconfigured = {
|
|
68
|
+
ok: false,
|
|
69
|
+
off: true,
|
|
70
|
+
text: "not live: the bridge is not configured (STATELOOM_BRIDGE_URL unset)",
|
|
71
|
+
};
|
|
72
|
+
const open = (docId) => createBridgeClient({
|
|
73
|
+
url: target.url,
|
|
74
|
+
role: "agent",
|
|
75
|
+
docId,
|
|
76
|
+
name: target.name ?? "mcp-agent",
|
|
77
|
+
token: target.token,
|
|
78
|
+
autoResync: false,
|
|
79
|
+
});
|
|
80
|
+
async function send(docId, def, kind, emit) {
|
|
81
|
+
if (!target.url)
|
|
82
|
+
return unconfigured;
|
|
83
|
+
const client = open(docId);
|
|
84
|
+
const refused = refusal(client);
|
|
85
|
+
try {
|
|
86
|
+
const joined = await withTimeout(Promise.race([client.join(), refused.promise]), ms, "joining the room");
|
|
87
|
+
const held = joined.snapshot?.def;
|
|
88
|
+
if (held && hashDef(held) === hashDef(def)) {
|
|
89
|
+
return { ok: true, text: "the room already holds it" };
|
|
90
|
+
}
|
|
91
|
+
const reply = answer(client, ms);
|
|
92
|
+
emit(client);
|
|
93
|
+
const r = await reply;
|
|
94
|
+
return r.error
|
|
95
|
+
? { ok: false, text: `the hub refused the ${kind}: ${r.error}` }
|
|
96
|
+
: { ok: true, text: `${kind} sent live (seq ${r.seq})` };
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
const why = e instanceof Refused ? `the hub refused the ${kind}: ${e.message}` : message(e);
|
|
100
|
+
return { ok: false, text: `not live: ${why}; the file stands` };
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
refused.off();
|
|
104
|
+
client.disconnect();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
patch: (docId, ops, def, mtime) => send(docId, def, "patch", (c) => c.emitPatch(ops, mtime)),
|
|
109
|
+
full: (docId, def, mtime) => send(docId, def, "document", (c) => c.emitFull(def, mtime)),
|
|
110
|
+
async view(view) {
|
|
111
|
+
if (!target.url)
|
|
112
|
+
return unconfigured;
|
|
113
|
+
const client = open(view.docId);
|
|
114
|
+
const refused = refusal(client);
|
|
115
|
+
try {
|
|
116
|
+
await withTimeout(Promise.race([client.emitView({ ...view, origin: view.origin ?? target.name ?? "agent" }), refused.promise]), ms, "reaching the hub");
|
|
117
|
+
// The hub acks no view, but it answers a refused one with an error
|
|
118
|
+
// frame: wait a moment for that before calling the view delivered.
|
|
119
|
+
await Promise.race([new Promise((r) => setTimeout(r, Math.min(ms, 250))), refused.promise]);
|
|
120
|
+
return { ok: true, text: "view sent to the hub" };
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return { ok: false, text: e instanceof Refused ? `the hub refused the view: ${e.message}` : `not sent: ${message(e)}` };
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
refused.off();
|
|
127
|
+
client.disconnect();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
package/dist/projectSwitch.d.ts
CHANGED
|
@@ -10,8 +10,12 @@ export interface SwitchResult {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function chronicleRoot(env?: Record<string, string | undefined>): string;
|
|
12
12
|
/**
|
|
13
|
-
* Resolve `miadi-chronicle://<episode>/[diagrams/]<name>[
|
|
14
|
-
* absolute path `<root>/<episode-dir>/diagrams/<name>.
|
|
13
|
+
* Resolve `miadi-chronicle://<episode>/[diagrams/]<name>[.<type>.json]` to the
|
|
14
|
+
* absolute path `<root>/<episode-dir>/diagrams/<name>.<type>.json`. The loom
|
|
15
|
+
* weaves four types, told apart by extension: `.smdf.json` (state machine),
|
|
16
|
+
* `.erdf.json` (ERD), `.sqdf.json` (sequence), `.sysdf.json` (system). An
|
|
17
|
+
* explicit extension is kept; `.smdf`/`.erdf`/`.sqdf`/`.sysdf` without `.json`
|
|
18
|
+
* gets it; a bare name is a state machine, `<name>.smdf.json`, as it always was.
|
|
15
19
|
*
|
|
16
20
|
* Episode matching: exact folder name first; else a bare number matches
|
|
17
21
|
* `-episode-NNN-` folders. Episode numbers are NOT unique in the chronicle
|
package/dist/projectSwitch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure logic behind the `set_project_file` MCP tool (path power — the agent
|
|
3
|
-
* chooses which
|
|
3
|
+
* chooses which document the loom weaves: a `.smdf.json`, `.erdf.json`,
|
|
4
|
+
* `.sqdf.json` or `.sysdf.json`).
|
|
4
5
|
*
|
|
5
6
|
* Two modalities, by William's orientation (2026-08-07): STANDALONE — a plain
|
|
6
7
|
* path, no episode, just designing — and RELATIONAL — the document lives
|
|
@@ -17,8 +18,12 @@ export function chronicleRoot(env = process.env) {
|
|
|
17
18
|
return env.MIADI_CHRONICLE_ROOT ?? "/srv/miadi/episodes/miadi-chronicle";
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
* Resolve `miadi-chronicle://<episode>/[diagrams/]<name>[
|
|
21
|
-
* absolute path `<root>/<episode-dir>/diagrams/<name>.
|
|
21
|
+
* Resolve `miadi-chronicle://<episode>/[diagrams/]<name>[.<type>.json]` to the
|
|
22
|
+
* absolute path `<root>/<episode-dir>/diagrams/<name>.<type>.json`. The loom
|
|
23
|
+
* weaves four types, told apart by extension: `.smdf.json` (state machine),
|
|
24
|
+
* `.erdf.json` (ERD), `.sqdf.json` (sequence), `.sysdf.json` (system). An
|
|
25
|
+
* explicit extension is kept; `.smdf`/`.erdf`/`.sqdf`/`.sysdf` without `.json`
|
|
26
|
+
* gets it; a bare name is a state machine, `<name>.smdf.json`, as it always was.
|
|
22
27
|
*
|
|
23
28
|
* Episode matching: exact folder name first; else a bare number matches
|
|
24
29
|
* `-episode-NNN-` folders. Episode numbers are NOT unique in the chronicle
|
|
@@ -64,13 +69,20 @@ export function resolveChronicleUri(uri, env = process.env) {
|
|
|
64
69
|
if (matches.length > 1) {
|
|
65
70
|
throw new Error(`episode '${episodeRef}' is ambiguous — candidates:\n ${matches.sort().join("\n ")}\nName the full folder.`);
|
|
66
71
|
}
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
return join(root, matches[0], "diagrams", loomFileName(name));
|
|
73
|
+
}
|
|
74
|
+
/** `<name>` → `<name>.smdf.json`; a loom type without `.json` gets it; any `.json` is kept. */
|
|
75
|
+
function loomFileName(name) {
|
|
76
|
+
if (name.toLowerCase().endsWith(".json"))
|
|
77
|
+
return name;
|
|
78
|
+
if (/\.(smdf|erdf|sqdf|sysdf)$/i.test(name))
|
|
79
|
+
return `${name}.json`;
|
|
80
|
+
return `${name}.smdf.json`;
|
|
69
81
|
}
|
|
70
82
|
export function resolveProjectSwitch(next, previous) {
|
|
71
83
|
const path = CHRONICLE_SCHEME.test(next) ? resolveChronicleUri(next) : resolve(next);
|
|
72
84
|
if (!path.endsWith(".json")) {
|
|
73
|
-
throw new Error(`project file must be a .json document (got '${path}') — the loom weaves .smdf.json files`);
|
|
85
|
+
throw new Error(`project file must be a .json document (got '${path}') — the loom weaves .smdf.json (state machine), .erdf.json (ERD), .sqdf.json (sequence) and .sysdf.json (system) files`);
|
|
74
86
|
}
|
|
75
87
|
return {
|
|
76
88
|
path,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sequence half of the MCP server (Spec 81).
|
|
3
|
+
*
|
|
4
|
+
* A sequence (`.sqdf.json`) is a usage scenario: who sends what to whom, in
|
|
5
|
+
* order. The tools here act on the active document when it is an SQDF;
|
|
6
|
+
* `create_sequence` is the one that also moves the active document.
|
|
7
|
+
*
|
|
8
|
+
* People and tools count from 1: a main message is addressed by its number
|
|
9
|
+
* (`"9"`), a fragment's message as `f<fragment>.<n>` (`"f1.2"`), a fragment by
|
|
10
|
+
* its number. The protocol's edits count from 0; the conversion happens here.
|
|
11
|
+
*
|
|
12
|
+
* A sequence travels to the live canvas as a whole document (`emitFull`), as an
|
|
13
|
+
* ERD does. After each edit that changes the scenario, `afterEdit` gives the
|
|
14
|
+
* system a chance to reconcile (Spec 82 reconcile mode), and its one line is
|
|
15
|
+
* appended to the tool result.
|
|
16
|
+
*/
|
|
17
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { type SequenceDefinition } from "@miadi/stateloom-protocol";
|
|
19
|
+
export interface SequenceHost {
|
|
20
|
+
/** The active document's absolute path. */
|
|
21
|
+
projectFile(): string;
|
|
22
|
+
/** Re-point the active document under set_project_file's guards. Returns the refusal, if any. */
|
|
23
|
+
switchTo(path: string): string | undefined;
|
|
24
|
+
/** Mirror the whole document to the live bridge room. Best-effort. */
|
|
25
|
+
emitFull(def: SequenceDefinition): void;
|
|
26
|
+
/** The refusal for a path outside the permitted document root, if any. */
|
|
27
|
+
outsideRoot(path: string): string | undefined;
|
|
28
|
+
/** After an edit that changed the scenario was written: one line to append (reconcile mode), or nothing. */
|
|
29
|
+
afterEdit?(path: string): Promise<string | undefined>;
|
|
30
|
+
}
|
|
31
|
+
type ToolResult = {
|
|
32
|
+
content: {
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
isError?: boolean;
|
|
37
|
+
};
|
|
38
|
+
export declare function readSequence(path: string): SequenceDefinition | null;
|
|
39
|
+
/** One line for set_project_file / get_project_file when the active document is an SQDF. */
|
|
40
|
+
export declare function sequenceSummary(path: string): string;
|
|
41
|
+
export declare function sequenceGetDefinition(host: SequenceHost): ToolResult;
|
|
42
|
+
export declare function sequenceLoadDefinition(host: SequenceHost, json: string): Promise<ToolResult>;
|
|
43
|
+
export declare function sequenceRender(host: SequenceHost, opts: {
|
|
44
|
+
format?: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
stamp?: boolean;
|
|
47
|
+
}): ToolResult;
|
|
48
|
+
export declare function sequenceGetNotes(host: SequenceHost): ToolResult;
|
|
49
|
+
/**
|
|
50
|
+
* `target` is a participant's name, a message address (`9`, `f1.2`, or `message 9`
|
|
51
|
+
* as get_notes prints it), or `fragment N`. Omitted: the whole sequence.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sequenceSetNotes(host: SequenceHost, target: string | undefined, notes: string): Promise<ToolResult>;
|
|
54
|
+
export declare function registerSequenceTools(server: McpServer, host: SequenceHost): void;
|
|
55
|
+
export {};
|