@mknrt/autotests-overkill 1.2.3 → 1.2.6

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.
@@ -1,11 +1,46 @@
1
- import { createRuntimeAppContext } from '../appContext.js';
2
- import { runMcpServer } from '../mcp/server.js';
3
1
  import { formatRuntimeStartupError } from './runtimeStartupError.js';
4
2
  import { isDirectExecution } from './shared.js';
3
+ let sqliteWarningSuppressionInstalled = false;
5
4
  export async function startMcpServer() {
5
+ suppressSqliteExperimentalWarning();
6
+ const [{ createRuntimeAppContext }, { runMcpServer }] = await Promise.all([
7
+ import('../appContext.js'),
8
+ import('../mcp/server.js'),
9
+ ]);
6
10
  const context = await createRuntimeAppContext();
7
11
  await runMcpServer(context);
8
12
  }
13
+ function suppressSqliteExperimentalWarning() {
14
+ if (sqliteWarningSuppressionInstalled) {
15
+ return;
16
+ }
17
+ sqliteWarningSuppressionInstalled = true;
18
+ const originalEmitWarning = process.emitWarning.bind(process);
19
+ process.emitWarning = ((warning, ...args) => {
20
+ if (isSqliteExperimentalWarning(warning, args)) {
21
+ return;
22
+ }
23
+ originalEmitWarning(warning, ...args);
24
+ });
25
+ }
26
+ function isSqliteExperimentalWarning(warning, args) {
27
+ const message = warning instanceof Error ? warning.message : warning;
28
+ const type = warning instanceof Error
29
+ ? warning.name
30
+ : readWarningType(args[0]);
31
+ return type === 'ExperimentalWarning'
32
+ && message.includes('SQLite is an experimental feature');
33
+ }
34
+ function readWarningType(value) {
35
+ if (typeof value === 'string') {
36
+ return value;
37
+ }
38
+ if (value && typeof value === 'object' && 'type' in value) {
39
+ const { type } = value;
40
+ return typeof type === 'string' ? type : undefined;
41
+ }
42
+ return undefined;
43
+ }
9
44
  if (isDirectExecution(import.meta.url)) {
10
45
  try {
11
46
  await startMcpServer();
@@ -0,0 +1,23 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
3
+ import { type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
4
+ /**
5
+ * Accepts both SDK newline-delimited stdio JSON-RPC and Content-Length framed MCP messages.
6
+ */
7
+ export declare class FramingAwareStdioServerTransport implements Transport {
8
+ private readonly stdin;
9
+ private readonly stdout;
10
+ private readonly readBuffer;
11
+ private started;
12
+ private responseFraming;
13
+ onclose?: () => void;
14
+ onerror?: (error: Error) => void;
15
+ onmessage?: (message: JSONRPCMessage) => void;
16
+ constructor(stdin?: Readable, stdout?: Writable);
17
+ private readonly ondata;
18
+ private readonly onstreamerror;
19
+ start(): Promise<void>;
20
+ close(): Promise<void>;
21
+ send(message: JSONRPCMessage): Promise<void>;
22
+ private processReadBuffer;
23
+ }
@@ -0,0 +1,152 @@
1
+ import process from 'node:process';
2
+ import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
3
+ class FramingAwareReadBuffer {
4
+ buffer;
5
+ append(chunk) {
6
+ this.buffer = this.buffer ? Buffer.concat([this.buffer, chunk]) : chunk;
7
+ }
8
+ clear() {
9
+ this.buffer = undefined;
10
+ }
11
+ readMessage() {
12
+ if (!this.buffer || this.buffer.length === 0) {
13
+ return null;
14
+ }
15
+ if (startsWithContentLengthHeader(this.buffer)) {
16
+ return this.readContentLengthMessage();
17
+ }
18
+ return this.readNewlineMessage();
19
+ }
20
+ readNewlineMessage() {
21
+ if (!this.buffer) {
22
+ return null;
23
+ }
24
+ const newlineIndex = this.buffer.indexOf('\n');
25
+ if (newlineIndex === -1) {
26
+ return null;
27
+ }
28
+ const line = this.buffer.toString('utf8', 0, newlineIndex).replace(/\r$/, '');
29
+ this.buffer = this.buffer.subarray(newlineIndex + 1);
30
+ return {
31
+ message: deserializeMessage(line),
32
+ framing: 'newline',
33
+ };
34
+ }
35
+ readContentLengthMessage() {
36
+ if (!this.buffer) {
37
+ return null;
38
+ }
39
+ const header = findHeader(this.buffer);
40
+ if (!header) {
41
+ return null;
42
+ }
43
+ const headerText = this.buffer.toString('utf8', 0, header.endIndex);
44
+ const contentLength = parseContentLength(headerText);
45
+ if (contentLength === undefined) {
46
+ this.buffer = this.buffer.subarray(header.endIndex + header.separatorLength);
47
+ throw new Error('Invalid MCP stdio Content-Length header');
48
+ }
49
+ const bodyStart = header.endIndex + header.separatorLength;
50
+ const bodyEnd = bodyStart + contentLength;
51
+ if (this.buffer.length < bodyEnd) {
52
+ return null;
53
+ }
54
+ const body = this.buffer.toString('utf8', bodyStart, bodyEnd);
55
+ this.buffer = this.buffer.subarray(bodyEnd);
56
+ return {
57
+ message: deserializeMessage(body),
58
+ framing: 'content-length',
59
+ };
60
+ }
61
+ }
62
+ /**
63
+ * Accepts both SDK newline-delimited stdio JSON-RPC and Content-Length framed MCP messages.
64
+ */
65
+ export class FramingAwareStdioServerTransport {
66
+ stdin;
67
+ stdout;
68
+ readBuffer = new FramingAwareReadBuffer();
69
+ started = false;
70
+ responseFraming = 'newline';
71
+ onclose;
72
+ onerror;
73
+ onmessage;
74
+ constructor(stdin = process.stdin, stdout = process.stdout) {
75
+ this.stdin = stdin;
76
+ this.stdout = stdout;
77
+ }
78
+ ondata = (chunk) => {
79
+ this.readBuffer.append(chunk);
80
+ this.processReadBuffer();
81
+ };
82
+ onstreamerror = (error) => {
83
+ this.onerror?.(error);
84
+ };
85
+ async start() {
86
+ if (this.started) {
87
+ throw new Error('FramingAwareStdioServerTransport already started');
88
+ }
89
+ this.started = true;
90
+ this.stdin.on('data', this.ondata);
91
+ this.stdin.on('error', this.onstreamerror);
92
+ }
93
+ async close() {
94
+ this.stdin.off('data', this.ondata);
95
+ this.stdin.off('error', this.onstreamerror);
96
+ if (this.stdin.listenerCount('data') === 0) {
97
+ this.stdin.pause();
98
+ }
99
+ this.readBuffer.clear();
100
+ this.onclose?.();
101
+ }
102
+ send(message) {
103
+ return new Promise((resolve) => {
104
+ const json = JSON.stringify(message);
105
+ const output = this.responseFraming === 'content-length'
106
+ ? `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`
107
+ : `${json}\n`;
108
+ if (this.stdout.write(output)) {
109
+ resolve();
110
+ }
111
+ else {
112
+ this.stdout.once('drain', resolve);
113
+ }
114
+ });
115
+ }
116
+ processReadBuffer() {
117
+ while (true) {
118
+ try {
119
+ const parsed = this.readBuffer.readMessage();
120
+ if (parsed === null) {
121
+ break;
122
+ }
123
+ this.responseFraming = parsed.framing;
124
+ this.onmessage?.(parsed.message);
125
+ }
126
+ catch (error) {
127
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
128
+ }
129
+ }
130
+ }
131
+ }
132
+ function deserializeMessage(json) {
133
+ return JSONRPCMessageSchema.parse(JSON.parse(json));
134
+ }
135
+ function startsWithContentLengthHeader(buffer) {
136
+ return buffer.subarray(0, 'Content-Length:'.length).toString('utf8').toLowerCase() === 'content-length:';
137
+ }
138
+ function findHeader(buffer) {
139
+ const crlfIndex = buffer.indexOf('\r\n\r\n');
140
+ if (crlfIndex !== -1) {
141
+ return { endIndex: crlfIndex, separatorLength: 4 };
142
+ }
143
+ const lfIndex = buffer.indexOf('\n\n');
144
+ if (lfIndex !== -1) {
145
+ return { endIndex: lfIndex, separatorLength: 2 };
146
+ }
147
+ return undefined;
148
+ }
149
+ function parseContentLength(header) {
150
+ const match = /^Content-Length:\s*(\d+)\s*$/im.exec(header);
151
+ return match ? Number(match[1]) : undefined;
152
+ }
@@ -1,11 +1,13 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ import { ErrorCode, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
+ import { FramingAwareStdioServerTransport } from './framingAwareStdioTransport.js';
3
4
  import { buildToolRegistry } from './registerTools.js';
4
5
  export async function createMcpServer(context) {
5
6
  const server = new McpServer({
6
7
  name: 'autotests-ultimate-testing-overkill',
7
8
  version: '1.1.0',
8
9
  });
10
+ registerEmptyResourceHandlers(server);
9
11
  for (const tool of buildToolRegistry()) {
10
12
  server.registerTool(tool.name, {
11
13
  title: tool.name,
@@ -22,8 +24,24 @@ export async function createMcpServer(context) {
22
24
  }
23
25
  return server;
24
26
  }
27
+ function registerEmptyResourceHandlers(server) {
28
+ server.server.registerCapabilities({
29
+ resources: {
30
+ listChanged: true,
31
+ },
32
+ });
33
+ server.server.setRequestHandler(ListResourcesRequestSchema, () => ({
34
+ resources: [],
35
+ }));
36
+ server.server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
37
+ resourceTemplates: [],
38
+ }));
39
+ server.server.setRequestHandler(ReadResourceRequestSchema, (request) => {
40
+ throw new McpError(ErrorCode.InvalidParams, `Resource ${request.params.uri} not found`);
41
+ });
42
+ }
25
43
  export async function runMcpServer(context) {
26
44
  const server = await createMcpServer(context);
27
- const transport = new StdioServerTransport();
45
+ const transport = new FramingAwareStdioServerTransport();
28
46
  await server.connect(transport);
29
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mknrt/autotests-overkill",
3
- "version": "1.2.3",
3
+ "version": "1.2.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "AI-assisted testing intelligence platform for autotests2, caseplatform-web, and CI artifacts",