@agentistics/mcp 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/agentistics-mcp.js +625 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @agentistics/mcp
|
|
2
|
+
|
|
3
|
+
MCP server that exposes your [Claude Code](https://claude.ai/code) analytics as tools for Claude Desktop, Claude Code agents, and any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Reads data from `~/.claude/` (the same data [agentistics](https://github.com/blpsoares/agentistics) visualizes) and exposes it as 12 structured MCP tools.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### With agentistics server (recommended)
|
|
12
|
+
|
|
13
|
+
The MCP server is included and auto-registered when you run `agentop server`. No separate install needed.
|
|
14
|
+
|
|
15
|
+
### Standalone with Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to your `claude_desktop_config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"agentistics": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@agentistics/mcp"],
|
|
25
|
+
"env": {
|
|
26
|
+
"AGENTISTICS_API": "http://localhost:47291"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `AGENTISTICS_API` env var must point to a running agentistics server.
|
|
34
|
+
|
|
35
|
+
## Available tools
|
|
36
|
+
|
|
37
|
+
| Tool | Returns |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `agentistics_summary` | All-time totals: tokens, cost, sessions, streak, cache hit rate |
|
|
40
|
+
| `agentistics_projects` | Per-project token and cost breakdown |
|
|
41
|
+
| `agentistics_sessions` | Recent sessions with duration, model, cost |
|
|
42
|
+
| `agentistics_costs` | Model pricing breakdown and cache savings |
|
|
43
|
+
| `agentistics_component_catalog` | Available dashboard components |
|
|
44
|
+
| `agentistics_get_layouts` | Current custom page layouts |
|
|
45
|
+
| `agentistics_build_layout` | Create a full layout from a component list |
|
|
46
|
+
| `agentistics_add_component` | Add one component to an existing layout |
|
|
47
|
+
| `agentistics_remove_component` | Remove a component by instance ID |
|
|
48
|
+
| `agentistics_create_layout` | Create a new empty layout |
|
|
49
|
+
| `agentistics_set_active_layout` | Switch the active /custom layout |
|
|
50
|
+
| `agentistics_delete_layout` | Delete a layout permanently |
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
Requires a running agentistics server (`agentop server` or the Windows desktop app). The MCP server proxies requests to `http://localhost:47291` by default.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT — part of the [agentistics](https://github.com/blpsoares/agentistics) project.
|
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// agentistics-mcp.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// ../core/src/types.ts
|
|
12
|
+
var MODEL_PRICING = {
|
|
13
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
14
|
+
"claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
15
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
16
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
17
|
+
"claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
18
|
+
"claude-opus-4-1-20250805": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
19
|
+
"claude-opus-4-20250514": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
20
|
+
"claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
21
|
+
"claude-sonnet-4-20250514": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
22
|
+
"claude-haiku-3-5-20241022": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
23
|
+
"claude-3-haiku-20240307": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
|
|
24
|
+
};
|
|
25
|
+
function getModelPrice(modelId) {
|
|
26
|
+
if (MODEL_PRICING[modelId])
|
|
27
|
+
return MODEL_PRICING[modelId];
|
|
28
|
+
for (const [key, price] of Object.entries(MODEL_PRICING)) {
|
|
29
|
+
if (modelId.startsWith(key) || key.startsWith(modelId))
|
|
30
|
+
return price;
|
|
31
|
+
}
|
|
32
|
+
return { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
33
|
+
}
|
|
34
|
+
function calcCost(usage, modelId) {
|
|
35
|
+
const price = getModelPrice(modelId);
|
|
36
|
+
return usage.inputTokens / 1e6 * price.input + usage.outputTokens / 1e6 * price.output + usage.cacheReadInputTokens / 1e6 * price.cacheRead + usage.cacheCreationInputTokens / 1e6 * price.cacheWrite;
|
|
37
|
+
}
|
|
38
|
+
// agentistics-mcp.ts
|
|
39
|
+
var API = process.env.AGENTISTICS_API ?? "http://localhost:47291";
|
|
40
|
+
function calcCostUSD(input, output, cacheRead, cacheWrite, model) {
|
|
41
|
+
return calcCost({
|
|
42
|
+
inputTokens: input,
|
|
43
|
+
outputTokens: output,
|
|
44
|
+
cacheReadInputTokens: cacheRead,
|
|
45
|
+
cacheCreationInputTokens: cacheWrite,
|
|
46
|
+
webSearchRequests: 0,
|
|
47
|
+
costUSD: 0
|
|
48
|
+
}, model);
|
|
49
|
+
}
|
|
50
|
+
var CATALOG = [
|
|
51
|
+
{ id: "kpi.messages", label: "Messages", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Total message count in selected period" },
|
|
52
|
+
{ id: "kpi.sessions", label: "Sessions", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Session count + avg msgs/session" },
|
|
53
|
+
{ id: "kpi.tool-calls", label: "Tool calls", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Total tool execution count" },
|
|
54
|
+
{ id: "kpi.cost", label: "Estimated cost", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Estimated USD cost" },
|
|
55
|
+
{ id: "kpi.streak", label: "Streak", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Consecutive days streak" },
|
|
56
|
+
{ id: "kpi.longest-session", label: "Longest session", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Duration of longest session" },
|
|
57
|
+
{ id: "kpi.commits", label: "Commits", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Git commits via Claude" },
|
|
58
|
+
{ id: "kpi.files", label: "Files modified", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Files modified + line diff" },
|
|
59
|
+
{ id: "kpi.input-tokens", label: "Input tokens", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Input tokens sent to model" },
|
|
60
|
+
{ id: "kpi.output-tokens", label: "Output tokens", category: "kpi", defaultW: 3, defaultH: 3, minW: 2, minH: 2, description: "Output tokens generated by model" },
|
|
61
|
+
{ id: "activity.chart", label: "Activity chart (full)", category: "activity", defaultW: 8, defaultH: 7, minW: 4, minH: 4, description: "Full activity chart with all metrics" },
|
|
62
|
+
{ id: "activity.chart.messages", label: "Activity — Messages only", category: "activity", defaultW: 6, defaultH: 6, minW: 3, minH: 3, description: "Daily messages bar chart" },
|
|
63
|
+
{ id: "activity.chart.sessions", label: "Activity — Sessions only", category: "activity", defaultW: 6, defaultH: 6, minW: 3, minH: 3, description: "Daily sessions bar chart" },
|
|
64
|
+
{ id: "activity.chart.tools", label: "Activity — Tool calls only", category: "activity", defaultW: 6, defaultH: 6, minW: 3, minH: 3, description: "Daily tool calls bar chart" },
|
|
65
|
+
{ id: "activity.chart.overlay", label: "Activity — Overlay", category: "activity", defaultW: 8, defaultH: 6, minW: 4, minH: 3, description: "Overlay visualization of all metrics" },
|
|
66
|
+
{ id: "activity.heatmap", label: "Activity heatmap", category: "activity", defaultW: 6, defaultH: 6, minW: 3, minH: 3, description: "Calendar heatmap of daily activity" },
|
|
67
|
+
{ id: "activity.hours", label: "Usage by hour", category: "activity", defaultW: 8, defaultH: 6, minW: 4, minH: 3, description: "Hourly usage breakdown" },
|
|
68
|
+
{ id: "costs.models", label: "Model usage", category: "costs", defaultW: 12, defaultH: 7, minW: 6, minH: 4, description: "Cost and token breakdown by model" },
|
|
69
|
+
{ id: "costs.budget", label: "Budget & forecast", category: "costs", defaultW: 6, defaultH: 7, minW: 4, minH: 4, description: "Monthly budget progress and cost forecast" },
|
|
70
|
+
{ id: "costs.cache", label: "Cache efficiency", category: "costs", defaultW: 6, defaultH: 7, minW: 4, minH: 4, description: "Cache hit rate and savings" },
|
|
71
|
+
{ id: "projects.top", label: "Top projects", category: "projects", defaultW: 7, defaultH: 7, minW: 4, minH: 4, description: "Most active projects ranked by metrics" },
|
|
72
|
+
{ id: "projects.languages", label: "Languages", category: "projects", defaultW: 5, defaultH: 6, minW: 3, minH: 3, description: "Language distribution word cloud" },
|
|
73
|
+
{ id: "tools.metrics", label: "Tool metrics (full)", category: "tools", defaultW: 12, defaultH: 8, minW: 6, minH: 4, description: "Full breakdown of tool usage by type" },
|
|
74
|
+
{ id: "tools.metrics.calls", label: "Tool metrics — calls only", category: "tools", defaultW: 8, defaultH: 7, minW: 4, minH: 4, description: "Tool call counts chart" },
|
|
75
|
+
{ id: "tools.metrics.tokens", label: "Tool metrics — tokens only", category: "tools", defaultW: 8, defaultH: 7, minW: 4, minH: 4, description: "Tokens consumed per tool type" },
|
|
76
|
+
{ id: "tools.agents", label: "Agent metrics", category: "tools", defaultW: 12, defaultH: 8, minW: 6, minH: 4, description: "Sub-agent invocation metrics, durations, costs" },
|
|
77
|
+
{ id: "sessions.highlights", label: "Highlights", category: "sessions", defaultW: 12, defaultH: 6, minW: 6, minH: 3, description: "Record-breaking sessions" },
|
|
78
|
+
{ id: "sessions.recent", label: "Recent sessions", category: "sessions", defaultW: 12, defaultH: 7, minW: 6, minH: 4, description: "List of most recent sessions" }
|
|
79
|
+
];
|
|
80
|
+
async function apiGet(path) {
|
|
81
|
+
const res = await fetch(`${API}${path}`);
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
throw new Error(`agentistics API ${path} → HTTP ${res.status}`);
|
|
84
|
+
return res.json();
|
|
85
|
+
}
|
|
86
|
+
async function getPrefs() {
|
|
87
|
+
return apiGet("/api/preferences");
|
|
88
|
+
}
|
|
89
|
+
async function putPrefs(patch) {
|
|
90
|
+
const res = await fetch(`${API}/api/preferences`, {
|
|
91
|
+
method: "PUT",
|
|
92
|
+
headers: { "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify(patch)
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok)
|
|
96
|
+
throw new Error(`PUT /api/preferences → HTTP ${res.status}`);
|
|
97
|
+
return res.json();
|
|
98
|
+
}
|
|
99
|
+
function nextId(items) {
|
|
100
|
+
let max = 0;
|
|
101
|
+
for (const item of items) {
|
|
102
|
+
const n = parseInt(item.i, 10);
|
|
103
|
+
if (!isNaN(n) && n > max)
|
|
104
|
+
max = n;
|
|
105
|
+
}
|
|
106
|
+
return String(max + 1);
|
|
107
|
+
}
|
|
108
|
+
function autoPosition(items, newW = 3, gridW = 12) {
|
|
109
|
+
if (items.length === 0)
|
|
110
|
+
return { x: 0, y: 0 };
|
|
111
|
+
const maxBottom = Math.max(...items.map((i) => i.y + i.h));
|
|
112
|
+
for (let y = 0;y <= maxBottom; y++) {
|
|
113
|
+
const blocks = items.filter((i) => i.y <= y && i.y + i.h > y).map((i) => ({ start: i.x, end: i.x + i.w })).sort((a, b) => a.start - b.start);
|
|
114
|
+
let x = 0;
|
|
115
|
+
for (const block of blocks) {
|
|
116
|
+
if (block.start >= x + newW)
|
|
117
|
+
break;
|
|
118
|
+
if (block.end > x)
|
|
119
|
+
x = block.end;
|
|
120
|
+
}
|
|
121
|
+
if (x + newW <= gridW)
|
|
122
|
+
return { x, y };
|
|
123
|
+
}
|
|
124
|
+
return { x: 0, y: maxBottom };
|
|
125
|
+
}
|
|
126
|
+
function fillGaps(items, gridW = 12) {
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
const rightEdge = item.x + item.w;
|
|
129
|
+
if (rightEdge >= gridW)
|
|
130
|
+
continue;
|
|
131
|
+
const hasRightNeighbour = items.some((other) => other !== item && other.x >= rightEdge && other.y < item.y + item.h && other.y + other.h > item.y);
|
|
132
|
+
if (!hasRightNeighbour) {
|
|
133
|
+
item.w = gridW - item.x;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
var server = new Server({ name: "agentistics", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
138
|
+
var TOOLS = [
|
|
139
|
+
{
|
|
140
|
+
name: "agentistics_summary",
|
|
141
|
+
description: "Get an overview of Claude Code usage metrics: total tokens, estimated cost, sessions, streak, most used model, and top project. Good starting point for any metrics question.",
|
|
142
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "agentistics_projects",
|
|
146
|
+
description: "List all Claude Code projects with session counts, message counts, token usage, estimated cost, and last active date.",
|
|
147
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "agentistics_sessions",
|
|
151
|
+
description: "Get the most recent Claude Code sessions with project path, duration, message count, token usage, and model.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
limit: {
|
|
156
|
+
type: "number",
|
|
157
|
+
description: "Max sessions to return (default 20, max 50)"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "agentistics_costs",
|
|
164
|
+
description: "Get cost breakdown by model: token counts (input/output/cache read/write) and estimated USD cost per model.",
|
|
165
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "agentistics_component_catalog",
|
|
169
|
+
description: "List all components available for the custom layout page, with IDs, categories, descriptions, and default grid sizes. Call this before building or modifying a layout.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
category: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Filter by category: kpi, activity, costs, projects, tools, sessions"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "agentistics_get_layouts",
|
|
182
|
+
description: "Get all custom layouts: names, components in each, which is active, and each component's instance ID for removal.",
|
|
183
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "agentistics_create_layout",
|
|
187
|
+
description: "Create a new empty named layout on the custom page.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
name: { type: "string", description: "Name for the new layout" }
|
|
192
|
+
},
|
|
193
|
+
required: ["name"]
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "agentistics_add_component",
|
|
198
|
+
description: "Add a single component to a layout. Use agentistics_component_catalog to get valid componentId values. Positioned automatically at the bottom of the grid unless x/y are given.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
layoutName: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "Target layout name. Omit to use the currently active layout."
|
|
205
|
+
},
|
|
206
|
+
componentId: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Component ID from the catalog (e.g. 'kpi.cost', 'activity.chart')"
|
|
209
|
+
},
|
|
210
|
+
x: {
|
|
211
|
+
type: "number",
|
|
212
|
+
description: "Column (0–11). Optional — auto-placed if omitted."
|
|
213
|
+
},
|
|
214
|
+
y: {
|
|
215
|
+
type: "number",
|
|
216
|
+
description: "Row. Optional — auto-placed if omitted."
|
|
217
|
+
},
|
|
218
|
+
w: {
|
|
219
|
+
type: "number",
|
|
220
|
+
description: "Width in grid units (1–12). Optional — uses default."
|
|
221
|
+
},
|
|
222
|
+
h: {
|
|
223
|
+
type: "number",
|
|
224
|
+
description: "Height in grid units. Optional — uses default."
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
required: ["componentId"]
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "agentistics_remove_component",
|
|
232
|
+
description: "Remove a component from a layout by its instance ID (the 'instanceId' field from agentistics_get_layouts).",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
layoutName: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Layout name. Omit to use the active layout."
|
|
239
|
+
},
|
|
240
|
+
itemId: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Instance ID of the item to remove"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
required: ["itemId"]
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "agentistics_set_active_layout",
|
|
250
|
+
description: "Switch the active layout shown on the /custom page.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
name: { type: "string", description: "Name of the layout to activate" }
|
|
255
|
+
},
|
|
256
|
+
required: ["name"]
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "agentistics_delete_layout",
|
|
261
|
+
description: "Delete a named layout. Cannot delete the last remaining layout.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
name: { type: "string", description: "Name of the layout to delete" }
|
|
266
|
+
},
|
|
267
|
+
required: ["name"]
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "agentistics_export_pdf",
|
|
272
|
+
description: "Generate a PDF report of Claude Code usage. Returns a URL that, when opened in a browser, automatically opens the PDF export modal pre-configured with the requested options. The user can review settings and click Download to save the PDF.",
|
|
273
|
+
inputSchema: {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties: {
|
|
276
|
+
range: {
|
|
277
|
+
type: "string",
|
|
278
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
279
|
+
description: "Date range for the report (default: all)"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "agentistics_build_layout",
|
|
286
|
+
description: "Build a complete layout in one call: creates it, adds all requested components with auto-positioning, and optionally activates it. Ideal for setting up a themed dashboard from scratch.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
name: { type: "string", description: "Name for the layout" },
|
|
291
|
+
componentIds: {
|
|
292
|
+
type: "array",
|
|
293
|
+
items: { type: "string" },
|
|
294
|
+
description: "Ordered array of component IDs to add"
|
|
295
|
+
},
|
|
296
|
+
activate: {
|
|
297
|
+
type: "boolean",
|
|
298
|
+
description: "Set as active layout after building (default true)"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
required: ["name", "componentIds"]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
];
|
|
305
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
306
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
307
|
+
const { name, arguments: args } = req.params;
|
|
308
|
+
try {
|
|
309
|
+
switch (name) {
|
|
310
|
+
case "agentistics_summary": {
|
|
311
|
+
const data = await apiGet("/api/data");
|
|
312
|
+
const sc = data.statsCache ?? {};
|
|
313
|
+
const totals = sc.allTimeTotals ?? {};
|
|
314
|
+
const models = sc.modelUsage ?? {};
|
|
315
|
+
const topModel = Object.entries(models).sort(([, a], [, b]) => (b.totalTokens ?? 0) - (a.totalTokens ?? 0))[0]?.[0] ?? "—";
|
|
316
|
+
const projects = data.projects ?? [];
|
|
317
|
+
const topProject = [...projects].sort((a, b) => (b.sessions?.length ?? 0) - (a.sessions?.length ?? 0))[0]?.name ?? "—";
|
|
318
|
+
const allSessions = data.sessions ?? [];
|
|
319
|
+
let totalCostUSD = 0;
|
|
320
|
+
let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0;
|
|
321
|
+
for (const s of allSessions) {
|
|
322
|
+
const inp = s.input_tokens ?? 0;
|
|
323
|
+
const out = s.output_tokens ?? 0;
|
|
324
|
+
const cr = s.cache_read_input_tokens ?? 0;
|
|
325
|
+
const cw = s.cache_creation_input_tokens ?? 0;
|
|
326
|
+
totalInput += inp;
|
|
327
|
+
totalOutput += out;
|
|
328
|
+
totalCacheRead += cr;
|
|
329
|
+
totalCacheWrite += cw;
|
|
330
|
+
totalCostUSD += calcCostUSD(inp, out, cr, cw, s.model ?? "");
|
|
331
|
+
}
|
|
332
|
+
const finalInput = totalInput || (totals.inputTokens ?? 0);
|
|
333
|
+
const finalOutput = totalOutput || (totals.outputTokens ?? 0);
|
|
334
|
+
const finalCacheRead = totalCacheRead || (totals.cacheReadTokens ?? 0);
|
|
335
|
+
const finalCacheWrite = totalCacheWrite || (totals.cacheWriteTokens ?? 0);
|
|
336
|
+
return {
|
|
337
|
+
content: [{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: JSON.stringify({
|
|
340
|
+
totalInputTokens: finalInput,
|
|
341
|
+
totalOutputTokens: finalOutput,
|
|
342
|
+
totalCacheReadTokens: finalCacheRead,
|
|
343
|
+
totalCacheWriteTokens: finalCacheWrite,
|
|
344
|
+
estimatedCostUSD: Math.round(totalCostUSD * 100) / 100,
|
|
345
|
+
totalSessions: allSessions.length,
|
|
346
|
+
totalProjects: projects.length,
|
|
347
|
+
topModel,
|
|
348
|
+
topProject,
|
|
349
|
+
activeDays: sc.activeDays ?? 0,
|
|
350
|
+
currentStreak: sc.currentStreak ?? 0
|
|
351
|
+
}, null, 2)
|
|
352
|
+
}]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
case "agentistics_projects": {
|
|
356
|
+
const data = await apiGet("/api/data");
|
|
357
|
+
const allSessions = data.sessions ?? [];
|
|
358
|
+
const byPath = {};
|
|
359
|
+
for (const s of allSessions) {
|
|
360
|
+
const key = s.project_path;
|
|
361
|
+
if (!key)
|
|
362
|
+
continue;
|
|
363
|
+
if (!byPath[key])
|
|
364
|
+
byPath[key] = { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
365
|
+
const agg = byPath[key];
|
|
366
|
+
const inp = s.input_tokens ?? 0;
|
|
367
|
+
const out = s.output_tokens ?? 0;
|
|
368
|
+
const cr = s.cache_read_input_tokens ?? 0;
|
|
369
|
+
const cw = s.cache_creation_input_tokens ?? 0;
|
|
370
|
+
agg.inputTokens += inp;
|
|
371
|
+
agg.outputTokens += out;
|
|
372
|
+
agg.cacheRead += cr;
|
|
373
|
+
agg.cacheWrite += cw;
|
|
374
|
+
agg.costUSD += calcCostUSD(inp, out, cr, cw, s.model ?? "");
|
|
375
|
+
agg.messages += (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0);
|
|
376
|
+
if (!agg.lastActive || (s.start_time ?? "") > agg.lastActive)
|
|
377
|
+
agg.lastActive = s.start_time ?? "";
|
|
378
|
+
for (const lang of s.languages ?? [])
|
|
379
|
+
if (!agg.languages.includes(lang))
|
|
380
|
+
agg.languages.push(lang);
|
|
381
|
+
}
|
|
382
|
+
const projects = data.projects ?? [];
|
|
383
|
+
const summary = projects.map((p) => {
|
|
384
|
+
const agg = byPath[p.path] ?? { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
|
|
385
|
+
return {
|
|
386
|
+
name: p.name,
|
|
387
|
+
path: p.path,
|
|
388
|
+
sessions: (p.sessions ?? []).length,
|
|
389
|
+
messages: agg.messages,
|
|
390
|
+
inputTokens: agg.inputTokens,
|
|
391
|
+
outputTokens: agg.outputTokens,
|
|
392
|
+
totalTokens: agg.inputTokens + agg.outputTokens,
|
|
393
|
+
estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
|
|
394
|
+
lastActive: agg.lastActive || null,
|
|
395
|
+
languages: agg.languages
|
|
396
|
+
};
|
|
397
|
+
}).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
398
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
399
|
+
}
|
|
400
|
+
case "agentistics_sessions": {
|
|
401
|
+
const limit = Math.min(args?.limit ?? 20, 50);
|
|
402
|
+
const data = await apiGet("/api/data");
|
|
403
|
+
const rows = (data.sessions ?? []).slice(0, limit).map((s) => {
|
|
404
|
+
const inp = s.input_tokens ?? 0;
|
|
405
|
+
const out = s.output_tokens ?? 0;
|
|
406
|
+
const cr = s.cache_read_input_tokens ?? 0;
|
|
407
|
+
const cw = s.cache_creation_input_tokens ?? 0;
|
|
408
|
+
return {
|
|
409
|
+
id: s.session_id,
|
|
410
|
+
project: s.project_path,
|
|
411
|
+
startedAt: s.start_time,
|
|
412
|
+
durationMinutes: s.duration_minutes,
|
|
413
|
+
messages: (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0),
|
|
414
|
+
inputTokens: inp,
|
|
415
|
+
outputTokens: out,
|
|
416
|
+
cacheReadTokens: cr,
|
|
417
|
+
cacheWriteTokens: cw,
|
|
418
|
+
totalTokens: inp + out + cr + cw,
|
|
419
|
+
estimatedCostUSD: Math.round(calcCostUSD(inp, out, cr, cw, s.model ?? "") * 1e4) / 1e4,
|
|
420
|
+
model: s.model ?? null
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
424
|
+
}
|
|
425
|
+
case "agentistics_costs": {
|
|
426
|
+
const data = await apiGet("/api/data");
|
|
427
|
+
const usage = data.statsCache?.modelUsage ?? {};
|
|
428
|
+
const breakdown = Object.entries(usage).map(([model, u]) => ({
|
|
429
|
+
model,
|
|
430
|
+
inputTokens: u.inputTokens ?? 0,
|
|
431
|
+
outputTokens: u.outputTokens ?? 0,
|
|
432
|
+
cacheReadTokens: u.cacheReadTokens ?? 0,
|
|
433
|
+
cacheWriteTokens: u.cacheWriteTokens ?? 0,
|
|
434
|
+
totalTokens: u.totalTokens ?? 0,
|
|
435
|
+
estimatedCostUSD: u.costUSD ?? 0
|
|
436
|
+
})).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
437
|
+
return { content: [{ type: "text", text: JSON.stringify(breakdown, null, 2) }] };
|
|
438
|
+
}
|
|
439
|
+
case "agentistics_component_catalog": {
|
|
440
|
+
const category = args?.category;
|
|
441
|
+
const items = category ? CATALOG.filter((c) => c.category === category) : CATALOG;
|
|
442
|
+
return { content: [{ type: "text", text: JSON.stringify(items, null, 2) }] };
|
|
443
|
+
}
|
|
444
|
+
case "agentistics_get_layouts": {
|
|
445
|
+
const prefs = await getPrefs();
|
|
446
|
+
const layouts = prefs.layouts ?? {};
|
|
447
|
+
const active = prefs.activeLayout ?? "";
|
|
448
|
+
const summary = Object.entries(layouts).map(([layoutName, items]) => ({
|
|
449
|
+
name: layoutName,
|
|
450
|
+
isActive: layoutName === active,
|
|
451
|
+
componentCount: items.length,
|
|
452
|
+
components: items.map((item) => ({
|
|
453
|
+
instanceId: item.i,
|
|
454
|
+
componentId: item.componentId,
|
|
455
|
+
x: item.x,
|
|
456
|
+
y: item.y,
|
|
457
|
+
w: item.w,
|
|
458
|
+
h: item.h
|
|
459
|
+
}))
|
|
460
|
+
}));
|
|
461
|
+
return {
|
|
462
|
+
content: [{
|
|
463
|
+
type: "text",
|
|
464
|
+
text: JSON.stringify({ activeLayout: active, layouts: summary }, null, 2)
|
|
465
|
+
}]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
case "agentistics_create_layout": {
|
|
469
|
+
const layoutName = args.name;
|
|
470
|
+
const prefs = await getPrefs();
|
|
471
|
+
const layouts = { ...prefs.layouts ?? {} };
|
|
472
|
+
if (layouts[layoutName]) {
|
|
473
|
+
return { content: [{ type: "text", text: `Layout "${layoutName}" already exists.` }] };
|
|
474
|
+
}
|
|
475
|
+
layouts[layoutName] = [];
|
|
476
|
+
await putPrefs({ layouts, activeLayout: prefs.activeLayout });
|
|
477
|
+
return { content: [{ type: "text", text: `Layout "${layoutName}" created.` }] };
|
|
478
|
+
}
|
|
479
|
+
case "agentistics_add_component": {
|
|
480
|
+
const a = args;
|
|
481
|
+
const prefs = await getPrefs();
|
|
482
|
+
const layouts = { ...prefs.layouts ?? {} };
|
|
483
|
+
const targetName = a.layoutName ?? prefs.activeLayout ?? "";
|
|
484
|
+
if (!targetName)
|
|
485
|
+
throw new Error("No active layout. Create one first with agentistics_create_layout.");
|
|
486
|
+
if (!layouts[targetName])
|
|
487
|
+
throw new Error(`Layout "${targetName}" not found.`);
|
|
488
|
+
const catalogItem = CATALOG.find((c) => c.id === a.componentId);
|
|
489
|
+
if (!catalogItem)
|
|
490
|
+
throw new Error(`Unknown componentId "${a.componentId}". Use agentistics_component_catalog to list valid IDs.`);
|
|
491
|
+
const current = layouts[targetName];
|
|
492
|
+
const pos = a.x !== undefined && a.y !== undefined ? { x: a.x, y: a.y } : autoPosition(current, a.w ?? catalogItem.defaultW);
|
|
493
|
+
const newItem = {
|
|
494
|
+
i: nextId(current),
|
|
495
|
+
componentId: a.componentId,
|
|
496
|
+
x: pos.x,
|
|
497
|
+
y: pos.y,
|
|
498
|
+
w: a.w ?? catalogItem.defaultW,
|
|
499
|
+
h: a.h ?? catalogItem.defaultH,
|
|
500
|
+
minW: catalogItem.minW,
|
|
501
|
+
minH: catalogItem.minH
|
|
502
|
+
};
|
|
503
|
+
layouts[targetName] = [...current, newItem];
|
|
504
|
+
await putPrefs({ layouts });
|
|
505
|
+
return {
|
|
506
|
+
content: [{
|
|
507
|
+
type: "text",
|
|
508
|
+
text: `Added "${a.componentId}" to layout "${targetName}" at (${newItem.x}, ${newItem.y}), size ${newItem.w}×${newItem.h}. Instance ID: ${newItem.i}`
|
|
509
|
+
}]
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
case "agentistics_remove_component": {
|
|
513
|
+
const a = args;
|
|
514
|
+
const prefs = await getPrefs();
|
|
515
|
+
const layouts = { ...prefs.layouts ?? {} };
|
|
516
|
+
const targetName = a.layoutName ?? prefs.activeLayout ?? "";
|
|
517
|
+
if (!targetName || !layouts[targetName])
|
|
518
|
+
throw new Error(`Layout "${targetName}" not found.`);
|
|
519
|
+
const before = layouts[targetName].length;
|
|
520
|
+
layouts[targetName] = layouts[targetName].filter((item) => item.i !== a.itemId);
|
|
521
|
+
if (layouts[targetName].length === before) {
|
|
522
|
+
return { content: [{ type: "text", text: `Item "${a.itemId}" not found in layout "${targetName}".` }] };
|
|
523
|
+
}
|
|
524
|
+
await putPrefs({ layouts });
|
|
525
|
+
return { content: [{ type: "text", text: `Removed item "${a.itemId}" from layout "${targetName}".` }] };
|
|
526
|
+
}
|
|
527
|
+
case "agentistics_set_active_layout": {
|
|
528
|
+
const layoutName = args.name;
|
|
529
|
+
const prefs = await getPrefs();
|
|
530
|
+
const layouts = prefs.layouts ?? {};
|
|
531
|
+
if (!layouts[layoutName]) {
|
|
532
|
+
throw new Error(`Layout "${layoutName}" not found. Available: ${Object.keys(layouts).join(", ")}`);
|
|
533
|
+
}
|
|
534
|
+
await putPrefs({ activeLayout: layoutName });
|
|
535
|
+
return { content: [{ type: "text", text: `Active layout set to "${layoutName}".` }] };
|
|
536
|
+
}
|
|
537
|
+
case "agentistics_delete_layout": {
|
|
538
|
+
const layoutName = args.name;
|
|
539
|
+
const prefs = await getPrefs();
|
|
540
|
+
const layouts = { ...prefs.layouts ?? {} };
|
|
541
|
+
if (!layouts[layoutName])
|
|
542
|
+
throw new Error(`Layout "${layoutName}" not found.`);
|
|
543
|
+
if (Object.keys(layouts).length === 1)
|
|
544
|
+
throw new Error("Cannot delete the last remaining layout.");
|
|
545
|
+
delete layouts[layoutName];
|
|
546
|
+
let active = prefs.activeLayout ?? "";
|
|
547
|
+
if (active === layoutName)
|
|
548
|
+
active = Object.keys(layouts)[0] ?? "";
|
|
549
|
+
await putPrefs({ layouts, activeLayout: active });
|
|
550
|
+
return {
|
|
551
|
+
content: [{
|
|
552
|
+
type: "text",
|
|
553
|
+
text: `Deleted layout "${layoutName}". Active layout is now "${active}".`
|
|
554
|
+
}]
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
case "agentistics_export_pdf": {
|
|
558
|
+
const a = args;
|
|
559
|
+
const range = a?.range ?? "all";
|
|
560
|
+
const uiBase = API.replace(/:\d+$/, ":47292");
|
|
561
|
+
const params = new URLSearchParams({ export: "pdf" });
|
|
562
|
+
if (range !== "all")
|
|
563
|
+
params.set("range", range);
|
|
564
|
+
const url = `${uiBase}/?${params.toString()}`;
|
|
565
|
+
const rangeLabel = range === "all" ? "all-time" : `last ${range}`;
|
|
566
|
+
return {
|
|
567
|
+
content: [{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: `[⬇ Download PDF — ${rangeLabel}](pdf:${url})`
|
|
570
|
+
}]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
case "agentistics_build_layout": {
|
|
574
|
+
const a = args;
|
|
575
|
+
const layoutName = a.name;
|
|
576
|
+
const componentIds = a.componentIds;
|
|
577
|
+
const activate = a.activate !== false;
|
|
578
|
+
for (const id of componentIds) {
|
|
579
|
+
if (!CATALOG.find((c) => c.id === id)) {
|
|
580
|
+
throw new Error(`Unknown componentId "${id}". Use agentistics_component_catalog to list valid IDs.`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const prefs = await getPrefs();
|
|
584
|
+
const layouts = { ...prefs.layouts ?? {} };
|
|
585
|
+
const items = [];
|
|
586
|
+
for (const componentId of componentIds) {
|
|
587
|
+
const cat = CATALOG.find((c) => c.id === componentId);
|
|
588
|
+
const pos = autoPosition(items, cat.defaultW);
|
|
589
|
+
items.push({
|
|
590
|
+
i: nextId(items),
|
|
591
|
+
componentId,
|
|
592
|
+
x: pos.x,
|
|
593
|
+
y: pos.y,
|
|
594
|
+
w: cat.defaultW,
|
|
595
|
+
h: cat.defaultH,
|
|
596
|
+
minW: cat.minW,
|
|
597
|
+
minH: cat.minH
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
fillGaps(items);
|
|
601
|
+
layouts[layoutName] = items;
|
|
602
|
+
const patch = { layouts };
|
|
603
|
+
if (activate)
|
|
604
|
+
patch.activeLayout = layoutName;
|
|
605
|
+
await putPrefs(patch);
|
|
606
|
+
return {
|
|
607
|
+
content: [{
|
|
608
|
+
type: "text",
|
|
609
|
+
text: `Layout "${layoutName}" built with ${items.length} components: ${componentIds.join(", ")}${activate ? ". Now active." : "."}`
|
|
610
|
+
}]
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
default:
|
|
614
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
615
|
+
}
|
|
616
|
+
} catch (err) {
|
|
617
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
618
|
+
return {
|
|
619
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
620
|
+
isError: true
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
var transport = new StdioServerTransport;
|
|
625
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentistics/mcp",
|
|
3
|
+
"version": "1.3.3",
|
|
4
|
+
"description": "Agentistics MCP server — Claude Code analytics for Claude Desktop and Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/agentistics-mcp.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"agentistics-mcp": "dist/agentistics-mcp.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun build agentistics-mcp.ts --outfile dist/agentistics-mcp.js --external @modelcontextprotocol/sdk --target node && chmod +x dist/agentistics-mcp.js",
|
|
16
|
+
"prepublishOnly": "bun run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18",
|
|
23
|
+
"bun": ">=1.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"claude",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"analytics",
|
|
30
|
+
"agentistics"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/blpsoares/agentistics.git",
|
|
36
|
+
"directory": "packages/mcp"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/blpsoares/agentistics"
|
|
39
|
+
}
|