@hasna/logs 0.3.24 → 0.3.25
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/LICENSE +2 -1
- package/README.md +22 -3
- package/bun.lock +15 -7
- package/dist/cli/index.js +688 -3
- package/dist/http-zm3ph78w.js +1240 -0
- package/dist/index-5cj74qka.js +10803 -0
- package/dist/index-5qznfyah.js +5868 -0
- package/dist/{index-pf8hpweg.js → index-kezb178p.js} +2 -2
- package/dist/{index-w24zm361.js → index-p1vgwwsz.js} +2 -2
- package/dist/index-pen6t0yc.js +10794 -0
- package/dist/mcp/index.js +6618 -22153
- package/dist/server/index.js +2 -2
- package/package.json +4 -4
- package/sdk/package.json +9 -4
- package/src/cli/entrypoints.test.ts +1 -1
- package/src/cli/index.ts +2 -0
- package/src/db/index.ts +1 -1
- package/src/lib/ingest.ts +3 -3
- package/src/mcp/http.test.ts +92 -0
- package/src/mcp/http.ts +135 -0
- package/src/mcp/index.ts +34 -30
- package/dist/index-75dwghvv.js +0 -625
- package/dist/index-g8f8kep6.js +0 -625
- package/src/lib/cloud-sync.ts +0 -167
- package/src/lib/remote-storage.ts +0 -45
package/dist/server/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
setPageAuth,
|
|
7
7
|
setRetentionPolicy,
|
|
8
8
|
startScheduler
|
|
9
|
-
} from "../index-
|
|
9
|
+
} from "../index-kezb178p.js";
|
|
10
10
|
import {
|
|
11
11
|
exportToCsv,
|
|
12
12
|
exportToJson
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
updateAlertRule,
|
|
38
38
|
updateIssueStatus,
|
|
39
39
|
updateProject
|
|
40
|
-
} from "../index-
|
|
40
|
+
} from "../index-pen6t0yc.js";
|
|
41
41
|
import {
|
|
42
42
|
createJob,
|
|
43
43
|
deleteJob,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/logs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,12 +40,13 @@
|
|
|
40
40
|
"author": "Andrei Hasna <andrei@hasna.com>",
|
|
41
41
|
"license": "Apache-2.0",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@
|
|
43
|
+
"@hasna/cloud": "0.1.24",
|
|
44
|
+
"@hasna/events": "^0.1.6",
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
44
46
|
"commander": "^14.0.0",
|
|
45
47
|
"hono": "^4.7.11",
|
|
46
48
|
"ink": "^5.1.0",
|
|
47
49
|
"node-cron": "^3.0.3",
|
|
48
|
-
"pg": "^8.20.0",
|
|
49
50
|
"playwright": "^1.52.0",
|
|
50
51
|
"react": "^19.1.0"
|
|
51
52
|
},
|
|
@@ -53,7 +54,6 @@
|
|
|
53
54
|
"@biomejs/biome": "^1.9.4",
|
|
54
55
|
"@types/bun": "latest",
|
|
55
56
|
"@types/node-cron": "^3.0.11",
|
|
56
|
-
"@types/pg": "^8.20.0",
|
|
57
57
|
"@types/react": "^19.1.4",
|
|
58
58
|
"typescript": "^5.9.3"
|
|
59
59
|
}
|
package/sdk/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/logs-sdk",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"description": "Zero-dependency fetch client for @hasna/logs
|
|
4
|
+
"description": "Zero-dependency fetch client for @hasna/logs \u2014 push logs, query, browse issues, perf snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
"./browser": "./dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
|
-
"access": "
|
|
13
|
+
"access": "public",
|
|
14
14
|
"registry": "https://registry.npmjs.org/"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "bun build src/index.ts --outdir dist --target browser"
|
|
18
18
|
},
|
|
19
|
-
"keywords": [
|
|
19
|
+
"keywords": [
|
|
20
|
+
"logs",
|
|
21
|
+
"monitoring",
|
|
22
|
+
"sdk",
|
|
23
|
+
"ai-agents"
|
|
24
|
+
],
|
|
20
25
|
"author": "Andrei Hasna <andrei@hasna.com>",
|
|
21
|
-
"license": "
|
|
26
|
+
"license": "Apache-2.0"
|
|
22
27
|
}
|
|
@@ -46,7 +46,7 @@ test("logs-mcp --help prints usage and exits without starting stdio transport",
|
|
|
46
46
|
|
|
47
47
|
expect(result.exitCode).toBe(0)
|
|
48
48
|
expect(result.stdout).toContain("Usage: logs-mcp [options]")
|
|
49
|
-
expect(result.stdout).toContain("Start the @hasna/logs MCP server
|
|
49
|
+
expect(result.stdout).toContain("Start the @hasna/logs MCP server (stdio by default).")
|
|
50
50
|
expect(result.stdout).not.toContain("Listening")
|
|
51
51
|
expect(result.stderr.trim()).toBe("")
|
|
52
52
|
})
|
package/src/cli/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { registerEventsCommands } from "@hasna/events/commander";
|
|
2
3
|
import { Command } from "commander"
|
|
3
4
|
import { getDb } from "../db/index.ts"
|
|
4
5
|
import { ingestLog } from "../lib/ingest.ts"
|
|
@@ -465,5 +466,6 @@ function parseRelativeTime(val?: string): string | undefined {
|
|
|
465
466
|
const ms = Number(n) * (unit === "h" ? 3600 : unit === "d" ? 86400 : 60) * 1000
|
|
466
467
|
return new Date(Date.now() - ms).toISOString()
|
|
467
468
|
}
|
|
469
|
+
registerEventsCommands(program, { source: "logs" });
|
|
468
470
|
|
|
469
471
|
program.parse()
|
package/src/db/index.ts
CHANGED
package/src/lib/ingest.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DbAdapter } from "@hasna/cloud"
|
|
2
2
|
import type { LogEntry, LogRow } from "../types/index.ts"
|
|
3
3
|
import { upsertIssue } from "./issues.ts"
|
|
4
4
|
import { evaluateAlerts } from "./alerts.ts"
|
|
@@ -47,7 +47,7 @@ export function ingestBatch(db: DbAdapter, entries: LogEntry[], sharedTraceId?:
|
|
|
47
47
|
VALUES ($project_id, $page_id, $level, $source, $service, $message, $trace_id, $session_id, $agent, $url, $stack_trace, $metadata)
|
|
48
48
|
RETURNING *
|
|
49
49
|
`)
|
|
50
|
-
//
|
|
50
|
+
// @hasna/cloud executes the callback inside the transaction immediately.
|
|
51
51
|
const rows = db.transaction(() =>
|
|
52
52
|
entries.map(entry =>
|
|
53
53
|
insert.get({
|
|
@@ -65,7 +65,7 @@ export function ingestBatch(db: DbAdapter, entries: LogEntry[], sharedTraceId?:
|
|
|
65
65
|
$metadata: entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
66
66
|
}) as LogRow
|
|
67
67
|
)
|
|
68
|
-
)
|
|
68
|
+
)
|
|
69
69
|
|
|
70
70
|
// Issue grouping for error-level entries (outside transaction for perf)
|
|
71
71
|
for (const entry of entries) {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
5
|
+
import { buildServer } from "./index.ts";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_MCP_HTTP_PORT,
|
|
8
|
+
isHttpMode,
|
|
9
|
+
resolveMcpHttpPort,
|
|
10
|
+
startMcpHttpServer,
|
|
11
|
+
} from "./http.ts";
|
|
12
|
+
|
|
13
|
+
describe("logs MCP HTTP transport", () => {
|
|
14
|
+
test("defaults port to 8864", () => {
|
|
15
|
+
expect(DEFAULT_MCP_HTTP_PORT).toBe(8864);
|
|
16
|
+
expect(resolveMcpHttpPort(["node"], {})).toBe(8864);
|
|
17
|
+
expect(resolveMcpHttpPort(["node", "--port", "9001"], {})).toBe(9001);
|
|
18
|
+
expect(resolveMcpHttpPort(["node"], { MCP_HTTP_PORT: "9002" })).toBe(9002);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("isHttpMode detects flag and env", () => {
|
|
22
|
+
expect(isHttpMode(["node"], {})).toBe(false);
|
|
23
|
+
expect(isHttpMode(["node", "--http"], {})).toBe(true);
|
|
24
|
+
expect(isHttpMode(["node"], { MCP_HTTP: "1" })).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("logs buildServer stdio registration", () => {
|
|
29
|
+
test("registers tools over in-memory transport", async () => {
|
|
30
|
+
const server = buildServer();
|
|
31
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
32
|
+
await server.connect(serverTransport);
|
|
33
|
+
|
|
34
|
+
const client = new Client({ name: "test", version: "0.0.0" });
|
|
35
|
+
await client.connect(clientTransport);
|
|
36
|
+
|
|
37
|
+
const tools = await client.listTools();
|
|
38
|
+
expect(tools.tools.some((tool) => tool.name === "get_health")).toBe(true);
|
|
39
|
+
|
|
40
|
+
await client.close();
|
|
41
|
+
await server.close();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("logs streamable HTTP server", () => {
|
|
46
|
+
let handle: Awaited<ReturnType<typeof startMcpHttpServer>>;
|
|
47
|
+
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
handle = await startMcpHttpServer(buildServer, { port: 0 });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(async () => {
|
|
53
|
+
await handle.close();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("GET /health returns ok", async () => {
|
|
57
|
+
const res = await fetch(`http://${handle.host}:${handle.port}/health`);
|
|
58
|
+
expect(res.status).toBe(200);
|
|
59
|
+
expect(await res.json()).toEqual({ status: "ok", name: "logs" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("initialize and call get_health over streamable HTTP", async () => {
|
|
63
|
+
const transport = new StreamableHTTPClientTransport(
|
|
64
|
+
new URL(`http://${handle.host}:${handle.port}/mcp`),
|
|
65
|
+
);
|
|
66
|
+
const client = new Client({ name: "test", version: "0.0.0" });
|
|
67
|
+
await client.connect(transport);
|
|
68
|
+
|
|
69
|
+
const result = await client.callTool({ name: "get_health", arguments: {} });
|
|
70
|
+
expect(result.content).toBeDefined();
|
|
71
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
72
|
+
|
|
73
|
+
await client.close();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("serves three concurrent clients from one process", async () => {
|
|
77
|
+
const clients = await Promise.all(
|
|
78
|
+
Array.from({ length: 3 }, async () => {
|
|
79
|
+
const transport = new StreamableHTTPClientTransport(
|
|
80
|
+
new URL(`http://${handle.host}:${handle.port}/mcp`),
|
|
81
|
+
);
|
|
82
|
+
const client = new Client({ name: "test", version: "0.0.0" });
|
|
83
|
+
await client.connect(transport);
|
|
84
|
+
const tools = await client.listTools();
|
|
85
|
+
return { client, count: tools.tools.length };
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(clients.every((entry) => entry.count > 0)).toBe(true);
|
|
90
|
+
await Promise.all(clients.map((entry) => entry.client.close()));
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/mcp/http.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
|
|
5
|
+
export const MCP_HTTP_SERVICE_NAME = "logs";
|
|
6
|
+
export const DEFAULT_MCP_HTTP_PORT = 8864;
|
|
7
|
+
|
|
8
|
+
export function isHttpMode(
|
|
9
|
+
argv: string[] = process.argv,
|
|
10
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
11
|
+
): boolean {
|
|
12
|
+
return argv.includes("--http") || env.MCP_HTTP === "1";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isStdioMode(
|
|
16
|
+
argv: string[] = process.argv,
|
|
17
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
18
|
+
): boolean {
|
|
19
|
+
return argv.includes("--stdio") || env.MCP_STDIO === "1";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveMcpHttpPort(
|
|
23
|
+
argv: string[] = process.argv,
|
|
24
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
25
|
+
): number {
|
|
26
|
+
const portIdx = argv.indexOf("--port");
|
|
27
|
+
if (portIdx !== -1 && argv[portIdx + 1]) {
|
|
28
|
+
return parsePort(argv[portIdx + 1]!, "--port");
|
|
29
|
+
}
|
|
30
|
+
if (env.MCP_HTTP_PORT) {
|
|
31
|
+
return parsePort(env.MCP_HTTP_PORT, "MCP_HTTP_PORT");
|
|
32
|
+
}
|
|
33
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parsePort(raw: string, source: string): number {
|
|
37
|
+
const parsed = Number(raw);
|
|
38
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
|
|
39
|
+
throw new Error(`Invalid ${source} value "${raw}". Expected 0-65535.`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function readJsonBody(req: IncomingMessage): Promise<unknown> {
|
|
45
|
+
const chunks: Buffer[] = [];
|
|
46
|
+
for await (const chunk of req) {
|
|
47
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
48
|
+
}
|
|
49
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
50
|
+
if (!text) return undefined;
|
|
51
|
+
return JSON.parse(text) as unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type McpHttpServerHandle = {
|
|
55
|
+
port: number;
|
|
56
|
+
host: string;
|
|
57
|
+
close: () => Promise<void>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export async function startMcpHttpServer(
|
|
61
|
+
buildServer: () => McpServer,
|
|
62
|
+
options?: { port?: number; host?: string; serviceName?: string },
|
|
63
|
+
): Promise<McpHttpServerHandle> {
|
|
64
|
+
const host = options?.host ?? "127.0.0.1";
|
|
65
|
+
const requestedPort = options?.port ?? resolveMcpHttpPort();
|
|
66
|
+
const serviceName = options?.serviceName ?? MCP_HTTP_SERVICE_NAME;
|
|
67
|
+
|
|
68
|
+
const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
71
|
+
|
|
72
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
73
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
74
|
+
res.end(JSON.stringify({ status: "ok", name: serviceName }));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (url.pathname !== "/mcp") {
|
|
79
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
80
|
+
res.end("Not Found");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const server = buildServer();
|
|
85
|
+
const transport = new StreamableHTTPServerTransport({
|
|
86
|
+
sessionIdGenerator: undefined,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await server.connect(transport);
|
|
90
|
+
|
|
91
|
+
let parsedBody: unknown;
|
|
92
|
+
if (req.method === "POST") {
|
|
93
|
+
parsedBody = await readJsonBody(req);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
97
|
+
|
|
98
|
+
res.on("close", () => {
|
|
99
|
+
void transport.close();
|
|
100
|
+
void server.close();
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`[${serviceName}-mcp] HTTP error:`, error);
|
|
104
|
+
if (!res.headersSent) {
|
|
105
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
106
|
+
res.end(
|
|
107
|
+
JSON.stringify({
|
|
108
|
+
jsonrpc: "2.0",
|
|
109
|
+
error: { code: -32603, message: "Internal server error" },
|
|
110
|
+
id: null,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await new Promise<void>((resolve, reject) => {
|
|
118
|
+
httpServer.once("error", reject);
|
|
119
|
+
httpServer.listen(requestedPort, host, () => resolve());
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const addr = httpServer.address();
|
|
123
|
+
const port = typeof addr === "object" && addr ? addr.port : requestedPort;
|
|
124
|
+
|
|
125
|
+
console.error(`[${serviceName}-mcp] Streamable HTTP listening on http://${host}:${port}/mcp`);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
port,
|
|
129
|
+
host,
|
|
130
|
+
close: () =>
|
|
131
|
+
new Promise<void>((resolve, reject) => {
|
|
132
|
+
httpServer.close((err) => (err ? reject(err) : resolve()));
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
135
|
+
}
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
4
|
+
import { registerCloudTools } from "@hasna/cloud"
|
|
4
5
|
import { z } from "zod"
|
|
5
6
|
import { getDb } from "../db/index.ts"
|
|
6
7
|
import { exitIfMetadataRequest, PACKAGE_VERSION } from "../lib/package-meta.ts"
|
|
@@ -19,21 +20,26 @@ import { compare } from "../lib/compare.ts"
|
|
|
19
20
|
import { getHealth } from "../lib/health.ts"
|
|
20
21
|
import { getSessionContext } from "../lib/session-context.ts"
|
|
21
22
|
import { parseTime } from "../lib/parse-time.ts"
|
|
22
|
-
import { cloudPull, cloudPush, cloudSync, getCloudDatabaseUrl, getCloudPg } from "../lib/cloud-sync.ts"
|
|
23
23
|
import type { LogLevel, LogRow } from "../types/index.ts"
|
|
24
24
|
|
|
25
25
|
exitIfMetadataRequest({
|
|
26
26
|
name: "logs-mcp",
|
|
27
|
-
description: "Start the @hasna/logs MCP server
|
|
27
|
+
description: "Start the @hasna/logs MCP server (stdio by default).",
|
|
28
|
+
options: [
|
|
29
|
+
" --http Serve MCP over Streamable HTTP (127.0.0.1)",
|
|
30
|
+
" --port <number> HTTP port (default: 8820, env: MCP_HTTP_PORT)",
|
|
31
|
+
],
|
|
28
32
|
})
|
|
29
33
|
|
|
30
34
|
const db = getDb()
|
|
31
|
-
const server = new McpServer({ name: "logs", version: PACKAGE_VERSION })
|
|
32
35
|
|
|
33
|
-
// --- in-memory agent registry ---
|
|
36
|
+
// --- in-memory agent registry (module-level for shared HTTP process) ---
|
|
34
37
|
interface _LogsAgent { id: string; name: string; session_id?: string; last_seen_at: string; project_id?: string }
|
|
35
38
|
const _logsAgents = new Map<string, _LogsAgent>()
|
|
36
39
|
|
|
40
|
+
export function buildServer(): McpServer {
|
|
41
|
+
const server = new McpServer({ name: "logs", version: PACKAGE_VERSION })
|
|
42
|
+
|
|
37
43
|
const BRIEF_FIELDS: (keyof LogRow)[] = ["id", "timestamp", "level", "message", "service"]
|
|
38
44
|
|
|
39
45
|
function applyBrief(rows: LogRow[], brief = true): unknown[] {
|
|
@@ -408,33 +414,31 @@ server.tool("list_agents", "List all registered agents.", {}, async () => {
|
|
|
408
414
|
return { content: [{ type: "text" as const, text: JSON.stringify([..._logsAgents.values()]) }] }
|
|
409
415
|
})
|
|
410
416
|
|
|
411
|
-
server
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
let cloud: Awaited<ReturnType<typeof getCloudPg>> | null = null
|
|
415
|
-
try {
|
|
416
|
-
cloud = await getCloudPg()
|
|
417
|
-
await cloud.get("SELECT 1 as ok")
|
|
418
|
-
const tables = await cloud.all("SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename") as Array<{ tablename: string }>
|
|
419
|
-
return { content: [{ type: "text" as const, text: JSON.stringify({ connected: true, tables: tables.map(row => row.tablename) }) }] }
|
|
420
|
-
} catch (error) {
|
|
421
|
-
return { content: [{ type: "text" as const, text: String(error) }], isError: true }
|
|
422
|
-
} finally {
|
|
423
|
-
if (cloud) await cloud.close().catch(() => {})
|
|
424
|
-
}
|
|
425
|
-
})
|
|
417
|
+
registerCloudTools(server, "logs")
|
|
418
|
+
return server
|
|
419
|
+
}
|
|
426
420
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
})
|
|
421
|
+
async function main(): Promise<void> {
|
|
422
|
+
const { isStdioMode, resolveMcpHttpPort, startMcpHttpServer } = await import("./http.ts")
|
|
430
423
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
424
|
+
if (isStdioMode()) {
|
|
425
|
+
const server = buildServer()
|
|
426
|
+
const transport = new StdioServerTransport()
|
|
427
|
+
await server.connect(transport)
|
|
428
|
+
return
|
|
429
|
+
}
|
|
434
430
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
431
|
+
// Default: shared Streamable HTTP server (one process per MCP, many agents).
|
|
432
|
+
const handle = await startMcpHttpServer(buildServer, {
|
|
433
|
+
port: resolveMcpHttpPort(),
|
|
434
|
+
})
|
|
435
|
+
process.on("SIGINT", () => void handle.close().finally(() => process.exit(0)))
|
|
436
|
+
process.on("SIGTERM", () => void handle.close().finally(() => process.exit(0)))
|
|
437
|
+
}
|
|
438
438
|
|
|
439
|
-
|
|
440
|
-
|
|
439
|
+
if (import.meta.main) {
|
|
440
|
+
main().catch((err) => {
|
|
441
|
+
console.error(err)
|
|
442
|
+
process.exit(1)
|
|
443
|
+
})
|
|
444
|
+
}
|