@j0hanz/thinkseq-mcp 1.2.2 → 1.2.4
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/README.md +1 -2
- package/dist/app.js +3 -3
- package/dist/appConfig/runDependencies.js +34 -36
- package/dist/appConfig/shutdown.js +18 -13
- package/dist/appConfig/types.d.ts +2 -6
- package/dist/engine/revision.js +8 -15
- package/dist/engine/thoughtQueries.js +1 -6
- package/dist/engine/thoughtStore.js +19 -10
- package/dist/engine.js +20 -15
- package/dist/instructions.md +27 -50
- package/dist/lib/cli.js +22 -16
- package/dist/lib/mcpLogging.d.ts +5 -0
- package/dist/lib/mcpLogging.js +60 -0
- package/dist/lib/package.js +3 -13
- package/dist/lib/protocolGuards.js +10 -12
- package/dist/lib/stdioGuards.d.ts +1 -0
- package/dist/lib/stdioGuards.js +125 -11
- package/dist/lib/types.d.ts +6 -0
- package/dist/schemas/inputs.d.ts +1 -1
- package/dist/schemas/inputs.js +1 -1
- package/dist/tools/thinkseq.d.ts +1 -5
- package/dist/tools/thinkseq.js +62 -24
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -268,7 +268,7 @@ Output:
|
|
|
268
268
|
|
|
269
269
|
## Behavior and validation
|
|
270
270
|
|
|
271
|
-
- Inputs are validated with Zod and unknown keys are
|
|
271
|
+
- Inputs are validated with Zod and unknown keys are rejected.
|
|
272
272
|
- `thoughtNumber` is auto-incremented (1, 2, 3...).
|
|
273
273
|
- `totalThoughts` defaults to 3, must be in 1-25, and is adjusted up to at least `thoughtNumber`.
|
|
274
274
|
- The engine stores thoughts in memory and prunes when limits are exceeded:
|
|
@@ -282,7 +282,6 @@ This server publishes events via `node:diagnostics_channel`:
|
|
|
282
282
|
|
|
283
283
|
- `thinkseq:tool` for `tool.start` and `tool.end` (includes duration, errors, and request context).
|
|
284
284
|
- `thinkseq:lifecycle` for `lifecycle.started` and `lifecycle.shutdown`.
|
|
285
|
-
- `thinkseq:engine` for internal engine events such as `engine.sequence_gap`.
|
|
286
285
|
|
|
287
286
|
## Configuration
|
|
288
287
|
|
package/dist/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildShutdownDependencies, resolvePackageIdentity, resolveRunDependencies, } from './appConfig.js';
|
|
2
|
-
import {
|
|
2
|
+
import { installMcpLogging } from './lib/mcpLogging.js';
|
|
3
3
|
const toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
4
4
|
const createExit = (proc, exit) => exit ?? ((code) => proc.exit(code));
|
|
5
5
|
const createHandlerFor = (logError, exit) => (label) => (value) => {
|
|
@@ -19,14 +19,14 @@ export async function run(deps = {}) {
|
|
|
19
19
|
const resolved = resolveRunDependencies(deps);
|
|
20
20
|
const pkg = await resolved.readPackageJson(AbortSignal.timeout(resolved.packageReadTimeoutMs));
|
|
21
21
|
const { name, version } = resolvePackageIdentity(pkg);
|
|
22
|
+
const server = resolved.createServer(name, version);
|
|
23
|
+
installMcpLogging(server);
|
|
22
24
|
resolved.publishLifecycleEvent({
|
|
23
25
|
type: 'lifecycle.started',
|
|
24
26
|
ts: resolved.now(),
|
|
25
27
|
});
|
|
26
|
-
const server = resolved.createServer(name, version);
|
|
27
28
|
const engine = resolved.engineFactory();
|
|
28
29
|
resolved.registerTool(server, engine);
|
|
29
|
-
installInitializationGuards(server);
|
|
30
30
|
const transport = await resolved.connectServer(server);
|
|
31
31
|
resolved.installShutdownHandlers(buildShutdownDependencies(resolved, { server, engine, transport }));
|
|
32
32
|
}
|
|
@@ -1,73 +1,71 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { McpServer, ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { ThinkingEngine } from '../engine.js';
|
|
5
5
|
import { publishLifecycleEvent } from '../lib/diagnostics.js';
|
|
6
6
|
import { readSelfPackageJson } from '../lib/package.js';
|
|
7
|
-
import { installStdioInvalidMessageGuards, installStdioParseErrorResponder, } from '../lib/stdioGuards.js';
|
|
7
|
+
import { installStdioInitializationGuards, installStdioInvalidMessageGuards, installStdioParseErrorResponder, } from '../lib/stdioGuards.js';
|
|
8
8
|
import { registerThinkSeq } from '../tools/thinkseq.js';
|
|
9
9
|
import { installShutdownHandlers } from './shutdown.js';
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const INSTRUCTIONS_URL = new URL('../instructions.md', import.meta.url);
|
|
11
|
+
const INSTRUCTIONS_FALLBACK = 'ThinkSeq is a tool for structured, sequential thinking with revision support.';
|
|
12
|
+
function readInstructionsText() {
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
-
encoding: 'utf8',
|
|
15
|
-
});
|
|
16
|
-
const trimmed = raw.trim();
|
|
17
|
-
return trimmed.length > 0 ? trimmed : DEFAULT_SERVER_INSTRUCTIONS;
|
|
14
|
+
return readFileSync(INSTRUCTIONS_URL, { encoding: 'utf8' });
|
|
18
15
|
}
|
|
19
16
|
catch {
|
|
20
|
-
return
|
|
17
|
+
return INSTRUCTIONS_FALLBACK;
|
|
21
18
|
}
|
|
22
19
|
}
|
|
20
|
+
function loadServerInstructions() {
|
|
21
|
+
const raw = readInstructionsText();
|
|
22
|
+
const trimmed = raw.trim();
|
|
23
|
+
return trimmed.length > 0 ? trimmed : INSTRUCTIONS_FALLBACK;
|
|
24
|
+
}
|
|
25
|
+
function registerInstructionsResource(server) {
|
|
26
|
+
server.registerResource('instructions', new ResourceTemplate('internal://instructions', { list: undefined }), { title: 'Instructions', mimeType: 'text/markdown' }, (uri) => ({
|
|
27
|
+
contents: [
|
|
28
|
+
{
|
|
29
|
+
uri: uri.href,
|
|
30
|
+
text: readInstructionsText(),
|
|
31
|
+
mimeType: 'text/markdown',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
23
36
|
const SERVER_INSTRUCTIONS = loadServerInstructions();
|
|
24
37
|
const DEFAULT_PACKAGE_READ_TIMEOUT_MS = 2000;
|
|
25
38
|
const defaultCreateServer = (name, version) => {
|
|
26
|
-
|
|
39
|
+
const server = new McpServer({ name, version }, {
|
|
27
40
|
instructions: SERVER_INSTRUCTIONS,
|
|
28
|
-
capabilities: { logging: {} },
|
|
41
|
+
capabilities: { logging: {}, tools: { listChanged: true } },
|
|
29
42
|
});
|
|
43
|
+
registerInstructionsResource(server);
|
|
44
|
+
return server;
|
|
30
45
|
};
|
|
31
46
|
const defaultConnectServer = async (server, createTransport = () => new StdioServerTransport()) => {
|
|
32
47
|
const transport = createTransport();
|
|
33
48
|
await server.connect(transport);
|
|
49
|
+
installStdioInitializationGuards(transport);
|
|
34
50
|
installStdioInvalidMessageGuards(transport);
|
|
35
51
|
installStdioParseErrorResponder(transport);
|
|
36
52
|
return transport;
|
|
37
53
|
};
|
|
38
|
-
function
|
|
54
|
+
export function resolveRunDependencies(deps) {
|
|
39
55
|
return {
|
|
40
56
|
processLike: deps.processLike ?? process,
|
|
41
57
|
packageReadTimeoutMs: deps.packageReadTimeoutMs ?? DEFAULT_PACKAGE_READ_TIMEOUT_MS,
|
|
42
58
|
readPackageJson: deps.readPackageJson ?? readSelfPackageJson,
|
|
43
59
|
publishLifecycleEvent: deps.publishLifecycleEvent ?? publishLifecycleEvent,
|
|
44
|
-
now: deps.now ?? Date.now,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function resolveServerDependencies(deps) {
|
|
48
|
-
return {
|
|
49
60
|
createServer: deps.createServer ?? defaultCreateServer,
|
|
50
61
|
connectServer: deps.connectServer ?? defaultConnectServer,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function resolveEngineDependencies(deps) {
|
|
54
|
-
return {
|
|
55
62
|
registerTool: deps.registerTool ?? registerThinkSeq,
|
|
56
63
|
engineFactory: deps.engineFactory ?? (() => new ThinkingEngine()),
|
|
57
64
|
installShutdownHandlers: deps.installShutdownHandlers ?? installShutdownHandlers,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return {};
|
|
63
|
-
return { shutdownTimeoutMs: deps.shutdownTimeoutMs };
|
|
64
|
-
}
|
|
65
|
-
export function resolveRunDependencies(deps) {
|
|
66
|
-
return {
|
|
67
|
-
...resolveCoreDependencies(deps),
|
|
68
|
-
...resolveServerDependencies(deps),
|
|
69
|
-
...resolveEngineDependencies(deps),
|
|
70
|
-
...resolveShutdownTimeout(deps),
|
|
65
|
+
now: deps.now ?? Date.now,
|
|
66
|
+
...(deps.shutdownTimeoutMs !== undefined
|
|
67
|
+
? { shutdownTimeoutMs: deps.shutdownTimeoutMs }
|
|
68
|
+
: {}),
|
|
71
69
|
};
|
|
72
70
|
}
|
|
73
71
|
export function resolvePackageIdentity(pkg) {
|
|
@@ -6,17 +6,24 @@ function isRecord(value) {
|
|
|
6
6
|
function hasClose(value) {
|
|
7
7
|
return isRecord(value) && typeof value.close === 'function';
|
|
8
8
|
}
|
|
9
|
-
async function
|
|
9
|
+
async function closeSafely(value) {
|
|
10
|
+
try {
|
|
11
|
+
if (hasClose(value))
|
|
12
|
+
await value.close();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function closeAllWithinTimeout(values, timeoutMs) {
|
|
10
19
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
11
|
-
const attempt = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
})();
|
|
20
|
+
const attempt = Promise.allSettled(values.map((value) => closeSafely(value)))
|
|
21
|
+
.then(() => {
|
|
22
|
+
return;
|
|
23
|
+
})
|
|
24
|
+
.catch(() => {
|
|
25
|
+
return;
|
|
26
|
+
});
|
|
20
27
|
await Promise.race([attempt, timeout]);
|
|
21
28
|
}
|
|
22
29
|
function buildShutdownRunner(deps, proc) {
|
|
@@ -33,9 +40,7 @@ function buildShutdownRunner(deps, proc) {
|
|
|
33
40
|
ts: timestamp(),
|
|
34
41
|
signal,
|
|
35
42
|
});
|
|
36
|
-
await
|
|
37
|
-
await closeWithTimeout(deps.engine, timeoutMs);
|
|
38
|
-
await closeWithTimeout(deps.transport, timeoutMs);
|
|
43
|
+
await closeAllWithinTimeout([deps.server, deps.engine, deps.transport], timeoutMs);
|
|
39
44
|
proc.exit(0);
|
|
40
45
|
};
|
|
41
46
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
|
|
3
|
-
export type CloseFn = () => Promise<void> | void;
|
|
2
|
+
export type { CloseFn, EngineLike } from '../lib/types.js';
|
|
4
3
|
export type ProcessLike = Pick<typeof process, 'on' | 'exit'>;
|
|
5
4
|
export type TransportLike = Parameters<McpServer['connect']>[0];
|
|
6
|
-
export type ServerLike = Pick<McpServer, 'connect' | 'registerTool'>;
|
|
7
|
-
export type EngineLike = Pick<ThinkingEngine, 'processThought'> & {
|
|
8
|
-
close?: CloseFn;
|
|
9
|
-
};
|
|
5
|
+
export type ServerLike = Pick<McpServer, 'connect' | 'registerTool' | 'registerResource' | 'sendLoggingMessage'>;
|
package/dist/engine/revision.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
export function resolveRevisionTarget(input, getThoughtByNumber) {
|
|
2
|
-
const
|
|
3
|
-
if (
|
|
4
|
-
return
|
|
2
|
+
const targetNumber = input.revisesThought;
|
|
3
|
+
if (targetNumber === undefined) {
|
|
4
|
+
return {
|
|
5
|
+
ok: false,
|
|
6
|
+
error: buildRevisionError('E_REVISION_MISSING', 'revisesThought is required for revision'),
|
|
7
|
+
};
|
|
5
8
|
}
|
|
6
|
-
const validationError = validateRevisionTarget(getThoughtByNumber,
|
|
9
|
+
const validationError = validateRevisionTarget(getThoughtByNumber, targetNumber);
|
|
7
10
|
if (validationError) {
|
|
8
11
|
return { ok: false, error: validationError };
|
|
9
12
|
}
|
|
10
|
-
return { ok: true, targetNumber
|
|
11
|
-
}
|
|
12
|
-
function getRevisionTargetNumber(input) {
|
|
13
|
-
const targetNumber = input.revisesThought;
|
|
14
|
-
if (targetNumber !== undefined) {
|
|
15
|
-
return { ok: true, targetNumber };
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
ok: false,
|
|
19
|
-
error: buildRevisionError('E_REVISION_MISSING', 'revisesThought is required for revision'),
|
|
20
|
-
};
|
|
13
|
+
return { ok: true, targetNumber };
|
|
21
14
|
}
|
|
22
15
|
function validateRevisionTarget(getThoughtByNumber, targetNumber) {
|
|
23
16
|
const target = getThoughtByNumber(targetNumber);
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
function getRecentActiveThoughts(activeThoughts, limit) {
|
|
2
|
-
if (limit <= 0)
|
|
3
|
-
return [];
|
|
4
|
-
return activeThoughts.slice(-limit);
|
|
5
|
-
}
|
|
6
1
|
export function buildContextSummary(activeThoughts, revisionInfo) {
|
|
7
|
-
const recent =
|
|
2
|
+
const recent = activeThoughts.slice(-5);
|
|
8
3
|
const startIndex = activeThoughts.length - recent.length;
|
|
9
4
|
const recentThoughts = recent.map((thought, index) => ({
|
|
10
5
|
stepIndex: startIndex + index + 1,
|
|
@@ -4,6 +4,7 @@ export class ThoughtStore {
|
|
|
4
4
|
#thoughtIndex = new Map();
|
|
5
5
|
#activeThoughts = [];
|
|
6
6
|
#activeThoughtNumbers = [];
|
|
7
|
+
#activeMaxTotalThoughts = 0;
|
|
7
8
|
#headIndex = 0;
|
|
8
9
|
#nextThoughtNumber = 1;
|
|
9
10
|
#estimatedBytes = 0;
|
|
@@ -18,9 +19,10 @@ export class ThoughtStore {
|
|
|
18
19
|
nextThoughtNumbers(totalThoughts) {
|
|
19
20
|
const thoughtNumber = this.#nextThoughtNumber;
|
|
20
21
|
this.#nextThoughtNumber += 1;
|
|
22
|
+
const effectiveTotalThoughts = Math.max(totalThoughts, thoughtNumber, this.#activeMaxTotalThoughts);
|
|
21
23
|
return {
|
|
22
24
|
thoughtNumber,
|
|
23
|
-
totalThoughts:
|
|
25
|
+
totalThoughts: effectiveTotalThoughts,
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
storeThought(stored) {
|
|
@@ -29,9 +31,17 @@ export class ThoughtStore {
|
|
|
29
31
|
if (stored.isActive) {
|
|
30
32
|
this.#activeThoughts.push(stored);
|
|
31
33
|
this.#activeThoughtNumbers.push(stored.thoughtNumber);
|
|
34
|
+
this.#activeMaxTotalThoughts = Math.max(this.#activeMaxTotalThoughts, stored.totalThoughts);
|
|
32
35
|
}
|
|
33
36
|
this.#estimatedBytes += this.#estimateThoughtBytes(stored);
|
|
34
37
|
}
|
|
38
|
+
#recomputeActiveMaxTotalThoughts() {
|
|
39
|
+
let maxTotal = 0;
|
|
40
|
+
for (const thought of this.#activeThoughts) {
|
|
41
|
+
maxTotal = Math.max(maxTotal, thought.totalThoughts);
|
|
42
|
+
}
|
|
43
|
+
this.#activeMaxTotalThoughts = maxTotal;
|
|
44
|
+
}
|
|
35
45
|
#findActiveThoughtIndex(thoughtNumber) {
|
|
36
46
|
const activeThoughtNumbers = this.#activeThoughtNumbers;
|
|
37
47
|
let low = 0;
|
|
@@ -71,6 +81,7 @@ export class ThoughtStore {
|
|
|
71
81
|
}
|
|
72
82
|
this.#activeThoughts.length = startIndex;
|
|
73
83
|
this.#activeThoughtNumbers.length = startIndex;
|
|
84
|
+
this.#recomputeActiveMaxTotalThoughts();
|
|
74
85
|
return supersedes;
|
|
75
86
|
}
|
|
76
87
|
getActiveThoughts() {
|
|
@@ -114,6 +125,7 @@ export class ThoughtStore {
|
|
|
114
125
|
return;
|
|
115
126
|
this.#activeThoughts = this.#activeThoughts.slice(startIndex);
|
|
116
127
|
this.#activeThoughtNumbers = this.#activeThoughtNumbers.slice(startIndex);
|
|
128
|
+
this.#recomputeActiveMaxTotalThoughts();
|
|
117
129
|
}
|
|
118
130
|
#removeOldest(count, options = {}) {
|
|
119
131
|
const totalLength = this.getTotalLength();
|
|
@@ -147,27 +159,24 @@ export class ThoughtStore {
|
|
|
147
159
|
#compactIfNeeded(force = false) {
|
|
148
160
|
if (this.#headIndex === 0)
|
|
149
161
|
return;
|
|
150
|
-
|
|
151
|
-
if (totalLength === 0) {
|
|
162
|
+
if (this.getTotalLength() === 0) {
|
|
152
163
|
this.#resetThoughts();
|
|
153
164
|
return;
|
|
154
165
|
}
|
|
155
|
-
if (!
|
|
166
|
+
if (!force &&
|
|
167
|
+
this.#headIndex < COMPACT_THRESHOLD &&
|
|
168
|
+
this.#headIndex < this.#thoughts.length * COMPACT_RATIO) {
|
|
156
169
|
return;
|
|
170
|
+
}
|
|
157
171
|
this.#thoughts = this.#thoughts.slice(this.#headIndex);
|
|
158
172
|
this.#headIndex = 0;
|
|
159
173
|
}
|
|
160
|
-
#shouldCompact(force) {
|
|
161
|
-
if (force)
|
|
162
|
-
return true;
|
|
163
|
-
return (this.#headIndex >= COMPACT_THRESHOLD ||
|
|
164
|
-
this.#headIndex >= this.#thoughts.length * COMPACT_RATIO);
|
|
165
|
-
}
|
|
166
174
|
#resetThoughts() {
|
|
167
175
|
this.#thoughts = [];
|
|
168
176
|
this.#thoughtIndex.clear();
|
|
169
177
|
this.#activeThoughts = [];
|
|
170
178
|
this.#activeThoughtNumbers = [];
|
|
179
|
+
this.#activeMaxTotalThoughts = 0;
|
|
171
180
|
this.#headIndex = 0;
|
|
172
181
|
this.#nextThoughtNumber = 1;
|
|
173
182
|
this.#estimatedBytes = 0;
|
package/dist/engine.js
CHANGED
|
@@ -24,11 +24,8 @@ export class ThinkingEngine {
|
|
|
24
24
|
return this.#processNewThought(input);
|
|
25
25
|
}
|
|
26
26
|
#processNewThought(input) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const stored = this.#buildStoredThought(input, numbers);
|
|
30
|
-
this.#store.storeThought(stored);
|
|
31
|
-
this.#store.pruneHistoryIfNeeded();
|
|
27
|
+
const { stored } = this.#createStoredThought(input);
|
|
28
|
+
this.#commitThought(stored);
|
|
32
29
|
return this.#buildProcessResult(stored);
|
|
33
30
|
}
|
|
34
31
|
#processRevision(input) {
|
|
@@ -36,18 +33,14 @@ export class ThinkingEngine {
|
|
|
36
33
|
if (!resolved.ok)
|
|
37
34
|
return resolved.error;
|
|
38
35
|
const { targetNumber } = resolved;
|
|
39
|
-
const
|
|
40
|
-
|
|
36
|
+
const { numbers, stored } = this.#createStoredThought(input, {
|
|
37
|
+
revisionOf: targetNumber,
|
|
38
|
+
});
|
|
41
39
|
const supersedesAll = this.#store.supersedeFrom(targetNumber, numbers.thoughtNumber);
|
|
42
40
|
const supersedesTotal = supersedesAll.length;
|
|
43
41
|
const supersedes = capArrayStart(supersedesAll, MAX_SUPERSEDES);
|
|
44
|
-
const stored = this.#buildStoredThought(input, {
|
|
45
|
-
...numbers,
|
|
46
|
-
revisionOf: targetNumber,
|
|
47
|
-
});
|
|
48
|
-
this.#store.storeThought(stored);
|
|
49
42
|
this.#hasRevisions = true;
|
|
50
|
-
this.#
|
|
43
|
+
this.#commitThought(stored);
|
|
51
44
|
return this.#buildProcessResult(stored, {
|
|
52
45
|
revises: targetNumber,
|
|
53
46
|
supersedes,
|
|
@@ -69,14 +62,26 @@ export class ThinkingEngine {
|
|
|
69
62
|
}),
|
|
70
63
|
};
|
|
71
64
|
}
|
|
65
|
+
#createStoredThought(input, extras = {}) {
|
|
66
|
+
const effectiveTotalThoughts = this.#resolveEffectiveTotalThoughts(input);
|
|
67
|
+
const numbers = this.#store.nextThoughtNumbers(effectiveTotalThoughts);
|
|
68
|
+
const stored = this.#buildStoredThought(input, {
|
|
69
|
+
...numbers,
|
|
70
|
+
...extras,
|
|
71
|
+
});
|
|
72
|
+
return { stored, numbers };
|
|
73
|
+
}
|
|
74
|
+
#commitThought(stored) {
|
|
75
|
+
this.#store.storeThought(stored);
|
|
76
|
+
this.#store.pruneHistoryIfNeeded();
|
|
77
|
+
}
|
|
72
78
|
#resolveEffectiveTotalThoughts(input) {
|
|
73
79
|
if (input.totalThoughts !== undefined) {
|
|
74
80
|
return input.totalThoughts;
|
|
75
81
|
}
|
|
76
82
|
const activeThoughts = this.#store.getActiveThoughts();
|
|
77
83
|
const lastActive = activeThoughts[activeThoughts.length - 1];
|
|
78
|
-
if (lastActive !== undefined
|
|
79
|
-
lastActive.totalThoughts > _a.DEFAULT_TOTAL_THOUGHTS) {
|
|
84
|
+
if (lastActive !== undefined) {
|
|
80
85
|
return lastActive.totalThoughts;
|
|
81
86
|
}
|
|
82
87
|
return _a.DEFAULT_TOTAL_THOUGHTS;
|
package/dist/instructions.md
CHANGED
|
@@ -1,63 +1,40 @@
|
|
|
1
|
-
# ThinkSeq MCP Server
|
|
1
|
+
# ThinkSeq MCP Server Instructions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Guidance for the Agent:** These instructions are available as a resource (`internal://instructions`). Load them when you are confused about tool usage.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 1. Core Capability
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- If request is vague, ask clarifying questions.
|
|
7
|
+
- **Domain:** In-memory, sequential thinking with revision (destructive rewind).
|
|
8
|
+
- **Primary Resources:** `Thoughts`, `RevisionChain`, `ProgressContext`.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
## 2. The "Golden Path" Workflows (Critical)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
- **Action:** Use `thinkseq` to advance the reasoning chain or `revisesThought` to rewind and correct.
|
|
12
|
+
_Follow this order; do not guess indices._
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
### Workflow A: Capture a reasoning chain
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
1. Call `thinkseq` with `thought` (and optionally `totalThoughts`).
|
|
17
|
+
2. Continue calling `thinkseq` for each step.
|
|
18
|
+
3. Read `progress` and `isComplete` from the response to know when to stop.
|
|
19
|
+
> **Constraint:** Keep each step atomic; one decision per call.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
### Workflow B: Revise a prior step
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
1. Call `thinkseq` to get the latest `revisableThoughts` list.
|
|
24
|
+
2. Call `thinkseq` again with `revisesThought` set to a valid entry.
|
|
25
|
+
3. Continue from the revised step.
|
|
26
|
+
> **Constraint:** Never guess `revisesThought`; always pick from `revisableThoughts`.
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
thinkseq(thought="Plan: 1. check, 2. fix", totalThoughts=5) → Start chain
|
|
26
|
-
thinkseq(thought="Check passed, starting fix") → Progress chain
|
|
27
|
-
thinkseq(thought="Revised plan: use new API", revisesThought=1) → Correction
|
|
28
|
-
```
|
|
28
|
+
## 3. Tool Nuances & "Gotchas"
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
- **`thinkseq`**:
|
|
31
|
+
- **Side Effects:** Mutates in-memory thought history (write operation).
|
|
32
|
+
- **Limits:** `thought` max 5000 chars; `totalThoughts` max 25.
|
|
33
|
+
- **Revisions:** Revising a thought supersedes that step and all later active steps.
|
|
34
|
+
- **Compatibility:** Set `THINKSEQ_INCLUDE_TEXT_CONTENT=0|false|no|off` to omit the JSON string in `content`.
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
## 4. Error Handling Strategy
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
- **Args:**
|
|
38
|
-
- `thought` (string, required): Your current thinking step.
|
|
39
|
-
- `totalThoughts` (number, optional): Estimated total thoughts (1-25, default: 3).
|
|
40
|
-
- `revisesThought` (number, optional): Revise a previous thought by number.
|
|
41
|
-
- **Returns:** `thoughtNumber`, `progress`, `isComplete`, `revisableThoughts`, `context` (history previews).
|
|
42
|
-
|
|
43
|
-
## Response Shape
|
|
44
|
-
|
|
45
|
-
Success: `{ "ok": true, "result": { ... } }`
|
|
46
|
-
Error: `{ "ok": false, "error": { "code": "...", "message": "..." } }`
|
|
47
|
-
|
|
48
|
-
### Common Errors
|
|
49
|
-
|
|
50
|
-
| Code | Meaning | Resolution |
|
|
51
|
-
| ------------------------------ | -------------------------------------- | ----------------------------------------- |
|
|
52
|
-
| `E_REVISION_TARGET_NOT_FOUND` | Revision target ID does not exist | Check `revisableThoughts` for valid IDs |
|
|
53
|
-
| `E_REVISION_TARGET_SUPERSEDED` | Target thought was already overwritten | Revise the current active thought instead |
|
|
54
|
-
| `E_THINK` | Generic engine error | Check arguments and retry |
|
|
55
|
-
|
|
56
|
-
## Limits
|
|
57
|
-
|
|
58
|
-
- **Max Thoughts:** 25 (default estimate)
|
|
59
|
-
- **Max Length:** 5000 chars per thought
|
|
60
|
-
|
|
61
|
-
## Security
|
|
62
|
-
|
|
63
|
-
- Do not store credentials, secrets, or PII in thoughts. State is in-memory only but may be logged.
|
|
38
|
+
- If `E_REVISION_TARGET_NOT_FOUND`, fetch a fresh `revisableThoughts` list and retry.
|
|
39
|
+
- If `E_REVISION_TARGET_SUPERSEDED`, revise the latest active thought instead.
|
|
40
|
+
- If `E_THINK`, verify inputs and retry once.
|
package/dist/lib/cli.js
CHANGED
|
@@ -20,11 +20,24 @@ Options:
|
|
|
20
20
|
--package-read-timeout-ms <number> Package.json read timeout
|
|
21
21
|
-h, --help Show this help`;
|
|
22
22
|
const BYTES_PER_MB = 1024 * 1024;
|
|
23
|
+
const CLI_OPTION_SPECS = [
|
|
24
|
+
{ key: 'max-thoughts', configKey: 'maxThoughts' },
|
|
25
|
+
{
|
|
26
|
+
key: 'max-memory-mb',
|
|
27
|
+
configKey: 'maxMemoryBytes',
|
|
28
|
+
map: (value) => value * BYTES_PER_MB,
|
|
29
|
+
},
|
|
30
|
+
{ key: 'shutdown-timeout-ms', configKey: 'shutdownTimeoutMs' },
|
|
31
|
+
{ key: 'package-read-timeout-ms', configKey: 'packageReadTimeoutMs' },
|
|
32
|
+
];
|
|
23
33
|
function parsePositiveInt(value, label) {
|
|
24
34
|
if (value === undefined)
|
|
25
35
|
return undefined;
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
if (!/^\d+$/.test(value)) {
|
|
37
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
|
|
28
41
|
throw new Error(`Invalid ${label}: ${value}`);
|
|
29
42
|
}
|
|
30
43
|
return parsed;
|
|
@@ -40,21 +53,14 @@ function getStringOption(values, key) {
|
|
|
40
53
|
return typeof value === 'string' ? value : undefined;
|
|
41
54
|
}
|
|
42
55
|
function buildCliConfig(values) {
|
|
43
|
-
const maxThoughts = parsePositiveInt(getStringOption(values, 'max-thoughts'), 'max-thoughts');
|
|
44
|
-
const maxMemoryMb = parsePositiveInt(getStringOption(values, 'max-memory-mb'), 'max-memory-mb');
|
|
45
|
-
const shutdownTimeoutMs = parsePositiveInt(getStringOption(values, 'shutdown-timeout-ms'), 'shutdown-timeout-ms');
|
|
46
|
-
const packageReadTimeoutMs = parsePositiveInt(getStringOption(values, 'package-read-timeout-ms'), 'package-read-timeout-ms');
|
|
47
56
|
const config = {};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
config.
|
|
55
|
-
}
|
|
56
|
-
if (packageReadTimeoutMs !== undefined) {
|
|
57
|
-
config.packageReadTimeoutMs = packageReadTimeoutMs;
|
|
57
|
+
for (const spec of CLI_OPTION_SPECS) {
|
|
58
|
+
const raw = getStringOption(values, spec.key);
|
|
59
|
+
const parsed = parsePositiveInt(raw, spec.key);
|
|
60
|
+
if (parsed === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const mapped = spec.map ? spec.map(parsed) : parsed;
|
|
63
|
+
config[spec.configKey] = mapped;
|
|
58
64
|
}
|
|
59
65
|
return config;
|
|
60
66
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LoggingMessageNotificationParams } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
export interface LoggingTarget {
|
|
3
|
+
sendLoggingMessage: (params: LoggingMessageNotificationParams, sessionId?: string) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function installMcpLogging(target: LoggingTarget): () => void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import diagnosticsChannel from 'node:diagnostics_channel';
|
|
2
|
+
const TOOL_LOGGER = 'thinkseq.tool';
|
|
3
|
+
const LIFECYCLE_LOGGER = 'thinkseq.lifecycle';
|
|
4
|
+
const toolChannel = diagnosticsChannel.channel('thinkseq:tool');
|
|
5
|
+
const lifecycleChannel = diagnosticsChannel.channel('thinkseq:lifecycle');
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null;
|
|
8
|
+
}
|
|
9
|
+
function isToolEvent(value) {
|
|
10
|
+
if (!isRecord(value))
|
|
11
|
+
return false;
|
|
12
|
+
const { type } = value;
|
|
13
|
+
if (type !== 'tool.start' && type !== 'tool.end')
|
|
14
|
+
return false;
|
|
15
|
+
return value.tool === 'thinkseq';
|
|
16
|
+
}
|
|
17
|
+
function isLifecycleEvent(value) {
|
|
18
|
+
if (!isRecord(value))
|
|
19
|
+
return false;
|
|
20
|
+
return (value.type === 'lifecycle.started' || value.type === 'lifecycle.shutdown');
|
|
21
|
+
}
|
|
22
|
+
function sendLog(target, params) {
|
|
23
|
+
void target.sendLoggingMessage(params).catch(() => {
|
|
24
|
+
return;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function toolEventToLog(event) {
|
|
28
|
+
const level = event.type === 'tool.end' && !event.ok ? 'error' : 'info';
|
|
29
|
+
return {
|
|
30
|
+
level,
|
|
31
|
+
logger: TOOL_LOGGER,
|
|
32
|
+
data: event,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function lifecycleEventToLog(event) {
|
|
36
|
+
const level = event.type === 'lifecycle.shutdown' ? 'notice' : 'info';
|
|
37
|
+
return {
|
|
38
|
+
level,
|
|
39
|
+
logger: LIFECYCLE_LOGGER,
|
|
40
|
+
data: event,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function installMcpLogging(target) {
|
|
44
|
+
const onTool = (event) => {
|
|
45
|
+
if (!isToolEvent(event))
|
|
46
|
+
return;
|
|
47
|
+
sendLog(target, toolEventToLog(event));
|
|
48
|
+
};
|
|
49
|
+
const onLifecycle = (event) => {
|
|
50
|
+
if (!isLifecycleEvent(event))
|
|
51
|
+
return;
|
|
52
|
+
sendLog(target, lifecycleEventToLog(event));
|
|
53
|
+
};
|
|
54
|
+
toolChannel.subscribe(onTool);
|
|
55
|
+
lifecycleChannel.subscribe(onLifecycle);
|
|
56
|
+
return () => {
|
|
57
|
+
toolChannel.unsubscribe(onTool);
|
|
58
|
+
lifecycleChannel.unsubscribe(onLifecycle);
|
|
59
|
+
};
|
|
60
|
+
}
|
package/dist/lib/package.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
const defaultReadFile = (path, options) => readFile(path, options);
|
|
5
|
-
const defaultCwd = () => process.cwd();
|
|
6
5
|
const defaultPackageJsonPath = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
7
6
|
function isRecord(value) {
|
|
8
7
|
return typeof value === 'object' && value !== null;
|
|
@@ -30,25 +29,16 @@ function parsePackageJson(raw) {
|
|
|
30
29
|
return {};
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
function resolveReadFile(deps) {
|
|
34
|
-
return deps?.readFile ?? defaultReadFile;
|
|
35
|
-
}
|
|
36
|
-
function resolveCwd(deps) {
|
|
37
|
-
return deps?.cwd ?? defaultCwd;
|
|
38
|
-
}
|
|
39
32
|
function resolvePackageJsonPath(deps) {
|
|
40
33
|
// Only honor cwd injection when readFile is also injected (test seam).
|
|
41
34
|
if (deps?.readFile && deps.cwd)
|
|
42
|
-
return join(
|
|
35
|
+
return join(deps.cwd(), 'package.json');
|
|
43
36
|
return defaultPackageJsonPath;
|
|
44
37
|
}
|
|
45
|
-
function buildReadOptions(signal) {
|
|
46
|
-
return signal ? { encoding: 'utf8', signal } : { encoding: 'utf8' };
|
|
47
|
-
}
|
|
48
38
|
export async function readSelfPackageJson(signal, deps) {
|
|
49
39
|
try {
|
|
50
|
-
const readFileImpl =
|
|
51
|
-
const raw = await readFileImpl(resolvePackageJsonPath(deps),
|
|
40
|
+
const readFileImpl = deps?.readFile ?? defaultReadFile;
|
|
41
|
+
const raw = await readFileImpl(resolvePackageJsonPath(deps), signal ? { encoding: 'utf8', signal } : { encoding: 'utf8' });
|
|
52
42
|
return parsePackageJson(raw);
|
|
53
43
|
}
|
|
54
44
|
catch {
|