@ng-annotate/mcp-server 0.4.0 → 0.4.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/index.js +2 -0
- package/dist/store.js +1 -1
- package/dist/ws-server.d.ts +1 -0
- package/dist/ws-server.js +132 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { registerTools } from './tools.js';
|
|
4
|
+
import { startWsServer } from './ws-server.js';
|
|
4
5
|
function log(msg) {
|
|
5
6
|
process.stderr.write(`[ng-annotate-mcp] ${msg}\n`);
|
|
6
7
|
}
|
|
@@ -13,6 +14,7 @@ async function main() {
|
|
|
13
14
|
});
|
|
14
15
|
log('registering tools...');
|
|
15
16
|
registerTools(server);
|
|
17
|
+
startWsServer();
|
|
16
18
|
log('connecting stdio transport...');
|
|
17
19
|
const transport = new StdioServerTransport();
|
|
18
20
|
await server.connect(transport);
|
package/dist/store.js
CHANGED
|
@@ -11,7 +11,7 @@ const PROJECT_ROOT = process.env.NG_ANNOTATE_PROJECT_ROOT ?? process.cwd();
|
|
|
11
11
|
export const STORE_PATH = path.join(PROJECT_ROOT, STORE_DIR, 'store.json');
|
|
12
12
|
// ─── Store init ───────────────────────────────────────────────────────────────
|
|
13
13
|
export function ensureStore() {
|
|
14
|
-
const dir = path.
|
|
14
|
+
const dir = path.dirname(STORE_PATH);
|
|
15
15
|
if (!fs.existsSync(dir))
|
|
16
16
|
fs.mkdirSync(dir, { recursive: true });
|
|
17
17
|
if (!fs.existsSync(STORE_PATH)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startWsServer(): void;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
5
|
+
import { store } from './store.js';
|
|
6
|
+
const PORT = 4201;
|
|
7
|
+
const SYNC_INTERVAL_MS = 2000;
|
|
8
|
+
function buildManifest(projectRoot) {
|
|
9
|
+
const manifest = {};
|
|
10
|
+
const srcDir = path.join(projectRoot, 'src');
|
|
11
|
+
if (!fs.existsSync(srcDir))
|
|
12
|
+
return manifest;
|
|
13
|
+
function scan(dir) {
|
|
14
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
15
|
+
const fullPath = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
scan(fullPath);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!entry.name.endsWith('.ts') || entry.name.endsWith('.spec.ts'))
|
|
21
|
+
continue;
|
|
22
|
+
const code = fs.readFileSync(fullPath, 'utf8');
|
|
23
|
+
if (!code.includes('@Component'))
|
|
24
|
+
continue;
|
|
25
|
+
const classMatch = /export\s+class\s+(\w+)/.exec(code);
|
|
26
|
+
if (!classMatch)
|
|
27
|
+
continue;
|
|
28
|
+
const className = classMatch[1];
|
|
29
|
+
const relPath = path.relative(projectRoot, fullPath).replace(/\\/g, '/');
|
|
30
|
+
const item = { component: relPath };
|
|
31
|
+
const templateMatch = /templateUrl\s*:\s*['"`]([^'"`]+)['"`]/.exec(code);
|
|
32
|
+
if (templateMatch) {
|
|
33
|
+
const templateAbs = path.resolve(path.dirname(fullPath), templateMatch[1]);
|
|
34
|
+
item.template = path.relative(projectRoot, templateAbs).replace(/\\/g, '/');
|
|
35
|
+
}
|
|
36
|
+
manifest[className] = item;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
scan(srcDir);
|
|
40
|
+
return manifest;
|
|
41
|
+
}
|
|
42
|
+
function safeSend(ws, data) {
|
|
43
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
44
|
+
return;
|
|
45
|
+
ws.send(JSON.stringify(data), (err) => {
|
|
46
|
+
if (err) { /* connection closed mid-send */ }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function startWsServer() {
|
|
50
|
+
const projectRoot = process.env.NG_ANNOTATE_PROJECT_ROOT ?? process.cwd();
|
|
51
|
+
const sessionSockets = new Map();
|
|
52
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
53
|
+
wss.on('connection', (ws, req) => {
|
|
54
|
+
void (async () => {
|
|
55
|
+
const referer = req.headers.referer ?? req.headers.origin ?? '';
|
|
56
|
+
let sessionId;
|
|
57
|
+
try {
|
|
58
|
+
const session = await store.createSession({ active: true, url: referer });
|
|
59
|
+
sessionId = session.id;
|
|
60
|
+
safeSend(ws, { type: 'session:created', session });
|
|
61
|
+
const manifest = buildManifest(projectRoot);
|
|
62
|
+
safeSend(ws, { type: 'manifest:update', manifest });
|
|
63
|
+
sessionSockets.set(sessionId, ws);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
process.stderr.write(`[ng-annotate] Failed to create session: ${String(err)}\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
ws.on('message', (raw) => {
|
|
70
|
+
void (async () => {
|
|
71
|
+
let msg;
|
|
72
|
+
try {
|
|
73
|
+
msg = JSON.parse(raw.toString());
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
if (msg.type === 'annotation:create' && msg.payload) {
|
|
80
|
+
const annotation = await store.createAnnotation({
|
|
81
|
+
...msg.payload,
|
|
82
|
+
sessionId,
|
|
83
|
+
});
|
|
84
|
+
safeSend(ws, { type: 'annotation:created', annotation });
|
|
85
|
+
}
|
|
86
|
+
else if (msg.type === 'annotation:reply' && msg.id && msg.message) {
|
|
87
|
+
const annotation = await store.addReply(msg.id, { author: 'user', message: msg.message });
|
|
88
|
+
if (annotation)
|
|
89
|
+
safeSend(ws, { type: 'annotation:updated', annotation });
|
|
90
|
+
}
|
|
91
|
+
else if (msg.id) {
|
|
92
|
+
const annotation = await store.updateAnnotation(msg.id, { status: 'dismissed' });
|
|
93
|
+
if (annotation)
|
|
94
|
+
safeSend(ws, { type: 'annotation:updated', annotation });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
process.stderr.write(`[ng-annotate] Failed to process message: ${String(err)}\n`);
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
});
|
|
102
|
+
ws.on('close', () => {
|
|
103
|
+
void store.updateSession(sessionId, { active: false });
|
|
104
|
+
sessionSockets.delete(sessionId);
|
|
105
|
+
});
|
|
106
|
+
})();
|
|
107
|
+
});
|
|
108
|
+
setInterval(() => {
|
|
109
|
+
void (async () => {
|
|
110
|
+
for (const [sessionId, ws] of sessionSockets) {
|
|
111
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
112
|
+
continue;
|
|
113
|
+
const annotations = await store.listAnnotations(sessionId);
|
|
114
|
+
safeSend(ws, { type: 'annotations:sync', annotations });
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
117
|
+
}, SYNC_INTERVAL_MS);
|
|
118
|
+
const server = http.createServer((_req, res) => {
|
|
119
|
+
res.writeHead(404);
|
|
120
|
+
res.end();
|
|
121
|
+
});
|
|
122
|
+
server.on('upgrade', (req, socket, head) => {
|
|
123
|
+
if (req.url !== '/__annotate')
|
|
124
|
+
return;
|
|
125
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
126
|
+
wss.emit('connection', ws, req);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
server.listen(PORT, () => {
|
|
130
|
+
process.stderr.write(`[ng-annotate] WebSocket server listening on port ${String(PORT)}\n`);
|
|
131
|
+
});
|
|
132
|
+
}
|