@hasna/logs 0.3.14 → 0.3.16
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/mcp/index.js +45 -3
- package/package.json +4 -3
- package/src/db/pg-migrations.ts +167 -0
- package/src/mcp/index.test.ts +2 -0
- package/src/mcp/index.ts +44 -1
package/dist/mcp/index.js
CHANGED
|
@@ -6527,7 +6527,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6527
6527
|
var require_package = __commonJS((exports, module) => {
|
|
6528
6528
|
module.exports = {
|
|
6529
6529
|
name: "@hasna/logs",
|
|
6530
|
-
version: "0.3.
|
|
6530
|
+
version: "0.3.16",
|
|
6531
6531
|
description: "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
|
|
6532
6532
|
type: "module",
|
|
6533
6533
|
main: "./dist/index.js",
|
|
@@ -6544,7 +6544,8 @@ var require_package = __commonJS((exports, module) => {
|
|
|
6544
6544
|
dev: "bun run src/server/index.ts",
|
|
6545
6545
|
test: "bun test",
|
|
6546
6546
|
"test:coverage": "bun test --coverage",
|
|
6547
|
-
lint: "biome check src/"
|
|
6547
|
+
lint: "biome check src/",
|
|
6548
|
+
postinstall: "mkdir -p $HOME/.hasna/logs 2>/dev/null || true"
|
|
6548
6549
|
},
|
|
6549
6550
|
repository: {
|
|
6550
6551
|
type: "git",
|
|
@@ -23996,6 +23997,7 @@ async function getSessionContext(db, sessionId) {
|
|
|
23996
23997
|
// src/mcp/index.ts
|
|
23997
23998
|
var db = getDb();
|
|
23998
23999
|
var server = new McpServer({ name: "logs", version: "0.3.0" });
|
|
24000
|
+
var _logsAgents = new Map;
|
|
23999
24001
|
function applyBrief(rows, brief = true) {
|
|
24000
24002
|
if (!brief)
|
|
24001
24003
|
return rows;
|
|
@@ -24027,6 +24029,7 @@ var TOOLS = {
|
|
|
24027
24029
|
log_summary: { desc: "Error/warn counts by service", params: "(project_id?, since?)" },
|
|
24028
24030
|
log_context: { desc: "All logs for a trace_id", params: "(trace_id, brief?=true)" },
|
|
24029
24031
|
log_context_from_id: { desc: "Trace context from a log ID (no trace_id needed)", params: "(log_id, brief?=true)" },
|
|
24032
|
+
log_export: { desc: "Export matching logs as JSON or CSV", params: "(project_id?, format?='json', since?, until?, level?, service?, limit?=100000)" },
|
|
24030
24033
|
log_diagnose: { desc: "Full diagnosis: score, top errors, failing pages, perf regressions", params: "(project_id, since?='24h', include?=['top_errors','error_rate','failing_pages','perf'])" },
|
|
24031
24034
|
log_compare: { desc: "Diff two time windows for new/resolved errors", params: "(project_id, a_since, a_until, b_since, b_until)" },
|
|
24032
24035
|
log_session_context: { desc: "Logs + session metadata for a session_id", params: "(session_id, brief?=true)" },
|
|
@@ -24041,6 +24044,7 @@ var TOOLS = {
|
|
|
24041
24044
|
list_alert_rules: { desc: "List alert rules", params: "(project_id?)" },
|
|
24042
24045
|
delete_alert_rule: { desc: "Delete alert rule", params: "(id)" },
|
|
24043
24046
|
get_health: { desc: "Server health + DB stats", params: "()" },
|
|
24047
|
+
log_stats: { desc: "Aggregate DB-level log statistics for a project", params: "(project_id?)" },
|
|
24044
24048
|
search_tools: { desc: "Search tools by keyword \u2014 returns names, descriptions, param signatures", params: "(query)" },
|
|
24045
24049
|
describe_tools: { desc: "List all tools with descriptions and param signatures", params: "()" }
|
|
24046
24050
|
};
|
|
@@ -24296,7 +24300,7 @@ registerTool("delete_alert_rule", { id: exports_external.string() }, ({ id }) =>
|
|
|
24296
24300
|
registerTool("get_health", {}, () => ({
|
|
24297
24301
|
content: [{ type: "text", text: JSON.stringify(getHealth(db)) }]
|
|
24298
24302
|
}));
|
|
24299
|
-
|
|
24303
|
+
registerTool("log_stats", {
|
|
24300
24304
|
project_id: exports_external.string().optional().describe("Project name or ID (scope stats to a project)")
|
|
24301
24305
|
}, (args) => {
|
|
24302
24306
|
const projectId = rp(args.project_id);
|
|
@@ -24333,5 +24337,43 @@ server.tool("send_feedback", "Send feedback about this service", {
|
|
|
24333
24337
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
24334
24338
|
}
|
|
24335
24339
|
});
|
|
24340
|
+
server.tool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
|
|
24341
|
+
name: exports_external.string(),
|
|
24342
|
+
session_id: exports_external.string().optional()
|
|
24343
|
+
}, async (params) => {
|
|
24344
|
+
const existing = [..._logsAgents.values()].find((a) => a.name === params.name);
|
|
24345
|
+
if (existing) {
|
|
24346
|
+
existing.last_seen_at = new Date().toISOString();
|
|
24347
|
+
if (params.session_id)
|
|
24348
|
+
existing.session_id = params.session_id;
|
|
24349
|
+
return { content: [{ type: "text", text: JSON.stringify(existing) }] };
|
|
24350
|
+
}
|
|
24351
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
24352
|
+
const ag = { id, name: params.name, session_id: params.session_id, last_seen_at: new Date().toISOString() };
|
|
24353
|
+
_logsAgents.set(id, ag);
|
|
24354
|
+
return { content: [{ type: "text", text: JSON.stringify(ag) }] };
|
|
24355
|
+
});
|
|
24356
|
+
server.tool("heartbeat", "Update last_seen_at to signal agent is active.", {
|
|
24357
|
+
agent_id: exports_external.string()
|
|
24358
|
+
}, async (params) => {
|
|
24359
|
+
const ag = _logsAgents.get(params.agent_id);
|
|
24360
|
+
if (!ag)
|
|
24361
|
+
return { content: [{ type: "text", text: `Agent not found: ${params.agent_id}` }], isError: true };
|
|
24362
|
+
ag.last_seen_at = new Date().toISOString();
|
|
24363
|
+
return { content: [{ type: "text", text: JSON.stringify({ agent_id: ag.id, last_seen_at: ag.last_seen_at }) }] };
|
|
24364
|
+
});
|
|
24365
|
+
server.tool("set_focus", "Set active project context for this agent session.", {
|
|
24366
|
+
agent_id: exports_external.string(),
|
|
24367
|
+
project_id: exports_external.string().optional()
|
|
24368
|
+
}, async (params) => {
|
|
24369
|
+
const ag = _logsAgents.get(params.agent_id);
|
|
24370
|
+
if (!ag)
|
|
24371
|
+
return { content: [{ type: "text", text: `Agent not found: ${params.agent_id}` }], isError: true };
|
|
24372
|
+
ag.project_id = params.project_id;
|
|
24373
|
+
return { content: [{ type: "text", text: JSON.stringify({ agent_id: ag.id, project_id: ag.project_id ?? null }) }] };
|
|
24374
|
+
});
|
|
24375
|
+
server.tool("list_agents", "List all registered agents.", {}, async () => {
|
|
24376
|
+
return { content: [{ type: "text", text: JSON.stringify([..._logsAgents.values()]) }] };
|
|
24377
|
+
});
|
|
24336
24378
|
var transport = new StdioServerTransport;
|
|
24337
24379
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/logs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
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",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"dev": "bun run src/server/index.ts",
|
|
18
18
|
"test": "bun test",
|
|
19
19
|
"test:coverage": "bun test --coverage",
|
|
20
|
-
"lint": "biome check src/"
|
|
20
|
+
"lint": "biome check src/",
|
|
21
|
+
"postinstall": "mkdir -p $HOME/.hasna/logs 2>/dev/null || true"
|
|
21
22
|
},
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
@@ -55,4 +56,4 @@
|
|
|
55
56
|
"@types/react": "^19.1.4",
|
|
56
57
|
"typescript": "^5.9.3"
|
|
57
58
|
}
|
|
58
|
-
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL migrations for open-logs cloud sync.
|
|
3
|
+
*
|
|
4
|
+
* Equivalent to the SQLite schema in index.ts + migrations/, translated for PostgreSQL.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const PG_MIGRATIONS: string[] = [
|
|
8
|
+
// Migration 1: projects table
|
|
9
|
+
`CREATE TABLE IF NOT EXISTS projects (
|
|
10
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
11
|
+
name TEXT NOT NULL UNIQUE,
|
|
12
|
+
github_repo TEXT,
|
|
13
|
+
base_url TEXT,
|
|
14
|
+
description TEXT,
|
|
15
|
+
github_description TEXT,
|
|
16
|
+
github_branch TEXT,
|
|
17
|
+
github_sha TEXT,
|
|
18
|
+
last_synced_at TEXT,
|
|
19
|
+
max_rows INTEGER NOT NULL DEFAULT 100000,
|
|
20
|
+
debug_ttl_hours INTEGER NOT NULL DEFAULT 24,
|
|
21
|
+
info_ttl_hours INTEGER NOT NULL DEFAULT 168,
|
|
22
|
+
warn_ttl_hours INTEGER NOT NULL DEFAULT 720,
|
|
23
|
+
error_ttl_hours INTEGER NOT NULL DEFAULT 2160,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
25
|
+
)`,
|
|
26
|
+
|
|
27
|
+
// Migration 2: pages table
|
|
28
|
+
`CREATE TABLE IF NOT EXISTS pages (
|
|
29
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
30
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
31
|
+
url TEXT NOT NULL,
|
|
32
|
+
path TEXT NOT NULL DEFAULT '/',
|
|
33
|
+
name TEXT,
|
|
34
|
+
last_scanned_at TEXT,
|
|
35
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text,
|
|
36
|
+
UNIQUE(project_id, url)
|
|
37
|
+
)`,
|
|
38
|
+
|
|
39
|
+
// Migration 3: logs table
|
|
40
|
+
`CREATE TABLE IF NOT EXISTS logs (
|
|
41
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
42
|
+
timestamp TEXT NOT NULL DEFAULT NOW()::text,
|
|
43
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
44
|
+
page_id TEXT REFERENCES pages(id) ON DELETE SET NULL,
|
|
45
|
+
level TEXT NOT NULL CHECK(level IN ('debug','info','warn','error','fatal')),
|
|
46
|
+
source TEXT NOT NULL DEFAULT 'sdk' CHECK(source IN ('sdk','script','scanner')),
|
|
47
|
+
service TEXT,
|
|
48
|
+
message TEXT NOT NULL,
|
|
49
|
+
trace_id TEXT,
|
|
50
|
+
session_id TEXT,
|
|
51
|
+
agent TEXT,
|
|
52
|
+
url TEXT,
|
|
53
|
+
stack_trace TEXT,
|
|
54
|
+
metadata TEXT
|
|
55
|
+
)`,
|
|
56
|
+
|
|
57
|
+
`CREATE INDEX IF NOT EXISTS idx_logs_project_level_ts ON logs(project_id, level, timestamp DESC)`,
|
|
58
|
+
|
|
59
|
+
`CREATE INDEX IF NOT EXISTS idx_logs_trace ON logs(trace_id)`,
|
|
60
|
+
|
|
61
|
+
`CREATE INDEX IF NOT EXISTS idx_logs_service ON logs(service)`,
|
|
62
|
+
|
|
63
|
+
`CREATE INDEX IF NOT EXISTS idx_logs_page ON logs(page_id)`,
|
|
64
|
+
|
|
65
|
+
`CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp DESC)`,
|
|
66
|
+
|
|
67
|
+
// Migration 4: scan_jobs table
|
|
68
|
+
`CREATE TABLE IF NOT EXISTS scan_jobs (
|
|
69
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
70
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
71
|
+
page_id TEXT REFERENCES pages(id) ON DELETE SET NULL,
|
|
72
|
+
schedule TEXT NOT NULL DEFAULT '*/30 * * * *',
|
|
73
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
74
|
+
last_run_at TEXT,
|
|
75
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
76
|
+
)`,
|
|
77
|
+
|
|
78
|
+
// Migration 5: scan_runs table
|
|
79
|
+
`CREATE TABLE IF NOT EXISTS scan_runs (
|
|
80
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
81
|
+
job_id TEXT NOT NULL REFERENCES scan_jobs(id) ON DELETE CASCADE,
|
|
82
|
+
page_id TEXT REFERENCES pages(id) ON DELETE SET NULL,
|
|
83
|
+
started_at TEXT NOT NULL DEFAULT NOW()::text,
|
|
84
|
+
finished_at TEXT,
|
|
85
|
+
status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running','completed','failed')),
|
|
86
|
+
logs_collected INTEGER NOT NULL DEFAULT 0,
|
|
87
|
+
errors_found INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
perf_score REAL
|
|
89
|
+
)`,
|
|
90
|
+
|
|
91
|
+
// Migration 6: performance_snapshots table
|
|
92
|
+
`CREATE TABLE IF NOT EXISTS performance_snapshots (
|
|
93
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
94
|
+
timestamp TEXT NOT NULL DEFAULT NOW()::text,
|
|
95
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
96
|
+
page_id TEXT REFERENCES pages(id) ON DELETE SET NULL,
|
|
97
|
+
url TEXT NOT NULL,
|
|
98
|
+
lcp REAL,
|
|
99
|
+
fcp REAL,
|
|
100
|
+
cls REAL,
|
|
101
|
+
tti REAL,
|
|
102
|
+
ttfb REAL,
|
|
103
|
+
score REAL,
|
|
104
|
+
raw_audit TEXT
|
|
105
|
+
)`,
|
|
106
|
+
|
|
107
|
+
`CREATE INDEX IF NOT EXISTS idx_perf_project_ts ON performance_snapshots(project_id, timestamp DESC)`,
|
|
108
|
+
|
|
109
|
+
`CREATE INDEX IF NOT EXISTS idx_perf_page ON performance_snapshots(page_id)`,
|
|
110
|
+
|
|
111
|
+
// Migration 7: alert_rules table
|
|
112
|
+
`CREATE TABLE IF NOT EXISTS alert_rules (
|
|
113
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
114
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
115
|
+
name TEXT NOT NULL,
|
|
116
|
+
service TEXT,
|
|
117
|
+
level TEXT NOT NULL DEFAULT 'error' CHECK(level IN ('debug','info','warn','error','fatal')),
|
|
118
|
+
threshold_count INTEGER NOT NULL DEFAULT 10,
|
|
119
|
+
window_seconds INTEGER NOT NULL DEFAULT 60,
|
|
120
|
+
action TEXT NOT NULL DEFAULT 'webhook' CHECK(action IN ('webhook','log')),
|
|
121
|
+
webhook_url TEXT,
|
|
122
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
123
|
+
last_fired_at TEXT,
|
|
124
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
125
|
+
)`,
|
|
126
|
+
|
|
127
|
+
`CREATE INDEX IF NOT EXISTS idx_alert_rules_project ON alert_rules(project_id)`,
|
|
128
|
+
|
|
129
|
+
// Migration 8: issues table
|
|
130
|
+
`CREATE TABLE IF NOT EXISTS issues (
|
|
131
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
132
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
133
|
+
fingerprint TEXT NOT NULL,
|
|
134
|
+
level TEXT NOT NULL,
|
|
135
|
+
service TEXT,
|
|
136
|
+
message_template TEXT NOT NULL,
|
|
137
|
+
first_seen TEXT NOT NULL DEFAULT NOW()::text,
|
|
138
|
+
last_seen TEXT NOT NULL DEFAULT NOW()::text,
|
|
139
|
+
count INTEGER NOT NULL DEFAULT 1,
|
|
140
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','resolved','ignored')),
|
|
141
|
+
UNIQUE(project_id, fingerprint)
|
|
142
|
+
)`,
|
|
143
|
+
|
|
144
|
+
`CREATE INDEX IF NOT EXISTS idx_issues_project ON issues(project_id, status)`,
|
|
145
|
+
|
|
146
|
+
`CREATE INDEX IF NOT EXISTS idx_issues_fingerprint ON issues(fingerprint)`,
|
|
147
|
+
|
|
148
|
+
// Migration 9: page_auth table
|
|
149
|
+
`CREATE TABLE IF NOT EXISTS page_auth (
|
|
150
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
151
|
+
page_id TEXT NOT NULL UNIQUE REFERENCES pages(id) ON DELETE CASCADE,
|
|
152
|
+
type TEXT NOT NULL CHECK(type IN ('cookie','bearer','basic')),
|
|
153
|
+
credentials TEXT NOT NULL,
|
|
154
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
155
|
+
)`,
|
|
156
|
+
|
|
157
|
+
// Migration 10: feedback table
|
|
158
|
+
`CREATE TABLE IF NOT EXISTS feedback (
|
|
159
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
160
|
+
message TEXT NOT NULL,
|
|
161
|
+
email TEXT,
|
|
162
|
+
category TEXT DEFAULT 'general',
|
|
163
|
+
version TEXT,
|
|
164
|
+
machine_id TEXT,
|
|
165
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
166
|
+
)`,
|
|
167
|
+
];
|
package/src/mcp/index.test.ts
CHANGED
|
@@ -18,7 +18,9 @@ test("logs MCP lists tools over stdio", async () => {
|
|
|
18
18
|
|
|
19
19
|
expect(toolNames.length).toBeGreaterThan(0)
|
|
20
20
|
expect(toolNames).toContain("get_health")
|
|
21
|
+
expect(toolNames).toContain("log_export")
|
|
21
22
|
expect(toolNames).toContain("log_search")
|
|
23
|
+
expect(toolNames).toContain("log_stats")
|
|
22
24
|
} finally {
|
|
23
25
|
await client.close().catch(() => {})
|
|
24
26
|
}
|
package/src/mcp/index.ts
CHANGED
|
@@ -23,6 +23,10 @@ import type { LogLevel, LogRow } from "../types/index.ts"
|
|
|
23
23
|
const db = getDb()
|
|
24
24
|
const server = new McpServer({ name: "logs", version: "0.3.0" })
|
|
25
25
|
|
|
26
|
+
// --- in-memory agent registry ---
|
|
27
|
+
interface _LogsAgent { id: string; name: string; session_id?: string; last_seen_at: string; project_id?: string }
|
|
28
|
+
const _logsAgents = new Map<string, _LogsAgent>()
|
|
29
|
+
|
|
26
30
|
const BRIEF_FIELDS: (keyof LogRow)[] = ["id", "timestamp", "level", "message", "service"]
|
|
27
31
|
|
|
28
32
|
function applyBrief(rows: LogRow[], brief = true): unknown[] {
|
|
@@ -57,6 +61,7 @@ const TOOLS: Record<string, { desc: string; params: string }> = {
|
|
|
57
61
|
log_summary: { desc: "Error/warn counts by service", params: "(project_id?, since?)" },
|
|
58
62
|
log_context: { desc: "All logs for a trace_id", params: "(trace_id, brief?=true)" },
|
|
59
63
|
log_context_from_id: { desc: "Trace context from a log ID (no trace_id needed)", params: "(log_id, brief?=true)" },
|
|
64
|
+
log_export: { desc: "Export matching logs as JSON or CSV", params: "(project_id?, format?='json', since?, until?, level?, service?, limit?=100000)" },
|
|
60
65
|
log_diagnose: { desc: "Full diagnosis: score, top errors, failing pages, perf regressions", params: "(project_id, since?='24h', include?=['top_errors','error_rate','failing_pages','perf'])" },
|
|
61
66
|
log_compare: { desc: "Diff two time windows for new/resolved errors", params: "(project_id, a_since, a_until, b_since, b_until)" },
|
|
62
67
|
log_session_context: { desc: "Logs + session metadata for a session_id", params: "(session_id, brief?=true)" },
|
|
@@ -71,6 +76,7 @@ const TOOLS: Record<string, { desc: string; params: string }> = {
|
|
|
71
76
|
list_alert_rules: { desc: "List alert rules", params: "(project_id?)" },
|
|
72
77
|
delete_alert_rule: { desc: "Delete alert rule", params: "(id)" },
|
|
73
78
|
get_health: { desc: "Server health + DB stats", params: "()" },
|
|
79
|
+
log_stats: { desc: "Aggregate DB-level log statistics for a project", params: "(project_id?)" },
|
|
74
80
|
search_tools: { desc: "Search tools by keyword — returns names, descriptions, param signatures", params: "(query)" },
|
|
75
81
|
describe_tools: { desc: "List all tools with descriptions and param signatures", params: "()" },
|
|
76
82
|
}
|
|
@@ -317,7 +323,7 @@ registerTool("get_health", {}, () => ({
|
|
|
317
323
|
content: [{ type: "text", text: JSON.stringify(getHealth(db)) }]
|
|
318
324
|
}))
|
|
319
325
|
|
|
320
|
-
|
|
326
|
+
registerTool("log_stats", {
|
|
321
327
|
project_id: z.string().optional().describe("Project name or ID (scope stats to a project)"),
|
|
322
328
|
}, (args) => {
|
|
323
329
|
const projectId = rp(args.project_id)
|
|
@@ -359,5 +365,42 @@ server.tool(
|
|
|
359
365
|
},
|
|
360
366
|
)
|
|
361
367
|
|
|
368
|
+
// --- Agent Tools ---
|
|
369
|
+
|
|
370
|
+
server.tool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
|
|
371
|
+
name: z.string(),
|
|
372
|
+
session_id: z.string().optional(),
|
|
373
|
+
}, async (params) => {
|
|
374
|
+
const existing = [..._logsAgents.values()].find(a => a.name === params.name)
|
|
375
|
+
if (existing) { existing.last_seen_at = new Date().toISOString(); if (params.session_id) existing.session_id = params.session_id; return { content: [{ type: "text" as const, text: JSON.stringify(existing) }] } }
|
|
376
|
+
const id = Math.random().toString(36).slice(2, 10)
|
|
377
|
+
const ag: _LogsAgent = { id, name: params.name, session_id: params.session_id, last_seen_at: new Date().toISOString() }
|
|
378
|
+
_logsAgents.set(id, ag)
|
|
379
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(ag) }] }
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
server.tool("heartbeat", "Update last_seen_at to signal agent is active.", {
|
|
383
|
+
agent_id: z.string(),
|
|
384
|
+
}, async (params) => {
|
|
385
|
+
const ag = _logsAgents.get(params.agent_id)
|
|
386
|
+
if (!ag) return { content: [{ type: "text" as const, text: `Agent not found: ${params.agent_id}` }], isError: true }
|
|
387
|
+
ag.last_seen_at = new Date().toISOString()
|
|
388
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ agent_id: ag.id, last_seen_at: ag.last_seen_at }) }] }
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
server.tool("set_focus", "Set active project context for this agent session.", {
|
|
392
|
+
agent_id: z.string(),
|
|
393
|
+
project_id: z.string().optional(),
|
|
394
|
+
}, async (params) => {
|
|
395
|
+
const ag = _logsAgents.get(params.agent_id)
|
|
396
|
+
if (!ag) return { content: [{ type: "text" as const, text: `Agent not found: ${params.agent_id}` }], isError: true }
|
|
397
|
+
ag.project_id = params.project_id
|
|
398
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ agent_id: ag.id, project_id: ag.project_id ?? null }) }] }
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
server.tool("list_agents", "List all registered agents.", {}, async () => {
|
|
402
|
+
return { content: [{ type: "text" as const, text: JSON.stringify([..._logsAgents.values()]) }] }
|
|
403
|
+
})
|
|
404
|
+
|
|
362
405
|
const transport = new StdioServerTransport()
|
|
363
406
|
await server.connect(transport)
|