@mp-consulting/homebridge-ai-kit 2.0.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/README.md +273 -0
- package/config.schema.json +131 -0
- package/dist/agent/run-agent.d.ts +61 -0
- package/dist/agent/run-agent.d.ts.map +1 -0
- package/dist/agent/run-agent.js +92 -0
- package/dist/agent/run-agent.js.map +1 -0
- package/dist/bin/homebridge-ai-kit.d.ts +3 -0
- package/dist/bin/homebridge-ai-kit.d.ts.map +1 -0
- package/dist/bin/homebridge-ai-kit.js +61 -0
- package/dist/bin/homebridge-ai-kit.js.map +1 -0
- package/dist/bin/homebridge-mcp-server.d.ts +3 -0
- package/dist/bin/homebridge-mcp-server.d.ts.map +1 -0
- package/dist/bin/homebridge-mcp-server.js +5 -0
- package/dist/bin/homebridge-mcp-server.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/create-server.d.ts +17 -0
- package/dist/mcp/create-server.d.ts.map +1 -0
- package/dist/mcp/create-server.js +42 -0
- package/dist/mcp/create-server.js.map +1 -0
- package/dist/mcp/homebridge-client.d.ts +105 -0
- package/dist/mcp/homebridge-client.d.ts.map +1 -0
- package/dist/mcp/homebridge-client.js +341 -0
- package/dist/mcp/homebridge-client.js.map +1 -0
- package/dist/mcp/http.d.ts +26 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +124 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/live.d.ts +46 -0
- package/dist/mcp/live.d.ts.map +1 -0
- package/dist/mcp/live.js +105 -0
- package/dist/mcp/live.js.map +1 -0
- package/dist/mcp/prompts.d.ts +4 -0
- package/dist/mcp/prompts.d.ts.map +1 -0
- package/dist/mcp/prompts.js +21 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/regex-search.d.ts +11 -0
- package/dist/mcp/regex-search.d.ts.map +1 -0
- package/dist/mcp/regex-search.js +53 -0
- package/dist/mcp/regex-search.js.map +1 -0
- package/dist/mcp/resources.d.ts +16 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +65 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/stdio.d.ts +11 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +56 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools/accessories.d.ts +15 -0
- package/dist/mcp/tools/accessories.d.ts.map +1 -0
- package/dist/mcp/tools/accessories.js +179 -0
- package/dist/mcp/tools/accessories.js.map +1 -0
- package/dist/mcp/tools/child-bridges.d.ts +3 -0
- package/dist/mcp/tools/child-bridges.d.ts.map +1 -0
- package/dist/mcp/tools/child-bridges.js +28 -0
- package/dist/mcp/tools/child-bridges.js.map +1 -0
- package/dist/mcp/tools/config.d.ts +7 -0
- package/dist/mcp/tools/config.d.ts.map +1 -0
- package/dist/mcp/tools/config.js +111 -0
- package/dist/mcp/tools/config.js.map +1 -0
- package/dist/mcp/tools/helpers.d.ts +31 -0
- package/dist/mcp/tools/helpers.d.ts.map +1 -0
- package/dist/mcp/tools/helpers.js +49 -0
- package/dist/mcp/tools/helpers.js.map +1 -0
- package/dist/mcp/tools/logs.d.ts +7 -0
- package/dist/mcp/tools/logs.d.ts.map +1 -0
- package/dist/mcp/tools/logs.js +122 -0
- package/dist/mcp/tools/logs.js.map +1 -0
- package/dist/mcp/tools/plugin-jobs.d.ts +11 -0
- package/dist/mcp/tools/plugin-jobs.d.ts.map +1 -0
- package/dist/mcp/tools/plugin-jobs.js +75 -0
- package/dist/mcp/tools/plugin-jobs.js.map +1 -0
- package/dist/mcp/tools/plugins.d.ts +3 -0
- package/dist/mcp/tools/plugins.d.ts.map +1 -0
- package/dist/mcp/tools/plugins.js +74 -0
- package/dist/mcp/tools/plugins.js.map +1 -0
- package/dist/mcp/tools/server.d.ts +3 -0
- package/dist/mcp/tools/server.d.ts.map +1 -0
- package/dist/mcp/tools/server.js +69 -0
- package/dist/mcp/tools/server.js.map +1 -0
- package/dist/mcp/tools/system.d.ts +3 -0
- package/dist/mcp/tools/system.d.ts.map +1 -0
- package/dist/mcp/tools/system.js +9 -0
- package/dist/mcp/tools/system.js.map +1 -0
- package/dist/mcp/types.d.ts +75 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/plugin/index.d.ts +7 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +5 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/platform.d.ts +28 -0
- package/dist/plugin/platform.d.ts.map +1 -0
- package/dist/plugin/platform.js +76 -0
- package/dist/plugin/platform.js.map +1 -0
- package/dist/plugin/snippets.d.ts +27 -0
- package/dist/plugin/snippets.d.ts.map +1 -0
- package/dist/plugin/snippets.js +33 -0
- package/dist/plugin/snippets.js.map +1 -0
- package/homebridge-ui/public/index.html +164 -0
- package/homebridge-ui/public/js/app.js +200 -0
- package/homebridge-ui/public/lib/ai.css +738 -0
- package/homebridge-ui/public/lib/bootstrap-icons.min.css +5 -0
- package/homebridge-ui/public/lib/bootstrap.bundle.min.js +6 -0
- package/homebridge-ui/public/lib/bootstrap.min.css +5 -0
- package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff +0 -0
- package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff2 +0 -0
- package/homebridge-ui/public/lib/kit.css +994 -0
- package/homebridge-ui/public/lib/kit.js +794 -0
- package/homebridge-ui/server.js +28 -0
- package/package.json +94 -0
package/dist/mcp/http.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for outside MCP clients. Every request needs
|
|
3
|
+
* `Authorization: Bearer <token>`; each MCP session gets its own server.
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID, timingSafeEqual, createHash } from 'node:crypto';
|
|
6
|
+
import { createServer as createHttpServer } from 'node:http';
|
|
7
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
8
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { createServer } from './create-server.js';
|
|
10
|
+
export const MCP_PATH = '/mcp';
|
|
11
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
12
|
+
function sameToken(given, expected) {
|
|
13
|
+
// Hash both so the comparison is constant-time regardless of length.
|
|
14
|
+
const a = createHash('sha256').update(given).digest();
|
|
15
|
+
const b = createHash('sha256').update(expected).digest();
|
|
16
|
+
return timingSafeEqual(a, b);
|
|
17
|
+
}
|
|
18
|
+
function sendJson(res, status, body, headers = {}) {
|
|
19
|
+
res.writeHead(status, { 'Content-Type': 'application/json', ...headers }).end(JSON.stringify(body));
|
|
20
|
+
}
|
|
21
|
+
function rpcError(res, status, message, headers) {
|
|
22
|
+
sendJson(res, status, { jsonrpc: '2.0', error: { code: -32000, message }, id: null }, headers);
|
|
23
|
+
}
|
|
24
|
+
async function readBody(req) {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let size = 0;
|
|
27
|
+
for await (const chunk of req) {
|
|
28
|
+
size += chunk.length;
|
|
29
|
+
if (size > MAX_BODY_BYTES) {
|
|
30
|
+
throw new Error('Request body too large');
|
|
31
|
+
}
|
|
32
|
+
chunks.push(chunk);
|
|
33
|
+
}
|
|
34
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
35
|
+
return text ? JSON.parse(text) : undefined;
|
|
36
|
+
}
|
|
37
|
+
export async function runHttpServer(options) {
|
|
38
|
+
const { token, client } = options;
|
|
39
|
+
if (!token) {
|
|
40
|
+
throw new Error('A bearer token is required to serve MCP over HTTP (set HOMEBRIDGE_AI_MCP_TOKEN)');
|
|
41
|
+
}
|
|
42
|
+
const host = options.host ?? '127.0.0.1';
|
|
43
|
+
const transports = new Map();
|
|
44
|
+
const handle = async (req, res) => {
|
|
45
|
+
const path = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
46
|
+
if (path !== MCP_PATH) {
|
|
47
|
+
return rpcError(res, 404, `Not found; the MCP endpoint is ${MCP_PATH}`);
|
|
48
|
+
}
|
|
49
|
+
const auth = req.headers.authorization ?? '';
|
|
50
|
+
const given = /^Bearer\s+(.+)$/i.exec(auth)?.[1]?.trim() ?? '';
|
|
51
|
+
if (!given || !sameToken(given, token)) {
|
|
52
|
+
return rpcError(res, 401, 'Unauthorized', { 'WWW-Authenticate': 'Bearer realm="homebridge-ai-kit"' });
|
|
53
|
+
}
|
|
54
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
55
|
+
const existing = typeof sessionId === 'string' ? transports.get(sessionId) : undefined;
|
|
56
|
+
if (req.method === 'POST') {
|
|
57
|
+
let body;
|
|
58
|
+
try {
|
|
59
|
+
body = await readBody(req);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return rpcError(res, 400, `Invalid request body: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
if (existing) {
|
|
65
|
+
return existing.handleRequest(req, res, body);
|
|
66
|
+
}
|
|
67
|
+
if (sessionId !== undefined || !isInitializeRequest(body)) {
|
|
68
|
+
return rpcError(res, sessionId === undefined ? 400 : 404, sessionId === undefined ? 'Missing mcp-session-id' : 'Unknown session');
|
|
69
|
+
}
|
|
70
|
+
const transport = new StreamableHTTPServerTransport({
|
|
71
|
+
sessionIdGenerator: () => randomUUID(),
|
|
72
|
+
onsessioninitialized: (id) => {
|
|
73
|
+
transports.set(id, transport);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const server = createServer(client, { readOnly: options.readOnly, live: options.live });
|
|
77
|
+
transport.onclose = () => {
|
|
78
|
+
if (transport.sessionId) {
|
|
79
|
+
transports.delete(transport.sessionId);
|
|
80
|
+
}
|
|
81
|
+
void server.close();
|
|
82
|
+
};
|
|
83
|
+
await server.connect(transport);
|
|
84
|
+
return transport.handleRequest(req, res, body);
|
|
85
|
+
}
|
|
86
|
+
if (req.method === 'GET' || req.method === 'DELETE') {
|
|
87
|
+
if (!existing) {
|
|
88
|
+
return rpcError(res, sessionId === undefined ? 400 : 404, sessionId === undefined ? 'Missing mcp-session-id' : 'Unknown session');
|
|
89
|
+
}
|
|
90
|
+
return existing.handleRequest(req, res);
|
|
91
|
+
}
|
|
92
|
+
return rpcError(res, 405, 'Method not allowed', { Allow: 'GET, POST, DELETE' });
|
|
93
|
+
};
|
|
94
|
+
const server = createHttpServer((req, res) => {
|
|
95
|
+
handle(req, res).catch((error) => {
|
|
96
|
+
if (!res.headersSent) {
|
|
97
|
+
rpcError(res, 500, error.message);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
await new Promise((resolve, reject) => {
|
|
105
|
+
server.once('error', reject);
|
|
106
|
+
server.listen(options.port ?? 8582, host, () => {
|
|
107
|
+
server.off('error', reject);
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
const address = server.address();
|
|
112
|
+
const urlHost = address.family === 'IPv6' ? `[${address.address}]` : address.address;
|
|
113
|
+
return {
|
|
114
|
+
server,
|
|
115
|
+
url: `http://${urlHost}:${address.port}${MCP_PATH}`,
|
|
116
|
+
async close() {
|
|
117
|
+
await Promise.all([...transports.values()].map((t) => t.close()));
|
|
118
|
+
transports.clear();
|
|
119
|
+
server.closeAllConnections();
|
|
120
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/mcp/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { VERSION, createServer, envFlag } from './create-server.js';
|
|
2
|
+
export type { ServerOptions } from './create-server.js';
|
|
3
|
+
export { HomebridgeClient } from './homebridge-client.js';
|
|
4
|
+
export type { HomebridgeClientOptions, LogTail } from './homebridge-client.js';
|
|
5
|
+
export { runHttpFromEnv, runStdioServer } from './stdio.js';
|
|
6
|
+
export { MCP_PATH, runHttpServer } from './http.js';
|
|
7
|
+
export type { HttpServerOptions, RunningHttpServer } from './http.js';
|
|
8
|
+
export { LIVE_TOPICS, createLiveSource } from './live.js';
|
|
9
|
+
export type { LiveOptions, LiveSource, LiveTopic, LiveWatch } from './live.js';
|
|
10
|
+
export { RESOURCE_URIS } from './resources.js';
|
|
11
|
+
export type { Accessory, CharacteristicInfo, ChildBridge, PluginJob, Room } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACpD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { VERSION, createServer, envFlag } from './create-server.js';
|
|
2
|
+
export { HomebridgeClient } from './homebridge-client.js';
|
|
3
|
+
export { runHttpFromEnv, runStdioServer } from './stdio.js';
|
|
4
|
+
export { MCP_PATH, runHttpServer } from './http.js';
|
|
5
|
+
export { LIVE_TOPICS, createLiveSource } from './live.js';
|
|
6
|
+
export { RESOURCE_URIS } from './resources.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change notifications for the MCP resources. Listens on the Homebridge UI's
|
|
3
|
+
* socket.io namespaces (`/accessories`, `/log`, `/status`); when the socket
|
|
4
|
+
* cannot connect (e.g. the UI does not accept the token on sockets) it falls
|
|
5
|
+
* back to polling the REST API and comparing snapshots.
|
|
6
|
+
*/
|
|
7
|
+
import type { HomebridgeClient } from './homebridge-client.js';
|
|
8
|
+
export type LiveTopic = 'accessories' | 'log' | 'status';
|
|
9
|
+
export interface LiveWatch {
|
|
10
|
+
close(): void;
|
|
11
|
+
}
|
|
12
|
+
/** Starts watching `topic`, calling `onChange` (throttled) whenever it changes. */
|
|
13
|
+
export type LiveSource = (topic: LiveTopic, onChange: () => void) => LiveWatch;
|
|
14
|
+
/** The subset of a socket.io client socket this module uses. */
|
|
15
|
+
export interface LiveSocket {
|
|
16
|
+
on(event: string, listener: (...args: unknown[]) => void): unknown;
|
|
17
|
+
emit(event: string, ...args: unknown[]): unknown;
|
|
18
|
+
close(): unknown;
|
|
19
|
+
}
|
|
20
|
+
export type SocketConnect = (url: string, options: {
|
|
21
|
+
auth: {
|
|
22
|
+
token: string;
|
|
23
|
+
};
|
|
24
|
+
transports: string[];
|
|
25
|
+
reconnectionDelayMax: number;
|
|
26
|
+
}) => LiveSocket;
|
|
27
|
+
interface TopicSpec {
|
|
28
|
+
/** Message that asks the namespace to start streaming. */
|
|
29
|
+
start: [string, ...unknown[]];
|
|
30
|
+
/** Event that carries updates. */
|
|
31
|
+
event: string;
|
|
32
|
+
pollMs: number;
|
|
33
|
+
}
|
|
34
|
+
/** Event names from the Glass UI (and homebridge-config-ui-x) gateways. */
|
|
35
|
+
export declare const LIVE_TOPICS: Record<LiveTopic, TopicSpec>;
|
|
36
|
+
export interface LiveOptions {
|
|
37
|
+
/** socket.io client factory (tests inject a fake). */
|
|
38
|
+
connect?: SocketConnect;
|
|
39
|
+
/** Minimum time between two notifications for one topic. */
|
|
40
|
+
throttleMs?: number;
|
|
41
|
+
/** Override the polling interval of every topic. */
|
|
42
|
+
pollMs?: number;
|
|
43
|
+
}
|
|
44
|
+
export declare function createLiveSource(client: HomebridgeClient, options?: LiveOptions): LiveSource;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/mcp/live.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;AAE/E,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IACnE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACjD,KAAK,IAAI,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE,KAAK,UAAU,CAAC;AAElJ,UAAU,SAAS;IACjB,0DAA0D;IAC1D,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC9B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,CAIpD,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAkFhG"}
|
package/dist/mcp/live.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change notifications for the MCP resources. Listens on the Homebridge UI's
|
|
3
|
+
* socket.io namespaces (`/accessories`, `/log`, `/status`); when the socket
|
|
4
|
+
* cannot connect (e.g. the UI does not accept the token on sockets) it falls
|
|
5
|
+
* back to polling the REST API and comparing snapshots.
|
|
6
|
+
*/
|
|
7
|
+
import { io } from 'socket.io-client';
|
|
8
|
+
/** Event names from the Glass UI (and homebridge-config-ui-x) gateways. */
|
|
9
|
+
export const LIVE_TOPICS = {
|
|
10
|
+
accessories: { start: ['get-accessories'], event: 'accessories-data', pollMs: 10_000 },
|
|
11
|
+
log: { start: ['tail-log', { cols: 200, rows: 50 }], event: 'stdout', pollMs: 5_000 },
|
|
12
|
+
status: { start: ['monitor-server-status'], event: 'homebridge-status', pollMs: 15_000 },
|
|
13
|
+
};
|
|
14
|
+
/** A snapshot of a topic used to detect changes while polling. */
|
|
15
|
+
async function fingerprint(client, topic) {
|
|
16
|
+
switch (topic) {
|
|
17
|
+
case 'accessories':
|
|
18
|
+
return JSON.stringify((await client.getAccessories()).map((a) => [a.uniqueId, a.values]));
|
|
19
|
+
case 'log':
|
|
20
|
+
return (await client.getLogTail(8192)).text;
|
|
21
|
+
case 'status':
|
|
22
|
+
return JSON.stringify(await client.getHomebridgeStatus());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function createLiveSource(client, options = {}) {
|
|
26
|
+
const connect = options.connect ?? io;
|
|
27
|
+
const throttleMs = options.throttleMs ?? 1000;
|
|
28
|
+
return (topic, onChange) => {
|
|
29
|
+
const spec = LIVE_TOPICS[topic];
|
|
30
|
+
let closed = false;
|
|
31
|
+
let socket;
|
|
32
|
+
let poller;
|
|
33
|
+
let pending;
|
|
34
|
+
let lastNotified = 0;
|
|
35
|
+
const notify = () => {
|
|
36
|
+
if (closed || pending) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const wait = lastNotified + throttleMs - Date.now();
|
|
40
|
+
const fire = () => {
|
|
41
|
+
pending = undefined;
|
|
42
|
+
lastNotified = Date.now();
|
|
43
|
+
if (!closed) {
|
|
44
|
+
onChange();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
if (wait <= 0) {
|
|
48
|
+
fire();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
pending = setTimeout(fire, wait);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const startPolling = async () => {
|
|
55
|
+
if (closed || poller) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let previous = await fingerprint(client, topic).catch(() => undefined);
|
|
59
|
+
if (closed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
poller = setInterval(async () => {
|
|
63
|
+
const current = await fingerprint(client, topic).catch(() => undefined);
|
|
64
|
+
if (current !== undefined && previous !== undefined && current !== previous) {
|
|
65
|
+
notify();
|
|
66
|
+
}
|
|
67
|
+
previous = current ?? previous;
|
|
68
|
+
}, options.pollMs ?? spec.pollMs);
|
|
69
|
+
};
|
|
70
|
+
void (async () => {
|
|
71
|
+
let connected = false;
|
|
72
|
+
try {
|
|
73
|
+
const token = await client.accessToken();
|
|
74
|
+
if (closed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
socket = connect(`${client.url}/${topic}`, { auth: { token }, transports: ['websocket'], reconnectionDelayMax: 30_000 });
|
|
78
|
+
socket.on('connect', () => {
|
|
79
|
+
connected = true;
|
|
80
|
+
socket.emit(...spec.start);
|
|
81
|
+
});
|
|
82
|
+
socket.on(spec.event, notify);
|
|
83
|
+
socket.on('connect_error', () => {
|
|
84
|
+
if (!connected) {
|
|
85
|
+
socket?.close();
|
|
86
|
+
socket = undefined;
|
|
87
|
+
void startPolling();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
void startPolling();
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
return {
|
|
96
|
+
close() {
|
|
97
|
+
closed = true;
|
|
98
|
+
socket?.close();
|
|
99
|
+
clearInterval(poller);
|
|
100
|
+
clearTimeout(pending);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/mcp/live.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/mcp/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4BvD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { PROMPTS } from '@mp-consulting/homebridge-ai-core';
|
|
3
|
+
function message(text) {
|
|
4
|
+
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
|
|
5
|
+
}
|
|
6
|
+
/** MCP prompts sharing their text with the built-in Assistant's templates. */
|
|
7
|
+
export function registerPrompts(server) {
|
|
8
|
+
const p = PROMPTS.mcp;
|
|
9
|
+
server.registerPrompt('diagnose-logs', {
|
|
10
|
+
title: p['diagnose-logs'].title,
|
|
11
|
+
description: p['diagnose-logs'].description,
|
|
12
|
+
argsSchema: { focus: z.string().optional().describe('A plugin, device or symptom to focus on') },
|
|
13
|
+
}, ({ focus }) => message(p['diagnose-logs'].text({ focus })));
|
|
14
|
+
server.registerPrompt('plan-upgrade', {
|
|
15
|
+
title: p['plan-upgrade'].title,
|
|
16
|
+
description: p['plan-upgrade'].description,
|
|
17
|
+
argsSchema: { plugin: z.string().optional().describe('One plugin to plan for (default: all with updates)') },
|
|
18
|
+
}, ({ plugin }) => message(p['plan-upgrade'].text({ plugin })));
|
|
19
|
+
server.registerPrompt('audit-config', { title: p['audit-config'].title, description: p['audit-config'].description }, () => message(p['audit-config'].text()));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/mcp/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAE5D,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;AAC7F,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAEtB,MAAM,CAAC,cAAc,CACnB,eAAe,EACf;QACE,KAAK,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,KAAK;QAC/B,WAAW,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,WAAW;QAC3C,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC,EAAE;KACjG,EACD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAC3D,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,KAAK,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,KAAK;QAC9B,WAAW,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;QAC1C,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC,EAAE;KAC7G,EACD,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAC5D,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,cAAc,EACd,EAAE,KAAK,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,EAC9E,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC,CACxC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class RegexTimeoutError extends Error {
|
|
2
|
+
readonly timeoutMs: number;
|
|
3
|
+
constructor(timeoutMs: number);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Indices of the lines that match `pattern`, computed off the main thread.
|
|
7
|
+
* `flags` must not contain `g` or `y` (stateful `lastIndex` would skip matches).
|
|
8
|
+
* @throws RegexTimeoutError if matching takes longer than `timeoutMs`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function regexSearch(lines: string[], pattern: string, flags: string, timeoutMs: number): Promise<number[]>;
|
|
11
|
+
//# sourceMappingURL=regex-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regex-search.d.ts","sourceRoot":"","sources":["../../src/mcp/regex-search.ts"],"names":[],"mappings":"AAuBA,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAIvC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBjH"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
/**
|
|
3
|
+
* Runs in a worker so a catastrophically backtracking pattern can be killed.
|
|
4
|
+
* A time check in the main thread can't help there: a single `re.test()` call
|
|
5
|
+
* blocks the event loop until it returns, which can take hours.
|
|
6
|
+
*
|
|
7
|
+
* Loaded from a data: URL, which Node always evaluates as an ES module, so it
|
|
8
|
+
* needs no separate file in dist/ and works the same under tsx and vitest.
|
|
9
|
+
*/
|
|
10
|
+
const WORKER_SOURCE = `
|
|
11
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
12
|
+
const { lines, pattern, flags } = workerData;
|
|
13
|
+
const re = new RegExp(pattern, flags);
|
|
14
|
+
const matches = [];
|
|
15
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16
|
+
if (re.test(lines[i])) matches.push(i);
|
|
17
|
+
}
|
|
18
|
+
parentPort.postMessage(matches);
|
|
19
|
+
`;
|
|
20
|
+
const WORKER_URL = new URL(`data:text/javascript,${encodeURIComponent(WORKER_SOURCE)}`);
|
|
21
|
+
export class RegexTimeoutError extends Error {
|
|
22
|
+
timeoutMs;
|
|
23
|
+
constructor(timeoutMs) {
|
|
24
|
+
super(`regex search exceeded ${timeoutMs}ms`);
|
|
25
|
+
this.timeoutMs = timeoutMs;
|
|
26
|
+
this.name = 'RegexTimeoutError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Indices of the lines that match `pattern`, computed off the main thread.
|
|
31
|
+
* `flags` must not contain `g` or `y` (stateful `lastIndex` would skip matches).
|
|
32
|
+
* @throws RegexTimeoutError if matching takes longer than `timeoutMs`.
|
|
33
|
+
*/
|
|
34
|
+
export function regexSearch(lines, pattern, flags, timeoutMs) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const worker = new Worker(WORKER_URL, { workerData: { lines, pattern, flags } });
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
void worker.terminate();
|
|
39
|
+
reject(new RegexTimeoutError(timeoutMs));
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
const done = () => clearTimeout(timer);
|
|
42
|
+
worker.once('message', (matches) => {
|
|
43
|
+
done();
|
|
44
|
+
void worker.terminate();
|
|
45
|
+
resolve(matches);
|
|
46
|
+
});
|
|
47
|
+
worker.once('error', (error) => {
|
|
48
|
+
done();
|
|
49
|
+
reject(error);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=regex-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regex-search.js","sourceRoot":"","sources":["../../src/mcp/regex-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG;;;;;;;;;CASrB,CAAC;AAEF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,wBAAwB,kBAAkB,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;AAExF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACrB;IAArB,YAAqB,SAAiB;QACpC,KAAK,CAAC,yBAAyB,SAAS,IAAI,CAAC,CAAC;QAD3B,cAAS,GAAT,SAAS,CAAQ;QAEpC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAe,EAAE,OAAe,EAAE,KAAa,EAAE,SAAiB;IAC5F,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACjF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3C,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,OAAiB,EAAE,EAAE;YAC3C,IAAI,EAAE,CAAC;YACP,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;YACxB,OAAO,CAAC,OAAO,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC7B,IAAI,EAAE,CAAC;YACP,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { HomebridgeClient } from './homebridge-client.js';
|
|
3
|
+
import type { LiveSource } from './live.js';
|
|
4
|
+
export declare const RESOURCE_URIS: {
|
|
5
|
+
readonly accessories: "homebridge://accessories";
|
|
6
|
+
readonly logs: "homebridge://logs/recent";
|
|
7
|
+
readonly status: "homebridge://status";
|
|
8
|
+
};
|
|
9
|
+
/** Lines of log returned by `homebridge://logs/recent`. */
|
|
10
|
+
export declare const RECENT_LOG_LINES = 200;
|
|
11
|
+
/**
|
|
12
|
+
* Registers the live resources and, when `live` is given, `resources/subscribe`
|
|
13
|
+
* support: each subscribed URI gets a watch that sends `notifications/resources/updated`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerResources(server: McpServer, client: HomebridgeClient, live: LiveSource | false): void;
|
|
16
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,WAAW,CAAC;AAElE,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAQX,2DAA2D;AAC3D,eAAO,MAAM,gBAAgB,MAAM,CAAC;AASpC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK,GAAG,IAAI,CAsE7G"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ErrorCode, McpError, SubscribeRequestSchema, UnsubscribeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
export const RESOURCE_URIS = {
|
|
3
|
+
accessories: 'homebridge://accessories',
|
|
4
|
+
logs: 'homebridge://logs/recent',
|
|
5
|
+
status: 'homebridge://status',
|
|
6
|
+
};
|
|
7
|
+
const TOPIC_BY_URI = {
|
|
8
|
+
[RESOURCE_URIS.accessories]: 'accessories',
|
|
9
|
+
[RESOURCE_URIS.logs]: 'log',
|
|
10
|
+
[RESOURCE_URIS.status]: 'status',
|
|
11
|
+
};
|
|
12
|
+
/** Lines of log returned by `homebridge://logs/recent`. */
|
|
13
|
+
export const RECENT_LOG_LINES = 200;
|
|
14
|
+
// eslint-disable-next-line no-control-regex
|
|
15
|
+
const ANSI = /\u001B\[[0-9;]*m/g;
|
|
16
|
+
function json(uri, data) {
|
|
17
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(data) }] };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Registers the live resources and, when `live` is given, `resources/subscribe`
|
|
21
|
+
* support: each subscribed URI gets a watch that sends `notifications/resources/updated`.
|
|
22
|
+
*/
|
|
23
|
+
export function registerResources(server, client, live) {
|
|
24
|
+
server.registerResource('accessories', RESOURCE_URIS.accessories, { title: 'Accessories', description: 'Every accessory with its current values. Subscribe to hear about changes.', mimeType: 'application/json' }, async (uri) => json(uri.href, (await client.getAccessories()).map((a) => ({ uniqueId: a.uniqueId, serviceName: a.serviceName, type: a.type, values: a.values ?? {} }))));
|
|
25
|
+
server.registerResource('recent-logs', RESOURCE_URIS.logs, { title: 'Recent log', description: `The last ${RECENT_LOG_LINES} Homebridge log lines. Subscribe to follow the log.`, mimeType: 'text/plain' }, async (uri) => {
|
|
26
|
+
const { text } = await client.getLogTail(64 * 1024);
|
|
27
|
+
const lines = text.replace(ANSI, '').split('\n');
|
|
28
|
+
return { contents: [{ uri: uri.href, mimeType: 'text/plain', text: lines.slice(-RECENT_LOG_LINES).join('\n') }] };
|
|
29
|
+
});
|
|
30
|
+
server.registerResource('status', RESOURCE_URIS.status, { title: 'Homebridge status', description: 'Whether Homebridge is up, and its child bridges. Subscribe to hear about changes.', mimeType: 'application/json' }, async (uri) => {
|
|
31
|
+
const [status, childBridges] = await Promise.all([client.getHomebridgeStatus(), client.getChildBridges().catch(() => undefined)]);
|
|
32
|
+
return json(uri.href, { status, childBridges });
|
|
33
|
+
});
|
|
34
|
+
if (!live) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const watches = new Map();
|
|
38
|
+
server.server.registerCapabilities({ resources: { subscribe: true } });
|
|
39
|
+
server.server.setRequestHandler(SubscribeRequestSchema, async ({ params }) => {
|
|
40
|
+
const topic = TOPIC_BY_URI[params.uri];
|
|
41
|
+
if (!topic) {
|
|
42
|
+
throw new McpError(ErrorCode.InvalidParams, `Cannot subscribe to ${params.uri}`);
|
|
43
|
+
}
|
|
44
|
+
if (!watches.has(params.uri)) {
|
|
45
|
+
watches.set(params.uri, live(topic, () => {
|
|
46
|
+
server.server.sendResourceUpdated({ uri: params.uri }).catch(() => undefined);
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return {};
|
|
50
|
+
});
|
|
51
|
+
server.server.setRequestHandler(UnsubscribeRequestSchema, async ({ params }) => {
|
|
52
|
+
watches.get(params.uri)?.close();
|
|
53
|
+
watches.delete(params.uri);
|
|
54
|
+
return {};
|
|
55
|
+
});
|
|
56
|
+
const previous = server.server.onclose;
|
|
57
|
+
server.server.onclose = () => {
|
|
58
|
+
for (const watch of watches.values()) {
|
|
59
|
+
watch.close();
|
|
60
|
+
}
|
|
61
|
+
watches.clear();
|
|
62
|
+
previous?.();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts the MCP server on stdio, configured from HOMEBRIDGE_* env vars.
|
|
3
|
+
* `prog` prefixes every message so users see the command name they actually ran.
|
|
4
|
+
*/
|
|
5
|
+
export declare function runStdioServer(prog: string): Promise<void>;
|
|
6
|
+
/** Starts the Streamable HTTP MCP server; the bearer token comes from HOMEBRIDGE_AI_MCP_TOKEN. */
|
|
7
|
+
export declare function runHttpFromEnv(prog: string, { port, host }?: {
|
|
8
|
+
port?: number;
|
|
9
|
+
host?: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhE;AAED,kGAAkG;AAClG,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBvH"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
import { HomebridgeClient } from './homebridge-client.js';
|
|
3
|
+
import { createServer, envFlag } from './create-server.js';
|
|
4
|
+
import { runHttpServer } from './http.js';
|
|
5
|
+
/** Builds the client from HOMEBRIDGE_* env vars, exiting with a readable message on bad config. */
|
|
6
|
+
function clientFromEnv(prog) {
|
|
7
|
+
let client;
|
|
8
|
+
try {
|
|
9
|
+
client = new HomebridgeClient();
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
console.error(`${prog}: ${error instanceof Error ? error.message : error}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
if (client.transportWarning) {
|
|
16
|
+
console.error(`${prog}: warning: ${client.transportWarning}`);
|
|
17
|
+
}
|
|
18
|
+
const readOnly = envFlag(process.env.HOMEBRIDGE_READ_ONLY);
|
|
19
|
+
if (readOnly) {
|
|
20
|
+
console.error(`${prog}: read-only mode, write tools are disabled`);
|
|
21
|
+
}
|
|
22
|
+
return { client, readOnly };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Starts the MCP server on stdio, configured from HOMEBRIDGE_* env vars.
|
|
26
|
+
* `prog` prefixes every message so users see the command name they actually ran.
|
|
27
|
+
*/
|
|
28
|
+
export async function runStdioServer(prog) {
|
|
29
|
+
// stdout carries the MCP protocol, so every human-facing message goes to stderr.
|
|
30
|
+
const { client, readOnly } = clientFromEnv(prog);
|
|
31
|
+
const server = createServer(client, { readOnly });
|
|
32
|
+
await server.connect(new StdioServerTransport());
|
|
33
|
+
}
|
|
34
|
+
/** Starts the Streamable HTTP MCP server; the bearer token comes from HOMEBRIDGE_AI_MCP_TOKEN. */
|
|
35
|
+
export async function runHttpFromEnv(prog, { port, host } = {}) {
|
|
36
|
+
const token = process.env.HOMEBRIDGE_AI_MCP_TOKEN?.trim();
|
|
37
|
+
if (!token) {
|
|
38
|
+
console.error(`${prog}: HOMEBRIDGE_AI_MCP_TOKEN is required with --http (clients must send it as a bearer token)`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const { client, readOnly } = clientFromEnv(prog);
|
|
42
|
+
try {
|
|
43
|
+
const running = await runHttpServer({ port, host, token, client, readOnly });
|
|
44
|
+
console.error(`${prog}: MCP over HTTP at ${running.url}`);
|
|
45
|
+
const stop = () => {
|
|
46
|
+
void running.close().then(() => process.exit(0));
|
|
47
|
+
};
|
|
48
|
+
process.once('SIGINT', stop);
|
|
49
|
+
process.once('SIGTERM', stop);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`${prog}: ${error instanceof Error ? error.message : error}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"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"}
|