@mknrt/autotests-overkill 1.2.3 → 1.2.5
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
|
+
}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import {
|
|
2
|
+
import { FramingAwareStdioServerTransport } from './framingAwareStdioTransport.js';
|
|
3
3
|
import { buildToolRegistry } from './registerTools.js';
|
|
4
4
|
export async function createMcpServer(context) {
|
|
5
5
|
const server = new McpServer({
|
|
@@ -24,6 +24,6 @@ export async function createMcpServer(context) {
|
|
|
24
24
|
}
|
|
25
25
|
export async function runMcpServer(context) {
|
|
26
26
|
const server = await createMcpServer(context);
|
|
27
|
-
const transport = new
|
|
27
|
+
const transport = new FramingAwareStdioServerTransport();
|
|
28
28
|
await server.connect(transport);
|
|
29
29
|
}
|