@hasna/economy 0.2.28 → 0.2.29
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 +16 -1
- package/dist/cli/index.js +332 -132
- package/dist/db/database.d.ts +9 -4
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +185 -107
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +13 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +763 -615
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/server/index.js +200 -114
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -971,8 +971,10 @@ function queryModelBreakdown(db) {
|
|
|
971
971
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
972
972
|
`).all();
|
|
973
973
|
}
|
|
974
|
-
function queryAgentBreakdown(db, period = "all") {
|
|
974
|
+
function queryAgentBreakdown(db, period = "all", machine) {
|
|
975
975
|
const requestWhere = requestPeriodWhere(period);
|
|
976
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
977
|
+
const machineParams = machine ? [machine] : [];
|
|
976
978
|
const groups = new Map;
|
|
977
979
|
const requestRows = db.prepare(`
|
|
978
980
|
SELECT agent,
|
|
@@ -988,10 +990,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
988
990
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
989
991
|
MAX(timestamp) as last_active
|
|
990
992
|
FROM requests
|
|
991
|
-
WHERE ${requestWhere}
|
|
993
|
+
WHERE ${requestWhere}${machineClause}
|
|
992
994
|
GROUP BY agent
|
|
993
995
|
ORDER BY api_equivalent_usd DESC
|
|
994
|
-
`).all();
|
|
996
|
+
`).all(...machineParams);
|
|
995
997
|
for (const row of requestRows) {
|
|
996
998
|
groups.set(row.agent, row);
|
|
997
999
|
}
|
|
@@ -1004,10 +1006,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1004
1006
|
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1005
1007
|
MAX(started_at) as last_active
|
|
1006
1008
|
FROM sessions
|
|
1007
|
-
WHERE ${sessionWhere}
|
|
1009
|
+
WHERE ${sessionWhere}${machineClause}
|
|
1008
1010
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1009
1011
|
GROUP BY agent
|
|
1010
|
-
`).all();
|
|
1012
|
+
`).all(...machineParams);
|
|
1011
1013
|
for (const row of sessionOnlyRows) {
|
|
1012
1014
|
const existing = groups.get(row.agent) ?? {
|
|
1013
1015
|
agent: row.agent,
|
|
@@ -1084,14 +1086,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1084
1086
|
function groupKeyForPath(projectPath, projectName) {
|
|
1085
1087
|
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1086
1088
|
}
|
|
1087
|
-
function queryProjectBreakdown(db, period = "all") {
|
|
1089
|
+
function queryProjectBreakdown(db, period = "all", machine) {
|
|
1088
1090
|
const requestWhere = requestPeriodWhere(period);
|
|
1089
1091
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1092
|
+
const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
|
|
1093
|
+
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1094
|
+
const sessionMachineParams = machine ? [machine, machine] : [];
|
|
1095
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1096
|
+
const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1097
|
+
const sessionOnlyMachineParams = machine ? [machine] : [];
|
|
1090
1098
|
const sessions = db.prepare(`
|
|
1091
1099
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1092
1100
|
FROM sessions
|
|
1093
|
-
WHERE project_path != '' OR project_name != ''
|
|
1094
|
-
`).all();
|
|
1101
|
+
WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
|
|
1102
|
+
`).all(...sessionMachineParams);
|
|
1095
1103
|
const groups = new Map;
|
|
1096
1104
|
for (const s of sessions) {
|
|
1097
1105
|
const label = labelForPath(s.project_path, s.project_name);
|
|
@@ -1117,7 +1125,8 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1117
1125
|
FROM requests
|
|
1118
1126
|
WHERE session_id IN (${placeholders})
|
|
1119
1127
|
AND ${requestWhere}
|
|
1120
|
-
|
|
1128
|
+
${requestMachineClause}
|
|
1129
|
+
`).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1121
1130
|
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1122
1131
|
SELECT
|
|
1123
1132
|
COUNT(*) as sessions,
|
|
@@ -1128,8 +1137,9 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1128
1137
|
FROM sessions
|
|
1129
1138
|
WHERE id IN (${placeholders})
|
|
1130
1139
|
AND ${sessionWhere}
|
|
1140
|
+
${sessionOnlyMachineClause}
|
|
1131
1141
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1132
|
-
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1142
|
+
`).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1133
1143
|
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1134
1144
|
if (totalSessions === 0)
|
|
1135
1145
|
continue;
|
|
@@ -1147,107 +1157,156 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1147
1157
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1148
1158
|
return result;
|
|
1149
1159
|
}
|
|
1150
|
-
function
|
|
1160
|
+
function normalizeAccountEmail(email) {
|
|
1161
|
+
return (email ?? "").trim().toLowerCase();
|
|
1162
|
+
}
|
|
1163
|
+
function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
|
|
1164
|
+
const identityAgent = (agent || "").trim();
|
|
1165
|
+
const normalizedEmail = normalizeAccountEmail(accountEmail);
|
|
1166
|
+
if (identityAgent && normalizedEmail)
|
|
1167
|
+
return `${identityAgent}:${normalizedEmail}`;
|
|
1168
|
+
if (identityAgent && accountName)
|
|
1169
|
+
return `${identityAgent}:${accountName}`;
|
|
1170
|
+
if (accountKey)
|
|
1171
|
+
return accountKey;
|
|
1172
|
+
return identityAgent ? `${identityAgent}:unknown` : "";
|
|
1173
|
+
}
|
|
1174
|
+
function addAccountBreakdownRow(groups, row, sessionOnly) {
|
|
1175
|
+
const agent = row.agent || row.account_tool;
|
|
1176
|
+
const email = normalizeAccountEmail(row.account_email);
|
|
1177
|
+
const accountName = row.account_name || email || row.account_key;
|
|
1178
|
+
const key = accountIdentityKey(agent, row.account_key, accountName, email);
|
|
1179
|
+
if (!key)
|
|
1180
|
+
return;
|
|
1181
|
+
const group = groups.get(key) ?? {
|
|
1182
|
+
account_key: key,
|
|
1183
|
+
account_tool: agent,
|
|
1184
|
+
account_name: accountName,
|
|
1185
|
+
account_email: email || null,
|
|
1186
|
+
account_source: row.account_source || "unknown",
|
|
1187
|
+
sessionIds: new Set,
|
|
1188
|
+
requests: 0,
|
|
1189
|
+
total_tokens: 0,
|
|
1190
|
+
api_equivalent_usd: 0,
|
|
1191
|
+
metered_api_usd: 0,
|
|
1192
|
+
subscription_included_usd: 0,
|
|
1193
|
+
estimated_usd: 0,
|
|
1194
|
+
unknown_usd: 0,
|
|
1195
|
+
last_active: ""
|
|
1196
|
+
};
|
|
1197
|
+
if (!group.account_email && email)
|
|
1198
|
+
group.account_email = email;
|
|
1199
|
+
if (!group.account_name && accountName)
|
|
1200
|
+
group.account_name = accountName;
|
|
1201
|
+
if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
1202
|
+
group.account_source = row.account_source;
|
|
1203
|
+
}
|
|
1204
|
+
if (row.session_id)
|
|
1205
|
+
group.sessionIds.add(row.session_id);
|
|
1206
|
+
group.requests += row.requests;
|
|
1207
|
+
group.total_tokens += row.total_tokens;
|
|
1208
|
+
group.api_equivalent_usd += row.cost_usd;
|
|
1209
|
+
if (sessionOnly) {
|
|
1210
|
+
group.estimated_usd += row.cost_usd;
|
|
1211
|
+
} else if (row.cost_basis === "metered_api") {
|
|
1212
|
+
group.metered_api_usd += row.cost_usd;
|
|
1213
|
+
} else if (row.cost_basis === "subscription_included") {
|
|
1214
|
+
group.subscription_included_usd += row.cost_usd;
|
|
1215
|
+
} else if (row.cost_basis === "unknown") {
|
|
1216
|
+
group.unknown_usd += row.cost_usd;
|
|
1217
|
+
} else {
|
|
1218
|
+
group.estimated_usd += row.cost_usd;
|
|
1219
|
+
}
|
|
1220
|
+
if (!group.last_active || row.last_active > group.last_active)
|
|
1221
|
+
group.last_active = row.last_active;
|
|
1222
|
+
groups.set(key, group);
|
|
1223
|
+
}
|
|
1224
|
+
function queryAccountBreakdown(db, period = "all", machine) {
|
|
1151
1225
|
const requestWhere = requestPeriodWhere(period);
|
|
1152
1226
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1158
|
-
`).all();
|
|
1227
|
+
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
1228
|
+
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
1229
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1230
|
+
const sessionMachineParams = machine ? [machine] : [];
|
|
1159
1231
|
const groups = new Map;
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
account_email: group.account_email,
|
|
1226
|
-
account_source: group.account_source,
|
|
1227
|
-
sessions: sessionsTotal,
|
|
1228
|
-
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1229
|
-
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1230
|
-
api_equivalent_usd: apiEquivalentUsd,
|
|
1231
|
-
billable_usd: billableUsd,
|
|
1232
|
-
metered_api_usd: reqStats.metered_api_usd,
|
|
1233
|
-
subscription_included_usd: reqStats.subscription_included_usd,
|
|
1234
|
-
estimated_usd: estimatedUsd,
|
|
1235
|
-
unknown_usd: reqStats.unknown_usd,
|
|
1236
|
-
cost_usd: apiEquivalentUsd,
|
|
1237
|
-
last_active: lastActive
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1232
|
+
const requestRows = db.prepare(`
|
|
1233
|
+
SELECT
|
|
1234
|
+
r.session_id as session_id,
|
|
1235
|
+
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
1236
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
1237
|
+
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
1238
|
+
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
1239
|
+
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
1240
|
+
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1241
|
+
1 as requests,
|
|
1242
|
+
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1243
|
+
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1244
|
+
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1245
|
+
r.timestamp as last_active
|
|
1246
|
+
FROM requests r
|
|
1247
|
+
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1248
|
+
WHERE ${requestWhere}${requestMachineClause}
|
|
1249
|
+
AND (
|
|
1250
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
1251
|
+
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
1252
|
+
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
1253
|
+
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
1254
|
+
)
|
|
1255
|
+
`).all(...requestMachineParams);
|
|
1256
|
+
for (const row of requestRows)
|
|
1257
|
+
addAccountBreakdownRow(groups, row, false);
|
|
1258
|
+
const sessionOnlyRows = db.prepare(`
|
|
1259
|
+
SELECT
|
|
1260
|
+
s.id as session_id,
|
|
1261
|
+
s.agent as agent,
|
|
1262
|
+
s.account_key as account_key,
|
|
1263
|
+
s.account_tool as account_tool,
|
|
1264
|
+
s.account_name as account_name,
|
|
1265
|
+
s.account_email as account_email,
|
|
1266
|
+
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1267
|
+
COALESCE(s.request_count, 0) as requests,
|
|
1268
|
+
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1269
|
+
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1270
|
+
'estimated' as cost_basis,
|
|
1271
|
+
s.started_at as last_active
|
|
1272
|
+
FROM sessions s
|
|
1273
|
+
WHERE ${sessionWhere}${sessionMachineClause}
|
|
1274
|
+
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1275
|
+
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
1276
|
+
`).all(...sessionMachineParams);
|
|
1277
|
+
for (const row of sessionOnlyRows)
|
|
1278
|
+
addAccountBreakdownRow(groups, row, true);
|
|
1279
|
+
const result = [...groups.values()].map((group) => ({
|
|
1280
|
+
account_key: group.account_key,
|
|
1281
|
+
account_tool: group.account_tool,
|
|
1282
|
+
account_name: group.account_name,
|
|
1283
|
+
account_email: group.account_email,
|
|
1284
|
+
account_source: group.account_source,
|
|
1285
|
+
sessions: group.sessionIds.size,
|
|
1286
|
+
requests: group.requests,
|
|
1287
|
+
total_tokens: group.total_tokens,
|
|
1288
|
+
api_equivalent_usd: group.api_equivalent_usd,
|
|
1289
|
+
billable_usd: group.metered_api_usd,
|
|
1290
|
+
metered_api_usd: group.metered_api_usd,
|
|
1291
|
+
subscription_included_usd: group.subscription_included_usd,
|
|
1292
|
+
estimated_usd: group.estimated_usd,
|
|
1293
|
+
unknown_usd: group.unknown_usd,
|
|
1294
|
+
cost_usd: group.api_equivalent_usd,
|
|
1295
|
+
last_active: group.last_active
|
|
1296
|
+
}));
|
|
1240
1297
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1241
1298
|
return result;
|
|
1242
1299
|
}
|
|
1243
|
-
function queryDailyBreakdown(db, days = 30) {
|
|
1300
|
+
function queryDailyBreakdown(db, days = 30, machine) {
|
|
1301
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1302
|
+
const params = machine ? [`-${days}`, machine] : [`-${days}`];
|
|
1244
1303
|
return db.prepare(`
|
|
1245
1304
|
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1246
1305
|
FROM requests
|
|
1247
|
-
WHERE timestamp >= DATE('now', ? || ' days')
|
|
1306
|
+
WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
|
|
1248
1307
|
GROUP BY DATE(timestamp), agent
|
|
1249
1308
|
ORDER BY date ASC
|
|
1250
|
-
`).all(
|
|
1309
|
+
`).all(...params);
|
|
1251
1310
|
}
|
|
1252
1311
|
function upsertBudget(db, budget) {
|
|
1253
1312
|
db.prepare(`
|
|
@@ -1676,18 +1735,36 @@ var init_pg_migrations = __esm(() => {
|
|
|
1676
1735
|
});
|
|
1677
1736
|
|
|
1678
1737
|
// src/mcp/index.ts
|
|
1738
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1739
|
+
|
|
1740
|
+
// src/lib/package-metadata.ts
|
|
1741
|
+
import { readFileSync } from "fs";
|
|
1742
|
+
var cachedMetadata = null;
|
|
1743
|
+
function getPackageMetadata() {
|
|
1744
|
+
if (cachedMetadata)
|
|
1745
|
+
return cachedMetadata;
|
|
1746
|
+
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
|
|
1747
|
+
const parsed = JSON.parse(raw);
|
|
1748
|
+
cachedMetadata = {
|
|
1749
|
+
name: parsed.name ?? "@hasna/economy",
|
|
1750
|
+
version: parsed.version ?? "0.0.0"
|
|
1751
|
+
};
|
|
1752
|
+
return cachedMetadata;
|
|
1753
|
+
}
|
|
1754
|
+
var packageMetadata = getPackageMetadata();
|
|
1755
|
+
|
|
1756
|
+
// src/mcp/server.ts
|
|
1679
1757
|
init_database();
|
|
1680
1758
|
init_pg_migrations();
|
|
1681
1759
|
import { randomUUID } from "crypto";
|
|
1682
1760
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1683
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1684
1761
|
import { registerCloudTools } from "@hasna/cloud";
|
|
1685
1762
|
import { z } from "zod";
|
|
1686
1763
|
|
|
1687
1764
|
// src/ingest/claude.ts
|
|
1688
1765
|
init_database();
|
|
1689
1766
|
init_pricing();
|
|
1690
|
-
import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
1767
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
1691
1768
|
import { homedir as homedir2 } from "os";
|
|
1692
1769
|
import { join as join2, basename } from "path";
|
|
1693
1770
|
|
|
@@ -1856,18 +1933,22 @@ var AGENT_ACCOUNT_TOOLS = {
|
|
|
1856
1933
|
pi: ["pi"],
|
|
1857
1934
|
hermes: ["hermes"]
|
|
1858
1935
|
};
|
|
1859
|
-
function
|
|
1860
|
-
return
|
|
1936
|
+
function normalizeEmail(email) {
|
|
1937
|
+
return (email ?? "").trim().toLowerCase();
|
|
1938
|
+
}
|
|
1939
|
+
function accountKey(tool, name, email) {
|
|
1940
|
+
const normalizedEmail = normalizeEmail(email);
|
|
1941
|
+
return `${tool}:${normalizedEmail || name}`;
|
|
1861
1942
|
}
|
|
1862
1943
|
function normalizeDir(value) {
|
|
1863
1944
|
return value.replace(/\/+$/, "");
|
|
1864
1945
|
}
|
|
1865
1946
|
function fromProfile(profile, source) {
|
|
1866
1947
|
return {
|
|
1867
|
-
account_key: accountKey(profile.tool, profile.name),
|
|
1948
|
+
account_key: accountKey(profile.tool, profile.name, profile.email),
|
|
1868
1949
|
account_tool: profile.tool,
|
|
1869
1950
|
account_name: profile.name,
|
|
1870
|
-
...profile.email ? { account_email: profile.email } : {},
|
|
1951
|
+
...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
|
|
1871
1952
|
account_source: source
|
|
1872
1953
|
};
|
|
1873
1954
|
}
|
|
@@ -1879,10 +1960,12 @@ function fromOverride(raw, agent) {
|
|
|
1879
1960
|
const [tool, name] = value.includes(":") ? value.split(":", 2) : [candidateTool, value];
|
|
1880
1961
|
if (!tool || !name)
|
|
1881
1962
|
return null;
|
|
1963
|
+
const email = name.includes("@") ? normalizeEmail(name) : undefined;
|
|
1882
1964
|
return {
|
|
1883
|
-
account_key: accountKey(tool, name),
|
|
1965
|
+
account_key: accountKey(tool, name, email),
|
|
1884
1966
|
account_tool: tool,
|
|
1885
1967
|
account_name: name,
|
|
1968
|
+
...email ? { account_email: email } : {},
|
|
1886
1969
|
account_source: "override"
|
|
1887
1970
|
};
|
|
1888
1971
|
}
|
|
@@ -1895,11 +1978,12 @@ function envOverride(agent, env) {
|
|
|
1895
1978
|
const name = env[`ECONOMY_${agentPrefix}_ACCOUNT_NAME`] ?? env["ECONOMY_ACCOUNT_NAME"];
|
|
1896
1979
|
if (!tool || !name)
|
|
1897
1980
|
return null;
|
|
1981
|
+
const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
|
|
1898
1982
|
return {
|
|
1899
|
-
account_key: accountKey(tool, name),
|
|
1983
|
+
account_key: accountKey(tool, name, email),
|
|
1900
1984
|
account_tool: tool,
|
|
1901
1985
|
account_name: name,
|
|
1902
|
-
account_email:
|
|
1986
|
+
...email ? { account_email: email } : {},
|
|
1903
1987
|
account_source: "override"
|
|
1904
1988
|
};
|
|
1905
1989
|
}
|
|
@@ -2030,7 +2114,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
2030
2114
|
continue;
|
|
2031
2115
|
let lines;
|
|
2032
2116
|
try {
|
|
2033
|
-
lines =
|
|
2117
|
+
lines = readFileSync2(filePath, "utf-8").split(`
|
|
2034
2118
|
`).filter((l) => l.trim());
|
|
2035
2119
|
} catch {
|
|
2036
2120
|
continue;
|
|
@@ -2150,7 +2234,7 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
2150
2234
|
// src/ingest/codex.ts
|
|
2151
2235
|
init_database();
|
|
2152
2236
|
init_pricing();
|
|
2153
|
-
import { existsSync as existsSync3, readFileSync as
|
|
2237
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2154
2238
|
import { homedir as homedir3 } from "os";
|
|
2155
2239
|
import { join as join3, basename as basename2 } from "path";
|
|
2156
2240
|
import { Database as BunDatabase } from "bun:sqlite";
|
|
@@ -2168,7 +2252,7 @@ function readCodexModel() {
|
|
|
2168
2252
|
if (!existsSync3(configPath))
|
|
2169
2253
|
return "gpt-5-codex";
|
|
2170
2254
|
try {
|
|
2171
|
-
const content =
|
|
2255
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
2172
2256
|
const match = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
2173
2257
|
return match?.[1] ?? "gpt-5-codex";
|
|
2174
2258
|
} catch {
|
|
@@ -2211,7 +2295,7 @@ function readTokenEvents(rolloutPath) {
|
|
|
2211
2295
|
const fallbackUsages = new Map;
|
|
2212
2296
|
let fallbackTimestamp;
|
|
2213
2297
|
let aggregate = null;
|
|
2214
|
-
for (const line of
|
|
2298
|
+
for (const line of readFileSync3(rolloutPath, "utf-8").split(`
|
|
2215
2299
|
`)) {
|
|
2216
2300
|
if (!line.trim())
|
|
2217
2301
|
continue;
|
|
@@ -2367,7 +2451,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2367
2451
|
// src/ingest/gemini.ts
|
|
2368
2452
|
init_database();
|
|
2369
2453
|
init_pricing();
|
|
2370
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2454
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
2371
2455
|
import { homedir as homedir4 } from "os";
|
|
2372
2456
|
import { join as join4, basename as basename3 } from "path";
|
|
2373
2457
|
var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
|
|
@@ -2407,7 +2491,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
2407
2491
|
const rootFile = join4(projectDir, ".project_root");
|
|
2408
2492
|
try {
|
|
2409
2493
|
if (existsSync4(rootFile))
|
|
2410
|
-
return
|
|
2494
|
+
return readFileSync4(rootFile, "utf-8").trim();
|
|
2411
2495
|
} catch {}
|
|
2412
2496
|
return "";
|
|
2413
2497
|
}
|
|
@@ -2448,7 +2532,7 @@ async function ingestGemini(db, verbose) {
|
|
|
2448
2532
|
continue;
|
|
2449
2533
|
let chatData;
|
|
2450
2534
|
try {
|
|
2451
|
-
chatData = JSON.parse(
|
|
2535
|
+
chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
2452
2536
|
} catch {
|
|
2453
2537
|
continue;
|
|
2454
2538
|
}
|
|
@@ -2526,7 +2610,7 @@ async function ingestGemini(db, verbose) {
|
|
|
2526
2610
|
// src/ingest/opencode.ts
|
|
2527
2611
|
init_database();
|
|
2528
2612
|
init_pricing();
|
|
2529
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2613
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
2530
2614
|
import { homedir as homedir5 } from "os";
|
|
2531
2615
|
import { join as join5 } from "path";
|
|
2532
2616
|
var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
|
|
@@ -2569,7 +2653,7 @@ async function ingestOpenCode(db, verbose = false) {
|
|
|
2569
2653
|
continue;
|
|
2570
2654
|
let parsed;
|
|
2571
2655
|
try {
|
|
2572
|
-
parsed = JSON.parse(
|
|
2656
|
+
parsed = JSON.parse(readFileSync5(file, "utf-8"));
|
|
2573
2657
|
} catch {
|
|
2574
2658
|
continue;
|
|
2575
2659
|
}
|
|
@@ -2756,7 +2840,7 @@ async function ingestCursor(db, verbose = false) {
|
|
|
2756
2840
|
|
|
2757
2841
|
// src/ingest/pi.ts
|
|
2758
2842
|
init_database();
|
|
2759
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2843
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
2760
2844
|
import { homedir as homedir6 } from "os";
|
|
2761
2845
|
import { join as join6 } from "path";
|
|
2762
2846
|
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
|
|
@@ -2786,7 +2870,7 @@ async function ingestPi(db, verbose = false) {
|
|
|
2786
2870
|
continue;
|
|
2787
2871
|
let data;
|
|
2788
2872
|
try {
|
|
2789
|
-
data = JSON.parse(
|
|
2873
|
+
data = JSON.parse(readFileSync6(file, "utf-8"));
|
|
2790
2874
|
} catch {
|
|
2791
2875
|
continue;
|
|
2792
2876
|
}
|
|
@@ -2941,7 +3025,7 @@ function statSyncSafe(path) {
|
|
|
2941
3025
|
|
|
2942
3026
|
// src/ingest/claude-quota.ts
|
|
2943
3027
|
init_database();
|
|
2944
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3028
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
2945
3029
|
|
|
2946
3030
|
// src/lib/paths.ts
|
|
2947
3031
|
import { homedir as homedir8 } from "os";
|
|
@@ -2979,7 +3063,7 @@ function readClaudeToken() {
|
|
|
2979
3063
|
if (!existsSync8(CREDENTIALS_PATH))
|
|
2980
3064
|
return null;
|
|
2981
3065
|
try {
|
|
2982
|
-
const creds = JSON.parse(
|
|
3066
|
+
const creds = JSON.parse(readFileSync7(CREDENTIALS_PATH, "utf-8"));
|
|
2983
3067
|
const oauth = creds.claudeAiOauth;
|
|
2984
3068
|
if (!oauth?.accessToken)
|
|
2985
3069
|
return null;
|
|
@@ -3115,7 +3199,7 @@ async function ingestClaudeQuota(db, verbose = false) {
|
|
|
3115
3199
|
|
|
3116
3200
|
// src/ingest/codex-quota.ts
|
|
3117
3201
|
init_database();
|
|
3118
|
-
import { existsSync as existsSync9, readFileSync as
|
|
3202
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
3119
3203
|
var WHAM_USAGE_URL = process.env["CODEX_USAGE_URL"] ?? "https://chatgpt.com/backend-api/wham/usage";
|
|
3120
3204
|
function readCodexAuth() {
|
|
3121
3205
|
const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
|
|
@@ -3125,7 +3209,7 @@ function readCodexAuth() {
|
|
|
3125
3209
|
if (!existsSync9(authPath))
|
|
3126
3210
|
return null;
|
|
3127
3211
|
try {
|
|
3128
|
-
const auth = JSON.parse(
|
|
3212
|
+
const auth = JSON.parse(readFileSync8(authPath, "utf-8"));
|
|
3129
3213
|
const token = auth.tokens?.access_token;
|
|
3130
3214
|
if (!token)
|
|
3131
3215
|
return null;
|
|
@@ -3257,24 +3341,6 @@ init_database();
|
|
|
3257
3341
|
|
|
3258
3342
|
// src/lib/cloud-sync.ts
|
|
3259
3343
|
init_database();
|
|
3260
|
-
|
|
3261
|
-
// src/lib/package-metadata.ts
|
|
3262
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
3263
|
-
var cachedMetadata = null;
|
|
3264
|
-
function getPackageMetadata() {
|
|
3265
|
-
if (cachedMetadata)
|
|
3266
|
-
return cachedMetadata;
|
|
3267
|
-
const raw = readFileSync8(new URL("../../package.json", import.meta.url), "utf8");
|
|
3268
|
-
const parsed = JSON.parse(raw);
|
|
3269
|
-
cachedMetadata = {
|
|
3270
|
-
name: parsed.name ?? "@hasna/economy",
|
|
3271
|
-
version: parsed.version ?? "0.0.0"
|
|
3272
|
-
};
|
|
3273
|
-
return cachedMetadata;
|
|
3274
|
-
}
|
|
3275
|
-
var packageMetadata = getPackageMetadata();
|
|
3276
|
-
|
|
3277
|
-
// src/lib/cloud-sync.ts
|
|
3278
3344
|
var CLOUD_TABLES = [
|
|
3279
3345
|
"requests",
|
|
3280
3346
|
"sessions",
|
|
@@ -3440,6 +3506,9 @@ var AGENTS = [
|
|
|
3440
3506
|
"hermes"
|
|
3441
3507
|
];
|
|
3442
3508
|
|
|
3509
|
+
// src/mcp/server.ts
|
|
3510
|
+
init_database();
|
|
3511
|
+
|
|
3443
3512
|
// src/lib/periods.ts
|
|
3444
3513
|
function ymd(date) {
|
|
3445
3514
|
return date.toISOString().substring(0, 10);
|
|
@@ -3468,501 +3537,580 @@ function usageSnapshotFilterForPeriod(period) {
|
|
|
3468
3537
|
}
|
|
3469
3538
|
}
|
|
3470
3539
|
|
|
3471
|
-
// src/mcp/
|
|
3472
|
-
init_database();
|
|
3540
|
+
// src/mcp/server.ts
|
|
3473
3541
|
init_pricing();
|
|
3474
3542
|
init_pricing();
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
const cost = fmtUsd(Number(s["total_cost_usd"] ?? 0));
|
|
3579
|
-
const tok = fmtTok(Number(s["total_tokens"] ?? 0));
|
|
3580
|
-
return `${id} ${agent.padEnd(9)} ${cost.padEnd(10)} ${tok.padEnd(8)} ${proj}`;
|
|
3581
|
-
}
|
|
3582
|
-
function text(text2) {
|
|
3583
|
-
return { content: [{ type: "text", text: text2 }] };
|
|
3584
|
-
}
|
|
3585
|
-
function textError(message) {
|
|
3586
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
3587
|
-
}
|
|
3588
|
-
server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z.string().optional() }, async ({ query }) => {
|
|
3589
|
-
const q = query?.toLowerCase();
|
|
3590
|
-
const matches = q ? TOOL_NAMES.filter((name) => name.includes(q)) : [...TOOL_NAMES];
|
|
3591
|
-
return text(matches.join(", "));
|
|
3592
|
-
});
|
|
3593
|
-
server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z.array(z.string()) }, async ({ names }) => {
|
|
3594
|
-
const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
|
|
3543
|
+
var MCP_NAME = "economy";
|
|
3544
|
+
var DEFAULT_MCP_HTTP_PORT = 8860;
|
|
3545
|
+
function buildServer() {
|
|
3546
|
+
const db = openDatabase();
|
|
3547
|
+
ensurePricingSeeded(db);
|
|
3548
|
+
const server = new McpServer({
|
|
3549
|
+
name: MCP_NAME,
|
|
3550
|
+
version: packageMetadata.version
|
|
3551
|
+
});
|
|
3552
|
+
const _econAgents = new Map;
|
|
3553
|
+
const TOOL_NAMES = [
|
|
3554
|
+
"get_cost_summary",
|
|
3555
|
+
"get_sessions",
|
|
3556
|
+
"get_top_sessions",
|
|
3557
|
+
"get_model_breakdown",
|
|
3558
|
+
"get_project_breakdown",
|
|
3559
|
+
"get_agent_breakdown",
|
|
3560
|
+
"get_account_breakdown",
|
|
3561
|
+
"get_budget_status",
|
|
3562
|
+
"set_budget",
|
|
3563
|
+
"remove_budget",
|
|
3564
|
+
"get_pricing",
|
|
3565
|
+
"set_pricing",
|
|
3566
|
+
"remove_pricing",
|
|
3567
|
+
"get_daily",
|
|
3568
|
+
"get_billing_summary",
|
|
3569
|
+
"get_session_detail",
|
|
3570
|
+
"get_usage",
|
|
3571
|
+
"get_savings",
|
|
3572
|
+
"list_subscriptions",
|
|
3573
|
+
"set_subscription",
|
|
3574
|
+
"remove_subscription",
|
|
3575
|
+
"sync",
|
|
3576
|
+
"search_tools",
|
|
3577
|
+
"describe_tools",
|
|
3578
|
+
"get_goals",
|
|
3579
|
+
"set_goal",
|
|
3580
|
+
"remove_goal",
|
|
3581
|
+
"list_machines",
|
|
3582
|
+
"register_agent",
|
|
3583
|
+
"heartbeat",
|
|
3584
|
+
"set_focus",
|
|
3585
|
+
"list_agents",
|
|
3586
|
+
"send_feedback"
|
|
3587
|
+
];
|
|
3588
|
+
const TOOL_DESCRIPTIONS = {
|
|
3589
|
+
get_cost_summary: "period(today|week|month|year|all), machine?(hostname) -> {total_usd, sessions, requests, tokens, summary}",
|
|
3590
|
+
get_sessions: `agent(${AGENTS.join("|")}), project(partial), account?(key/name/email), machine?(hostname), limit(20) -> compact session table`,
|
|
3591
|
+
get_top_sessions: `n(10), agent(${AGENTS.join("|")}) -> top sessions by cost`,
|
|
3592
|
+
list_machines: "no params -> machine_id, sessions, requests, cost, last_active",
|
|
3593
|
+
get_model_breakdown: "no params -> model, requests, tokens, cost",
|
|
3594
|
+
get_project_breakdown: "period?(today|week|month|year|all) -> project_name, sessions, tokens, cost",
|
|
3595
|
+
get_agent_breakdown: "period?(today|week|month|year|all) -> agent, sessions, requests, tokens, api-equivalent, billable, included",
|
|
3596
|
+
get_account_breakdown: "period?(today|week|month|year|all) -> account profile, sessions, requests, tokens, api-equivalent, billable, included",
|
|
3597
|
+
get_budget_status: "no params -> budget limits, current spend, percent_used, is_over_alert",
|
|
3598
|
+
set_budget: "period(daily|weekly|monthly), limit_usd, project_path?, agent?, alert_at_percent? -> create budget",
|
|
3599
|
+
remove_budget: "id -> delete budget",
|
|
3600
|
+
get_pricing: "no params -> model pricing rows with input, output, cache read/write, and cache storage rates",
|
|
3601
|
+
set_pricing: "model, input_per_1m, output_per_1m, cache_read_per_1m?, cache_write_per_1m?, cache_write_1h_per_1m?, cache_storage_per_1m_hour? -> create/update pricing",
|
|
3602
|
+
remove_pricing: "model -> delete pricing row",
|
|
3603
|
+
get_daily: "days(30) -> daily cost table grouped by date and agent",
|
|
3604
|
+
get_billing_summary: "period(today|yesterday|week|month|year|all) -> actual provider billing totals",
|
|
3605
|
+
get_session_detail: "session_id(prefix ok) -> per-request breakdown with model, tokens, cost",
|
|
3606
|
+
get_usage: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> usage snapshots and all-machine summary`,
|
|
3607
|
+
get_savings: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> subscription/API-equivalent savings`,
|
|
3608
|
+
list_subscriptions: "no params -> configured subscription plans and included usage",
|
|
3609
|
+
set_subscription: `provider, plan, monthly_fee_usd?, included_usage_usd?, agent?(${AGENTS.join("|")}) -> create/update subscription plan`,
|
|
3610
|
+
remove_subscription: "id -> delete subscription plan",
|
|
3611
|
+
sync: `sources(all|${AGENTS.join("|")}) -> ingest latest cost data`,
|
|
3612
|
+
search_tools: "query substring -> tool name list",
|
|
3613
|
+
describe_tools: "names[] -> one-line parameter hints",
|
|
3614
|
+
get_goals: "no params -> goal progress summary",
|
|
3615
|
+
set_goal: "period(day|week|month|year), limit_usd, project_path?, agent? -> create goal",
|
|
3616
|
+
remove_goal: "id -> delete goal",
|
|
3617
|
+
register_agent: "name, session_id? -> register agent session",
|
|
3618
|
+
heartbeat: "agent_id -> update last_seen_at",
|
|
3619
|
+
set_focus: "agent_id, project_id? -> set active project context",
|
|
3620
|
+
list_agents: "no params -> registered agent list",
|
|
3621
|
+
send_feedback: "message, email?, category? -> save feedback locally"
|
|
3622
|
+
};
|
|
3623
|
+
const fmtUsd = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
3624
|
+
const fmtTok = (n) => n >= 1e9 ? `${(n / 1e9).toFixed(1)}B` : n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
|
3625
|
+
function fmtSession(s) {
|
|
3626
|
+
const id = String(s["id"] ?? "").slice(0, 8);
|
|
3627
|
+
const agent = String(s["agent"] ?? "");
|
|
3628
|
+
const proj = String(s["project_name"] || s["project_path"] || "\u2014").slice(0, 20);
|
|
3629
|
+
const cost = fmtUsd(Number(s["total_cost_usd"] ?? 0));
|
|
3630
|
+
const tok = fmtTok(Number(s["total_tokens"] ?? 0));
|
|
3631
|
+
return `${id} ${agent.padEnd(6)} ${cost.padEnd(10)} ${tok.padEnd(8)} ${proj}`;
|
|
3632
|
+
}
|
|
3633
|
+
function text(text2) {
|
|
3634
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
3635
|
+
}
|
|
3636
|
+
function textError(message) {
|
|
3637
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
3638
|
+
}
|
|
3639
|
+
server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z.string().optional() }, async ({ query }) => {
|
|
3640
|
+
const q = query?.toLowerCase();
|
|
3641
|
+
const matches = q ? TOOL_NAMES.filter((name) => name.includes(q)) : [...TOOL_NAMES];
|
|
3642
|
+
return text(matches.join(", "));
|
|
3643
|
+
});
|
|
3644
|
+
server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z.array(z.string()) }, async ({ names }) => {
|
|
3645
|
+
const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
|
|
3595
3646
|
`);
|
|
3596
|
-
|
|
3597
|
-
});
|
|
3598
|
-
server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3647
|
+
return text(result);
|
|
3648
|
+
});
|
|
3649
|
+
server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
|
|
3650
|
+
const resolved = period ?? "today";
|
|
3651
|
+
const s = querySummary(db, resolved, machine);
|
|
3652
|
+
const machineLabel = machine ? ` on ${machine}` : "";
|
|
3653
|
+
return text([
|
|
3654
|
+
`period: ${resolved}${machineLabel}`,
|
|
3655
|
+
`cost: ${fmtUsd(s.total_usd)}`,
|
|
3656
|
+
`sessions: ${s.sessions}`,
|
|
3657
|
+
`requests: ${s.requests.toLocaleString()}`,
|
|
3658
|
+
`tokens: ${fmtTok(s.tokens)}`,
|
|
3659
|
+
`summary: You've spent ${fmtUsd(s.total_usd)} ${resolved === "all" ? "total" : resolved}${machineLabel} across ${s.sessions} sessions (${s.requests.toLocaleString()} requests, ${fmtTok(s.tokens)} tokens)`
|
|
3660
|
+
].join(`
|
|
3610
3661
|
`));
|
|
3611
|
-
});
|
|
3612
|
-
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
|
|
3613
|
-
agent: z.enum(AGENTS).optional(),
|
|
3614
|
-
project: z.string().optional(),
|
|
3615
|
-
account: z.string().optional(),
|
|
3616
|
-
machine: z.string().optional(),
|
|
3617
|
-
limit: z.number().int().positive().max(100).optional()
|
|
3618
|
-
}, async ({ agent, project, account, machine, limit }) => {
|
|
3619
|
-
const sessions = querySessions(db, {
|
|
3620
|
-
agent,
|
|
3621
|
-
project,
|
|
3622
|
-
account,
|
|
3623
|
-
machine,
|
|
3624
|
-
limit: limit ?? 20
|
|
3625
3662
|
});
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3663
|
+
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
|
|
3664
|
+
agent: z.enum(AGENTS).optional(),
|
|
3665
|
+
project: z.string().optional(),
|
|
3666
|
+
account: z.string().optional(),
|
|
3667
|
+
machine: z.string().optional(),
|
|
3668
|
+
limit: z.number().int().positive().max(100).optional()
|
|
3669
|
+
}, async ({ agent, project, account, machine, limit }) => {
|
|
3670
|
+
const sessions = querySessions(db, {
|
|
3671
|
+
agent,
|
|
3672
|
+
project,
|
|
3673
|
+
account,
|
|
3674
|
+
machine,
|
|
3675
|
+
limit: limit ?? 20
|
|
3676
|
+
});
|
|
3677
|
+
const lines = ["id agent cost tokens project"];
|
|
3678
|
+
for (const session of sessions)
|
|
3679
|
+
lines.push(fmtSession(session));
|
|
3680
|
+
return text(lines.join(`
|
|
3630
3681
|
`));
|
|
3631
|
-
});
|
|
3632
|
-
server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
}, async ({ n, agent }) => {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3682
|
+
});
|
|
3683
|
+
server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
|
|
3684
|
+
n: z.number().int().positive().max(100).optional(),
|
|
3685
|
+
agent: z.enum(AGENTS).optional()
|
|
3686
|
+
}, async ({ n, agent }) => {
|
|
3687
|
+
const sessions = queryTopSessions(db, n ?? 10, agent);
|
|
3688
|
+
const lines = ["rank id agent cost tokens project"];
|
|
3689
|
+
sessions.forEach((session, i) => lines.push(`${String(i + 1).padEnd(5)} ${fmtSession(session)}`));
|
|
3690
|
+
return text(lines.join(`
|
|
3640
3691
|
`));
|
|
3641
|
-
});
|
|
3642
|
-
server.tool("get_model_breakdown", "Cost per model. No params.", {}, async () => {
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3692
|
+
});
|
|
3693
|
+
server.tool("get_model_breakdown", "Cost per model. No params.", {}, async () => {
|
|
3694
|
+
const rows = queryModelBreakdown(db);
|
|
3695
|
+
const lines = ["model agent reqs tokens cost"];
|
|
3696
|
+
for (const row of rows) {
|
|
3697
|
+
lines.push(`${String(row["model"]).slice(0, 30).padEnd(31)}${String(row["agent"]).padEnd(10)}${String(row["requests"]).padEnd(8)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
|
|
3698
|
+
}
|
|
3699
|
+
return text(lines.join(`
|
|
3649
3700
|
`));
|
|
3650
|
-
});
|
|
3651
|
-
server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all).", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3701
|
+
});
|
|
3702
|
+
server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all).", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3703
|
+
const rows = queryProjectBreakdown(db, period ?? "all");
|
|
3704
|
+
const lines = ["project sessions tokens cost"];
|
|
3705
|
+
for (const row of rows) {
|
|
3706
|
+
const name = String(row["project_name"] || row["project_path"] || "\u2014").slice(0, 20);
|
|
3707
|
+
lines.push(`${name.padEnd(21)}${String(row["sessions"]).padEnd(9)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
|
|
3708
|
+
}
|
|
3709
|
+
return text(lines.join(`
|
|
3659
3710
|
`));
|
|
3660
|
-
});
|
|
3661
|
-
server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3711
|
+
});
|
|
3712
|
+
server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3713
|
+
const rows = queryAgentBreakdown(db, period ?? "all");
|
|
3714
|
+
if (rows.length === 0)
|
|
3715
|
+
return text("No agent usage yet.");
|
|
3716
|
+
const lines = ["agent sessions requests tokens api_eq billable included"];
|
|
3717
|
+
for (const row of rows) {
|
|
3718
|
+
lines.push(`${String(row["agent"]).slice(0, 10).padEnd(11)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
|
|
3719
|
+
}
|
|
3720
|
+
return text(lines.join(`
|
|
3670
3721
|
`));
|
|
3671
|
-
});
|
|
3672
|
-
server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3722
|
+
});
|
|
3723
|
+
server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3724
|
+
const rows = queryAccountBreakdown(db, period ?? "all");
|
|
3725
|
+
if (rows.length === 0)
|
|
3726
|
+
return text("No account-attributed sessions yet.");
|
|
3727
|
+
const lines = ["account agent sessions requests tokens api_eq billable included"];
|
|
3728
|
+
for (const row of rows) {
|
|
3729
|
+
const label = String(row["account_email"] || row["account_name"] || row["account_key"] || "\u2014").slice(0, 20);
|
|
3730
|
+
lines.push(`${label.padEnd(21)}` + `${String(row["account_tool"] ?? "").slice(0, 10).padEnd(11)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
|
|
3731
|
+
}
|
|
3732
|
+
return text(lines.join(`
|
|
3682
3733
|
`));
|
|
3683
|
-
});
|
|
3684
|
-
server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. No params.", {}, async () => {
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3734
|
+
});
|
|
3735
|
+
server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. No params.", {}, async () => {
|
|
3736
|
+
const budgets = getBudgetStatuses(db);
|
|
3737
|
+
if (budgets.length === 0)
|
|
3738
|
+
return text("No budgets set.");
|
|
3739
|
+
const lines = ["scope period spent limit used% status"];
|
|
3740
|
+
for (const budget of budgets) {
|
|
3741
|
+
const scope = String(budget["project_path"] ?? "global").slice(0, 20);
|
|
3742
|
+
const pct = Number(budget["percent_used"]).toFixed(1);
|
|
3743
|
+
const status = budget["is_over_limit"] ? "OVER" : budget["is_over_alert"] ? "ALERT" : "OK";
|
|
3744
|
+
lines.push(`${scope.padEnd(21)}${String(budget["period"]).padEnd(9)}${fmtUsd(Number(budget["current_spend_usd"])).padEnd(11)}${fmtUsd(Number(budget["limit_usd"])).padEnd(11)}${pct}%`.padEnd(49) + ` ${status}`);
|
|
3745
|
+
}
|
|
3746
|
+
return text(lines.join(`
|
|
3696
3747
|
`));
|
|
3697
|
-
});
|
|
3698
|
-
server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
|
|
3699
|
-
period: z.enum(["daily", "weekly", "monthly"]),
|
|
3700
|
-
limit_usd: z.number().positive(),
|
|
3701
|
-
project_path: z.string().optional(),
|
|
3702
|
-
agent: z.enum(AGENTS).optional(),
|
|
3703
|
-
alert_at_percent: z.number().positive().max(100).optional()
|
|
3704
|
-
}, async ({ period, limit_usd, project_path, agent, alert_at_percent }) => {
|
|
3705
|
-
const now = new Date().toISOString();
|
|
3706
|
-
const id = randomUUID();
|
|
3707
|
-
upsertBudget(db, {
|
|
3708
|
-
id,
|
|
3709
|
-
project_path: project_path ?? null,
|
|
3710
|
-
agent: agent ?? null,
|
|
3711
|
-
period,
|
|
3712
|
-
limit_usd,
|
|
3713
|
-
alert_at_percent: alert_at_percent ?? 80,
|
|
3714
|
-
created_at: now,
|
|
3715
|
-
updated_at: now
|
|
3716
3748
|
});
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3749
|
+
server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
|
|
3750
|
+
period: z.enum(["daily", "weekly", "monthly"]),
|
|
3751
|
+
limit_usd: z.number().positive(),
|
|
3752
|
+
project_path: z.string().optional(),
|
|
3753
|
+
agent: z.enum(AGENTS).optional(),
|
|
3754
|
+
alert_at_percent: z.number().positive().max(100).optional()
|
|
3755
|
+
}, async ({ period, limit_usd, project_path, agent, alert_at_percent }) => {
|
|
3756
|
+
const now = new Date().toISOString();
|
|
3757
|
+
const id = randomUUID();
|
|
3758
|
+
upsertBudget(db, {
|
|
3759
|
+
id,
|
|
3760
|
+
project_path: project_path ?? null,
|
|
3761
|
+
agent: agent ?? null,
|
|
3762
|
+
period,
|
|
3763
|
+
limit_usd,
|
|
3764
|
+
alert_at_percent: alert_at_percent ?? 80,
|
|
3765
|
+
created_at: now,
|
|
3766
|
+
updated_at: now
|
|
3767
|
+
});
|
|
3768
|
+
return text(`Budget set: ${id}`);
|
|
3769
|
+
});
|
|
3770
|
+
server.tool("remove_budget", "Delete a budget by id.", { id: z.string() }, async ({ id }) => {
|
|
3771
|
+
deleteBudget(db, id);
|
|
3772
|
+
return text("Budget removed.");
|
|
3773
|
+
});
|
|
3774
|
+
server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage.", {}, async () => {
|
|
3775
|
+
const rows = listModelPricing(db);
|
|
3776
|
+
const lines = ["model input output cache-r cache-w cache-1h storage-h"];
|
|
3777
|
+
for (const row of rows) {
|
|
3778
|
+
lines.push(`${row.model.slice(0, 30).padEnd(31)}` + `${fmtUsd(row.input_per_1m).padEnd(9)}` + `${fmtUsd(row.output_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_read_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_1h_per_1m ?? 0).padEnd(9)}` + `${fmtUsd(row.cache_storage_per_1m_hour ?? 0)}`);
|
|
3779
|
+
}
|
|
3780
|
+
return text(lines.join(`
|
|
3730
3781
|
`));
|
|
3731
|
-
});
|
|
3732
|
-
server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
|
|
3733
|
-
model: z.string().min(1),
|
|
3734
|
-
input_per_1m: z.number().nonnegative(),
|
|
3735
|
-
output_per_1m: z.number().nonnegative(),
|
|
3736
|
-
cache_read_per_1m: z.number().nonnegative().optional(),
|
|
3737
|
-
cache_write_per_1m: z.number().nonnegative().optional(),
|
|
3738
|
-
cache_write_1h_per_1m: z.number().nonnegative().optional(),
|
|
3739
|
-
cache_storage_per_1m_hour: z.number().nonnegative().optional()
|
|
3740
|
-
}, async (input) => {
|
|
3741
|
-
const model = input.model.trim();
|
|
3742
|
-
if (!model)
|
|
3743
|
-
return textError("model is required");
|
|
3744
|
-
upsertModelPricing(db, {
|
|
3745
|
-
model,
|
|
3746
|
-
input_per_1m: input.input_per_1m,
|
|
3747
|
-
output_per_1m: input.output_per_1m,
|
|
3748
|
-
cache_read_per_1m: input.cache_read_per_1m ?? 0,
|
|
3749
|
-
cache_write_per_1m: input.cache_write_per_1m ?? 0,
|
|
3750
|
-
cache_write_1h_per_1m: input.cache_write_1h_per_1m ?? 0,
|
|
3751
|
-
cache_storage_per_1m_hour: input.cache_storage_per_1m_hour ?? 0,
|
|
3752
|
-
updated_at: new Date().toISOString()
|
|
3753
3782
|
});
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3783
|
+
server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
|
|
3784
|
+
model: z.string().min(1),
|
|
3785
|
+
input_per_1m: z.number().nonnegative(),
|
|
3786
|
+
output_per_1m: z.number().nonnegative(),
|
|
3787
|
+
cache_read_per_1m: z.number().nonnegative().optional(),
|
|
3788
|
+
cache_write_per_1m: z.number().nonnegative().optional(),
|
|
3789
|
+
cache_write_1h_per_1m: z.number().nonnegative().optional(),
|
|
3790
|
+
cache_storage_per_1m_hour: z.number().nonnegative().optional()
|
|
3791
|
+
}, async (input) => {
|
|
3792
|
+
const model = input.model.trim();
|
|
3793
|
+
if (!model)
|
|
3794
|
+
return textError("model is required");
|
|
3795
|
+
upsertModelPricing(db, {
|
|
3796
|
+
model,
|
|
3797
|
+
input_per_1m: input.input_per_1m,
|
|
3798
|
+
output_per_1m: input.output_per_1m,
|
|
3799
|
+
cache_read_per_1m: input.cache_read_per_1m ?? 0,
|
|
3800
|
+
cache_write_per_1m: input.cache_write_per_1m ?? 0,
|
|
3801
|
+
cache_write_1h_per_1m: input.cache_write_1h_per_1m ?? 0,
|
|
3802
|
+
cache_storage_per_1m_hour: input.cache_storage_per_1m_hour ?? 0,
|
|
3803
|
+
updated_at: new Date().toISOString()
|
|
3804
|
+
});
|
|
3805
|
+
return text(`Pricing set: ${model}`);
|
|
3806
|
+
});
|
|
3807
|
+
server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z.string() }, async ({ model }) => {
|
|
3808
|
+
deleteModelPricing(db, model);
|
|
3809
|
+
return text("Pricing removed.");
|
|
3810
|
+
});
|
|
3811
|
+
server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z.number().int().positive().max(365).optional() }, async ({ days }) => {
|
|
3812
|
+
const rows = queryDailyBreakdown(db, days ?? 30);
|
|
3813
|
+
const byDate = new Map;
|
|
3814
|
+
for (const row of rows) {
|
|
3815
|
+
const date = String(row["date"]);
|
|
3816
|
+
const entry = byDate.get(date) ?? { claude: 0, takumi: 0, codex: 0, gemini: 0 };
|
|
3817
|
+
if (row["agent"] === "claude")
|
|
3818
|
+
entry.claude += Number(row["cost_usd"]);
|
|
3819
|
+
else if (row["agent"] === "takumi")
|
|
3820
|
+
entry.takumi += Number(row["cost_usd"]);
|
|
3821
|
+
else if (row["agent"] === "codex")
|
|
3822
|
+
entry.codex += Number(row["cost_usd"]);
|
|
3823
|
+
else if (row["agent"] === "gemini")
|
|
3824
|
+
entry.gemini += Number(row["cost_usd"]);
|
|
3825
|
+
byDate.set(date, entry);
|
|
3826
|
+
}
|
|
3827
|
+
const lines = ["date claude takumi codex gemini total"];
|
|
3828
|
+
for (const [date, costs] of [...byDate.entries()].sort()) {
|
|
3829
|
+
const total = costs.claude + costs.takumi + costs.codex + costs.gemini;
|
|
3830
|
+
lines.push(`${date} ${fmtUsd(costs.claude).padEnd(11)}${fmtUsd(costs.takumi).padEnd(11)}${fmtUsd(costs.codex).padEnd(11)}${fmtUsd(costs.gemini).padEnd(11)}${fmtUsd(total)}`);
|
|
3831
|
+
}
|
|
3832
|
+
return text(lines.join(`
|
|
3776
3833
|
`));
|
|
3777
|
-
});
|
|
3778
|
-
server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3834
|
+
});
|
|
3835
|
+
server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
3836
|
+
const summary = queryBillingSummary(db, period ?? "month");
|
|
3837
|
+
const lines = ["provider billed"];
|
|
3838
|
+
for (const [provider, cost] of Object.entries(summary.by_provider)) {
|
|
3839
|
+
lines.push(`${provider.padEnd(11)}${fmtUsd(cost)}`);
|
|
3840
|
+
}
|
|
3841
|
+
lines.push(`total ${fmtUsd(summary.total_usd)}`);
|
|
3842
|
+
return text(lines.join(`
|
|
3786
3843
|
`));
|
|
3787
|
-
});
|
|
3788
|
-
server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok)", { session_id: z.string() }, async ({ session_id }) => {
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3844
|
+
});
|
|
3845
|
+
server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok)", { session_id: z.string() }, async ({ session_id }) => {
|
|
3846
|
+
const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(session_id, `${session_id}%`);
|
|
3847
|
+
if (!session)
|
|
3848
|
+
return textError(`Session not found: ${session_id}`);
|
|
3849
|
+
const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC LIMIT 50`).all(session["id"]);
|
|
3850
|
+
const lines = [
|
|
3851
|
+
`session: ${String(session["id"]).slice(0, 16)}`,
|
|
3852
|
+
`agent: ${session["agent"]} project: ${session["project_name"] || "\u2014"}`,
|
|
3853
|
+
`cost: ${fmtUsd(Number(session["total_cost_usd"]))} tokens: ${fmtTok(Number(session["total_tokens"]))} requests: ${session["request_count"]}`,
|
|
3854
|
+
"",
|
|
3855
|
+
"time model input output cache-r cache-5m cache-1h cost"
|
|
3856
|
+
];
|
|
3857
|
+
for (const request of requests) {
|
|
3858
|
+
lines.push(`${String(request["timestamp"]).slice(11, 19)} ` + `${String(request["model"]).slice(0, 22).padEnd(23)}` + `${fmtTok(Number(request["input_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["output_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_read_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_create_5m_tokens"] ?? request["cache_create_tokens"] ?? 0)).padEnd(9)}` + `${fmtTok(Number(request["cache_create_1h_tokens"] ?? 0)).padEnd(9)}` + `${fmtUsd(Number(request["cost_usd"]))}`);
|
|
3859
|
+
}
|
|
3860
|
+
return text(lines.join(`
|
|
3804
3861
|
`));
|
|
3805
|
-
});
|
|
3806
|
-
server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, { sources: z.enum(["all", ...AGENTS]).optional() }, async ({ sources }) => {
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
});
|
|
3812
|
-
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
});
|
|
3818
|
-
server.tool("get_savings", "Subscription vs API savings summary", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3819
|
-
|
|
3820
|
-
});
|
|
3821
|
-
server.tool("list_subscriptions", "List configured subscription plans
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3862
|
+
});
|
|
3863
|
+
server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, { sources: z.enum(["all", ...AGENTS]).optional() }, async ({ sources }) => {
|
|
3864
|
+
const selected = sources ?? "all";
|
|
3865
|
+
const opts = selected === "all" ? {} : { [selected]: true };
|
|
3866
|
+
const result = await syncAll(db, opts);
|
|
3867
|
+
return text(JSON.stringify(result, null, 2));
|
|
3868
|
+
});
|
|
3869
|
+
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3870
|
+
const p = period ?? "month";
|
|
3871
|
+
const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
|
|
3872
|
+
const summary = querySummary(db, p, undefined, true);
|
|
3873
|
+
return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
|
|
3874
|
+
});
|
|
3875
|
+
server.tool("get_savings", "Subscription vs API savings summary", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3876
|
+
return text(JSON.stringify(querySavingsSummary(db, period ?? "month", agent), null, 2));
|
|
3877
|
+
});
|
|
3878
|
+
server.tool("list_subscriptions", "List configured subscription plans used by savings calculations.", {}, async () => {
|
|
3879
|
+
const rows = listSubscriptions(db);
|
|
3880
|
+
if (rows.length === 0)
|
|
3881
|
+
return text("No subscriptions configured.");
|
|
3882
|
+
const lines = ["id provider plan agent fee included active"];
|
|
3883
|
+
for (const row of rows) {
|
|
3884
|
+
lines.push(`${row.id.slice(0, 18).padEnd(19)}` + `${row.provider.slice(0, 10).padEnd(11)}` + `${row.plan.slice(0, 10).padEnd(11)}` + `${(row.agent ?? "all").slice(0, 10).padEnd(11)}` + `${fmtUsd(row.monthly_fee_usd).padEnd(10)}` + `${fmtUsd(row.included_usage_usd).padEnd(10)}` + `${row.active ? "yes" : "no"}`);
|
|
3885
|
+
}
|
|
3886
|
+
return text(lines.join(`
|
|
3830
3887
|
`));
|
|
3831
|
-
});
|
|
3832
|
-
server.tool("set_subscription",
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
}, async (input) => {
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
}
|
|
3861
|
-
|
|
3862
|
-
return text("
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
});
|
|
3868
|
-
server.tool("
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
return text(
|
|
3880
|
-
const lines = ["period scope limit spent used% status"];
|
|
3881
|
-
for (const goal of goals) {
|
|
3882
|
-
const scope = String(goal["project_path"] ?? goal["agent"] ?? "global").slice(0, 20);
|
|
3883
|
-
const pct = Number(goal["percent_used"]).toFixed(1);
|
|
3884
|
-
const status = goal["is_over"] ? "OVER" : goal["is_at_risk"] ? "AT RISK" : "ON TRACK";
|
|
3885
|
-
lines.push(`${String(goal["period"]).padEnd(9)}${scope.padEnd(21)}${fmtUsd(Number(goal["limit_usd"])).padEnd(11)}${fmtUsd(Number(goal["current_spend_usd"])).padEnd(11)}${pct}% ${status}`);
|
|
3886
|
-
}
|
|
3887
|
-
return text(lines.join(`
|
|
3888
|
+
});
|
|
3889
|
+
server.tool("set_subscription", "Create or update a subscription plan used by subscription-vs-API savings calculations.", {
|
|
3890
|
+
id: z.string().optional(),
|
|
3891
|
+
provider: z.string().min(1),
|
|
3892
|
+
plan: z.string().min(1),
|
|
3893
|
+
agent: z.enum(AGENTS).optional(),
|
|
3894
|
+
monthly_fee_usd: z.number().nonnegative().optional(),
|
|
3895
|
+
included_usage_usd: z.number().nonnegative().optional(),
|
|
3896
|
+
billing_cycle_start: z.string().optional(),
|
|
3897
|
+
reset_policy: z.string().optional(),
|
|
3898
|
+
active: z.boolean().optional()
|
|
3899
|
+
}, async (input) => {
|
|
3900
|
+
const now = new Date().toISOString();
|
|
3901
|
+
const row = {
|
|
3902
|
+
id: input.id ?? randomUUID(),
|
|
3903
|
+
provider: input.provider,
|
|
3904
|
+
plan: input.plan,
|
|
3905
|
+
agent: input.agent ?? null,
|
|
3906
|
+
monthly_fee_usd: input.monthly_fee_usd ?? 0,
|
|
3907
|
+
included_usage_usd: input.included_usage_usd ?? 0,
|
|
3908
|
+
billing_cycle_start: input.billing_cycle_start ?? null,
|
|
3909
|
+
reset_policy: input.reset_policy ?? "monthly",
|
|
3910
|
+
active: input.active === false ? 0 : 1,
|
|
3911
|
+
created_at: now,
|
|
3912
|
+
updated_at: now
|
|
3913
|
+
};
|
|
3914
|
+
upsertSubscription(db, row);
|
|
3915
|
+
return text(JSON.stringify(row, null, 2));
|
|
3916
|
+
});
|
|
3917
|
+
server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z.string() }, async ({ id }) => {
|
|
3918
|
+
deleteSubscription(db, id);
|
|
3919
|
+
return text("Subscription removed.");
|
|
3920
|
+
});
|
|
3921
|
+
server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z.string(), input_tokens: z.number().optional(), output_tokens: z.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
|
|
3922
|
+
const cost = computeCostFromDb(db, model, input_tokens ?? 0, output_tokens ?? 0, 0, 0, 0);
|
|
3923
|
+
return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
|
|
3924
|
+
});
|
|
3925
|
+
server.tool("get_goals", "All spending goals with current progress. No params.", {}, async () => {
|
|
3926
|
+
const goals = getGoalStatuses(db);
|
|
3927
|
+
if (goals.length === 0)
|
|
3928
|
+
return text("No goals set.");
|
|
3929
|
+
const lines = ["period scope limit spent used% status"];
|
|
3930
|
+
for (const goal of goals) {
|
|
3931
|
+
const scope = String(goal["project_path"] ?? goal["agent"] ?? "global").slice(0, 20);
|
|
3932
|
+
const pct = Number(goal["percent_used"]).toFixed(1);
|
|
3933
|
+
const status = goal["is_over"] ? "OVER" : goal["is_at_risk"] ? "AT RISK" : "ON TRACK";
|
|
3934
|
+
lines.push(`${String(goal["period"]).padEnd(9)}${scope.padEnd(21)}${fmtUsd(Number(goal["limit_usd"])).padEnd(11)}${fmtUsd(Number(goal["current_spend_usd"])).padEnd(11)}${pct}% ${status}`);
|
|
3935
|
+
}
|
|
3936
|
+
return text(lines.join(`
|
|
3888
3937
|
`));
|
|
3889
|
-
});
|
|
3890
|
-
server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
|
|
3891
|
-
period: z.enum(["day", "week", "month", "year"]),
|
|
3892
|
-
limit_usd: z.number().positive(),
|
|
3893
|
-
project_path: z.string().optional(),
|
|
3894
|
-
agent: z.enum(AGENTS).optional()
|
|
3895
|
-
}, async ({ period, limit_usd, project_path, agent }) => {
|
|
3896
|
-
const now = new Date().toISOString();
|
|
3897
|
-
upsertGoal(db, {
|
|
3898
|
-
id: randomUUID(),
|
|
3899
|
-
period,
|
|
3900
|
-
project_path: project_path ?? null,
|
|
3901
|
-
agent: agent ?? null,
|
|
3902
|
-
limit_usd,
|
|
3903
|
-
created_at: now,
|
|
3904
|
-
updated_at: now
|
|
3905
3938
|
});
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
})
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3939
|
+
server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
|
|
3940
|
+
period: z.enum(["day", "week", "month", "year"]),
|
|
3941
|
+
limit_usd: z.number().positive(),
|
|
3942
|
+
project_path: z.string().optional(),
|
|
3943
|
+
agent: z.string().optional()
|
|
3944
|
+
}, async ({ period, limit_usd, project_path, agent }) => {
|
|
3945
|
+
const now = new Date().toISOString();
|
|
3946
|
+
upsertGoal(db, {
|
|
3947
|
+
id: randomUUID(),
|
|
3948
|
+
period,
|
|
3949
|
+
project_path: project_path ?? null,
|
|
3950
|
+
agent: agent ?? null,
|
|
3951
|
+
limit_usd,
|
|
3952
|
+
created_at: now,
|
|
3953
|
+
updated_at: now
|
|
3954
|
+
});
|
|
3955
|
+
return text(`Goal set: ${period} $${limit_usd}`);
|
|
3956
|
+
});
|
|
3957
|
+
server.tool("remove_goal", "Delete a goal by id.", { id: z.string() }, async ({ id }) => {
|
|
3958
|
+
deleteGoal(db, id);
|
|
3959
|
+
return text("Goal removed.");
|
|
3960
|
+
});
|
|
3961
|
+
server.tool("list_machines", "List all machines that have synced data. No params.", {}, async () => {
|
|
3962
|
+
const machines = listMachines(db);
|
|
3963
|
+
if (machines.length === 0)
|
|
3964
|
+
return text(`No machine data yet. Current machine: ${getMachineId()}`);
|
|
3965
|
+
const lines = ["machine sessions requests cost last_active"];
|
|
3966
|
+
for (const m of machines) {
|
|
3967
|
+
lines.push(`${m.machine_id.padEnd(17)}${String(m.sessions).padEnd(10)}${String(m.requests).padEnd(10)}${fmtUsd(m.total_cost_usd).padEnd(12)}${m.last_active?.substring(0, 16) ?? "\u2014"}`);
|
|
3968
|
+
}
|
|
3969
|
+
lines.push(`
|
|
3921
3970
|
current machine: ${getMachineId()}`);
|
|
3922
|
-
|
|
3971
|
+
return text(lines.join(`
|
|
3923
3972
|
`));
|
|
3924
|
-
});
|
|
3925
|
-
server.tool("register_agent", "Register agent session.", { name: z.string(), session_id: z.string().optional() }, async ({ name }) => {
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
});
|
|
3936
|
-
server.tool("heartbeat", "Update last_seen_at.", { agent_id: z.string() }, async ({ agent_id }) => {
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
});
|
|
3943
|
-
server.tool("set_focus", "Set active project context.", { agent_id: z.string(), project_id: z.string().optional().nullable() }, async ({ agent_id, project_id }) => {
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
});
|
|
3950
|
-
server.tool("list_agents", "List all registered agents.", {}, async () => text(JSON.stringify([..._econAgents.values()])));
|
|
3951
|
-
server.tool("send_feedback", "Send feedback about this service.", {
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
}, async ({ message, email, category }) => {
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3973
|
+
});
|
|
3974
|
+
server.tool("register_agent", "Register agent session.", { name: z.string(), session_id: z.string().optional() }, async ({ name }) => {
|
|
3975
|
+
const existing = [..._econAgents.values()].find((agent2) => agent2.name === name);
|
|
3976
|
+
if (existing) {
|
|
3977
|
+
existing.last_seen_at = new Date().toISOString();
|
|
3978
|
+
return text(JSON.stringify(existing));
|
|
3979
|
+
}
|
|
3980
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
3981
|
+
const agent = { id, name, last_seen_at: new Date().toISOString() };
|
|
3982
|
+
_econAgents.set(id, agent);
|
|
3983
|
+
return text(JSON.stringify(agent));
|
|
3984
|
+
});
|
|
3985
|
+
server.tool("heartbeat", "Update last_seen_at.", { agent_id: z.string() }, async ({ agent_id }) => {
|
|
3986
|
+
const agent = _econAgents.get(agent_id);
|
|
3987
|
+
if (!agent)
|
|
3988
|
+
return textError("Agent not found");
|
|
3989
|
+
agent.last_seen_at = new Date().toISOString();
|
|
3990
|
+
return text(`\u2665 ${agent.name}`);
|
|
3991
|
+
});
|
|
3992
|
+
server.tool("set_focus", "Set active project context.", { agent_id: z.string(), project_id: z.string().optional().nullable() }, async ({ agent_id, project_id }) => {
|
|
3993
|
+
const agent = _econAgents.get(agent_id);
|
|
3994
|
+
if (!agent)
|
|
3995
|
+
return textError("Agent not found");
|
|
3996
|
+
agent.project_id = project_id ?? undefined;
|
|
3997
|
+
return text(project_id ? `Focus: ${project_id}` : "Focus cleared");
|
|
3998
|
+
});
|
|
3999
|
+
server.tool("list_agents", "List all registered agents.", {}, async () => text(JSON.stringify([..._econAgents.values()])));
|
|
4000
|
+
server.tool("send_feedback", "Send feedback about this service.", {
|
|
4001
|
+
message: z.string(),
|
|
4002
|
+
email: z.string().optional(),
|
|
4003
|
+
category: z.enum(["bug", "feature", "general"]).optional()
|
|
4004
|
+
}, async ({ message, email, category }) => {
|
|
4005
|
+
try {
|
|
4006
|
+
db.prepare("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(message, email ?? null, category ?? "general", packageMetadata.version);
|
|
4007
|
+
return text("Feedback saved. Thank you!");
|
|
4008
|
+
} catch (error) {
|
|
4009
|
+
return textError(String(error));
|
|
4010
|
+
}
|
|
4011
|
+
});
|
|
4012
|
+
registerCloudTools(server, MCP_NAME, {
|
|
4013
|
+
dbPath: getDbPath(),
|
|
4014
|
+
migrations: PG_MIGRATIONS
|
|
4015
|
+
});
|
|
4016
|
+
return server;
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
// src/mcp/http.ts
|
|
4020
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
4021
|
+
function isHttpMode(argv = process.argv.slice(2)) {
|
|
4022
|
+
return argv.includes("--http") || process.env["MCP_HTTP"] === "1";
|
|
4023
|
+
}
|
|
4024
|
+
function isStdioMode(argv = process.argv.slice(2)) {
|
|
4025
|
+
return argv.includes("--stdio") || process.env["MCP_STDIO"] === "1";
|
|
4026
|
+
}
|
|
4027
|
+
function resolveHttpPort(argv = process.argv.slice(2)) {
|
|
4028
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4029
|
+
const arg = argv[i];
|
|
4030
|
+
if (arg === "--port" || arg === "-p") {
|
|
4031
|
+
const raw = argv[i + 1];
|
|
4032
|
+
if (!raw)
|
|
4033
|
+
throw new Error(`Invalid port: ${raw ?? ""}`);
|
|
4034
|
+
return parsePort(raw, "port");
|
|
4035
|
+
}
|
|
3961
4036
|
}
|
|
4037
|
+
const fromEnv = process.env["MCP_HTTP_PORT"];
|
|
4038
|
+
if (fromEnv)
|
|
4039
|
+
return parsePort(fromEnv, "MCP_HTTP_PORT");
|
|
4040
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
4041
|
+
}
|
|
4042
|
+
function parsePort(raw, label) {
|
|
4043
|
+
const value = Number(raw);
|
|
4044
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535) {
|
|
4045
|
+
throw new Error(`Invalid ${label}: ${raw}`);
|
|
4046
|
+
}
|
|
4047
|
+
return value;
|
|
4048
|
+
}
|
|
4049
|
+
async function handleMcpHttpRequest(req) {
|
|
4050
|
+
const url = new URL(req.url);
|
|
4051
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
4052
|
+
return Response.json({ status: "ok", name: MCP_NAME });
|
|
4053
|
+
}
|
|
4054
|
+
if (url.pathname === "/mcp") {
|
|
4055
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
4056
|
+
sessionIdGenerator: undefined
|
|
4057
|
+
});
|
|
4058
|
+
const server = buildServer();
|
|
4059
|
+
await server.connect(transport);
|
|
4060
|
+
return transport.handleRequest(req);
|
|
4061
|
+
}
|
|
4062
|
+
return new Response("Not Found", { status: 404 });
|
|
4063
|
+
}
|
|
4064
|
+
function startHttpServer(options = {}) {
|
|
4065
|
+
const port = options.port ?? DEFAULT_MCP_HTTP_PORT;
|
|
4066
|
+
const hostname2 = options.hostname ?? "127.0.0.1";
|
|
4067
|
+
const log = options.log ?? console.error;
|
|
4068
|
+
const server = Bun.serve({
|
|
4069
|
+
port,
|
|
4070
|
+
hostname: hostname2,
|
|
4071
|
+
fetch: handleMcpHttpRequest
|
|
4072
|
+
});
|
|
4073
|
+
const address = `http://${hostname2}:${server.port}`;
|
|
4074
|
+
log(`${MCP_NAME}-mcp HTTP listening on ${address}/mcp (health: ${address}/health)`);
|
|
4075
|
+
return server;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
// src/mcp/index.ts
|
|
4079
|
+
function printHelp() {
|
|
4080
|
+
console.log(`Usage: economy-mcp [options]
|
|
4081
|
+
|
|
4082
|
+
Runs the ${packageMetadata.name} MCP server (stdio by default).
|
|
4083
|
+
|
|
4084
|
+
Options:
|
|
4085
|
+
--http Serve MCP over Streamable HTTP on 127.0.0.1
|
|
4086
|
+
-p, --port <port> HTTP port (default: MCP_HTTP_PORT or 8815)
|
|
4087
|
+
-V, --version output the version number
|
|
4088
|
+
-h, --help display help for command
|
|
4089
|
+
|
|
4090
|
+
Environment:
|
|
4091
|
+
MCP_HTTP=1 Enable HTTP mode
|
|
4092
|
+
MCP_HTTP_PORT Override default HTTP port (8815)`);
|
|
4093
|
+
}
|
|
4094
|
+
var args = process.argv.slice(2);
|
|
4095
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
4096
|
+
printHelp();
|
|
4097
|
+
process.exit(0);
|
|
4098
|
+
}
|
|
4099
|
+
if (args.includes("--version") || args.includes("-V")) {
|
|
4100
|
+
console.log(packageMetadata.version);
|
|
4101
|
+
process.exit(0);
|
|
4102
|
+
}
|
|
4103
|
+
async function main() {
|
|
4104
|
+
if (isStdioMode(args) || !isHttpMode(args)) {
|
|
4105
|
+
const server = buildServer();
|
|
4106
|
+
const transport = new StdioServerTransport;
|
|
4107
|
+
await server.connect(transport);
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
startHttpServer({ port: resolveHttpPort(args) });
|
|
4111
|
+
await new Promise(() => {});
|
|
4112
|
+
}
|
|
4113
|
+
main().catch((error) => {
|
|
4114
|
+
console.error("MCP server error:", error);
|
|
4115
|
+
process.exit(1);
|
|
3962
4116
|
});
|
|
3963
|
-
var transport = new StdioServerTransport;
|
|
3964
|
-
registerCloudTools(server, "economy", {
|
|
3965
|
-
dbPath: getDbPath(),
|
|
3966
|
-
migrations: PG_MIGRATIONS
|
|
3967
|
-
});
|
|
3968
|
-
await server.connect(transport);
|