@j0hanz/thinkseq-mcp 2.0.0 → 2.2.0

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/app.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import process from 'node:process';
1
3
  import { resolvePackageIdentity, resolveRunDependencies, } from './appConfig/runDependencies.js';
2
4
  import { buildShutdownDependencies } from './appConfig/shutdown.js';
5
+ import { runWithContext } from './lib/context.js';
3
6
  import { installConsoleBridge, installMcpLogging } from './lib/mcpLogging.js';
4
7
  const toError = (value) => value instanceof Error ? value : new Error(String(value));
5
8
  const createExit = (proc, exit) => exit ?? ((code) => proc.exit(code));
@@ -8,6 +11,19 @@ const createHandlerFor = (logError, exit) => (label) => (value) => {
8
11
  logError(`thinkseq: ${label}: ${error.message}`);
9
12
  exit(1);
10
13
  };
14
+ async function getLocalIconData() {
15
+ try {
16
+ const iconPath = new URL('../assets/logo.svg', import.meta.url);
17
+ const buffer = await readFile(iconPath);
18
+ if (buffer.length > 2 * 1024 * 1024) {
19
+ console.warn('Warning: logo.svg is larger than 2MB');
20
+ }
21
+ return `data:image/svg+xml;base64,${buffer.toString('base64')}`;
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
11
27
  export function installProcessErrorHandlers(deps = {}) {
12
28
  const proc = deps.processLike ?? process;
13
29
  const logError = deps.logError ?? console.error;
@@ -20,16 +36,19 @@ export async function run(deps = {}) {
20
36
  const resolved = resolveRunDependencies(deps);
21
37
  const pkg = await resolved.readPackageJson(AbortSignal.timeout(resolved.packageReadTimeoutMs));
22
38
  const { name, version } = resolvePackageIdentity(pkg);
23
- const server = resolved.createServer(name, version);
39
+ const localIcon = await getLocalIconData();
40
+ const server = resolved.createServer(name, version, localIcon);
24
41
  installMcpLogging(server);
25
42
  const { flush: flushConsole, restore: restoreConsole } = installConsoleBridge(server);
26
43
  process.on('exit', restoreConsole);
27
- resolved.publishLifecycleEvent({
28
- type: 'lifecycle.started',
29
- ts: resolved.now(),
30
- });
44
+ runWithContext(() => {
45
+ resolved.publishLifecycleEvent({
46
+ type: 'lifecycle.started',
47
+ ts: resolved.now(),
48
+ });
49
+ }, { requestId: 'lifecycle.started' });
31
50
  const engine = resolved.engineFactory();
32
- resolved.registerTool(server, engine);
51
+ resolved.registerTool(server, engine, localIcon);
33
52
  const transport = await resolved.connectServer(server);
34
53
  flushConsole();
35
54
  resolved.installShutdownHandlers(buildShutdownDependencies(resolved, { server, engine, transport }));
@@ -0,0 +1,3 @@
1
+ export declare const APP_ENV: {
2
+ readonly INCLUDE_TEXT_CONTENT: boolean;
3
+ };
@@ -0,0 +1,9 @@
1
+ import process from 'node:process';
2
+ const FALSY_ENV_VALUES = new Set(['0', 'false', 'no', 'off']);
3
+ function resolveIncludeTextContent() {
4
+ const raw = process.env.THINKSEQ_INCLUDE_TEXT_CONTENT;
5
+ return raw === undefined || !FALSY_ENV_VALUES.has(raw.trim().toLowerCase());
6
+ }
7
+ export const APP_ENV = {
8
+ INCLUDE_TEXT_CONTENT: resolveIncludeTextContent(),
9
+ };
@@ -8,9 +8,9 @@ export interface RunDependencies {
8
8
  shutdownTimeoutMs?: number;
9
9
  readPackageJson?: (signal?: AbortSignal) => Promise<PackageInfo>;
10
10
  publishLifecycleEvent?: (event: LifecycleEvent) => void;
11
- createServer?: (name: string, version: string) => ServerLike;
11
+ createServer?: (name: string, version: string, icon?: string) => ServerLike;
12
12
  connectServer?: (server: ServerLike, createTransport?: () => TransportLike) => Promise<TransportLike>;
13
- registerTool?: (server: ServerLike, engine: EngineLike) => void;
13
+ registerTool?: (server: ServerLike, engine: EngineLike, icon?: string) => void;
14
14
  engineFactory?: () => EngineLike;
15
15
  installShutdownHandlers?: (deps: ShutdownDependencies) => void;
16
16
  now?: () => number;
@@ -21,9 +21,9 @@ export interface ResolvedRunDependencies {
21
21
  shutdownTimeoutMs?: number;
22
22
  readPackageJson: (signal?: AbortSignal) => Promise<PackageInfo>;
23
23
  publishLifecycleEvent: (event: LifecycleEvent) => void;
24
- createServer: (name: string, version: string) => ServerLike;
24
+ createServer: (name: string, version: string, icon?: string) => ServerLike;
25
25
  connectServer: (server: ServerLike, createTransport?: () => TransportLike) => Promise<TransportLike>;
26
- registerTool: (server: ServerLike, engine: EngineLike) => void;
26
+ registerTool: (server: ServerLike, engine: EngineLike, icon?: string) => void;
27
27
  engineFactory: () => EngineLike;
28
28
  installShutdownHandlers: (deps: ShutdownDependencies) => void;
29
29
  now: () => number;
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { readFile } from 'node:fs/promises';
2
2
  import { McpServer, ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { ThinkingEngine } from '../engine.js';
@@ -9,55 +9,68 @@ import { registerThinkSeq } from '../tools/thinkseq.js';
9
9
  import { installShutdownHandlers } from './shutdown.js';
10
10
  const INSTRUCTIONS_URL = new URL('../instructions.md', import.meta.url);
11
11
  const INSTRUCTIONS_FALLBACK = 'ThinkSeq is a tool for structured, sequential thinking with revision support.';
12
- function readInstructionsText() {
12
+ async function readInstructionsText() {
13
13
  try {
14
- return readFileSync(INSTRUCTIONS_URL, { encoding: 'utf8' });
14
+ return await readFile(INSTRUCTIONS_URL, { encoding: 'utf8' });
15
15
  }
16
16
  catch {
17
17
  return INSTRUCTIONS_FALLBACK;
18
18
  }
19
19
  }
20
- function loadServerInstructions() {
21
- const raw = readInstructionsText();
20
+ function loadServerInstructions(raw) {
22
21
  const trimmed = raw.trim();
23
22
  return trimmed.length > 0 ? trimmed : INSTRUCTIONS_FALLBACK;
24
23
  }
25
- function registerInstructionsResource(server) {
26
- server.registerResource('instructions', new ResourceTemplate('internal://instructions', {
27
- list: () => ({
28
- resources: [
29
- {
30
- uri: 'internal://instructions',
31
- name: 'Instructions',
32
- mimeType: 'text/markdown',
33
- },
34
- ],
35
- }),
36
- }), { title: 'Instructions', mimeType: 'text/markdown' }, (uri) => ({
37
- contents: [
24
+ const INSTRUCTIONS_RESOURCE_TEMPLATE = new ResourceTemplate('internal://instructions', {
25
+ list: () => ({
26
+ resources: [
38
27
  {
39
- uri: uri.href,
40
- text: readInstructionsText(),
28
+ uri: 'internal://instructions',
29
+ name: 'Instructions',
41
30
  mimeType: 'text/markdown',
42
31
  },
43
32
  ],
44
- }));
33
+ }),
34
+ });
35
+ const INSTRUCTIONS_METADATA = {
36
+ title: 'Instructions',
37
+ mimeType: 'text/markdown',
38
+ };
39
+ const readInstructionsResource = (uri) => ({
40
+ contents: [
41
+ {
42
+ uri: uri.href,
43
+ text: INSTRUCTIONS_TEXT,
44
+ mimeType: 'text/markdown',
45
+ },
46
+ ],
47
+ });
48
+ function registerInstructionsResource(server) {
49
+ server.registerResource('instructions', INSTRUCTIONS_RESOURCE_TEMPLATE, INSTRUCTIONS_METADATA, readInstructionsResource);
45
50
  }
46
- const SERVER_INSTRUCTIONS = loadServerInstructions();
51
+ const INSTRUCTIONS_TEXT = await readInstructionsText();
52
+ const SERVER_INSTRUCTIONS = loadServerInstructions(INSTRUCTIONS_TEXT);
47
53
  const DEFAULT_PACKAGE_READ_TIMEOUT_MS = 2000;
48
- function buildServerConfig() {
54
+ function buildServerCapabilities(overrides = {}) {
49
55
  return {
50
- instructions: SERVER_INSTRUCTIONS,
51
- capabilities: {
52
- logging: {},
53
- tools: { listChanged: false },
54
- resources: { subscribe: false, listChanged: false },
55
- prompts: { listChanged: false },
56
- },
56
+ logging: {},
57
+ tools: { listChanged: false },
58
+ resources: { subscribe: false, listChanged: false },
59
+ prompts: { listChanged: false },
60
+ ...overrides,
57
61
  };
58
62
  }
59
- const defaultCreateServer = (name, version) => {
60
- const server = new McpServer({ name, version }, buildServerConfig());
63
+ const defaultCreateServer = (name, version, icon) => {
64
+ const capabilities = buildServerCapabilities();
65
+ const server = new McpServer({ name, version }, {
66
+ instructions: SERVER_INSTRUCTIONS,
67
+ capabilities,
68
+ ...(icon
69
+ ? {
70
+ icons: [{ src: icon, mimeType: 'image/svg+xml', sizes: ['any'] }],
71
+ }
72
+ : {}),
73
+ });
61
74
  registerInstructionsResource(server);
62
75
  server.registerPrompt('get-help', {
63
76
  description: 'Get usage instructions for this server',
@@ -1,3 +1,4 @@
1
+ import { runWithContext } from '../lib/context.js';
1
2
  import { publishLifecycleEvent } from '../lib/diagnostics.js';
2
3
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
3
4
  function isRecord(value) {
@@ -35,11 +36,13 @@ function buildShutdownRunner(deps, proc) {
35
36
  if (shuttingDown)
36
37
  return;
37
38
  shuttingDown = true;
38
- emit({
39
- type: 'lifecycle.shutdown',
40
- ts: timestamp(),
41
- signal,
42
- });
39
+ runWithContext(() => {
40
+ emit({
41
+ type: 'lifecycle.shutdown',
42
+ ts: timestamp(),
43
+ signal,
44
+ });
45
+ }, { requestId: `lifecycle.shutdown:${signal}` });
43
46
  await closeAllWithinTimeout([deps.server, deps.engine, deps.transport], timeoutMs);
44
47
  proc.exit(0);
45
48
  };