@neurynae/toolcairn-mcp 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +558 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,7 +62,484 @@ var require_dist = __commonJS({
|
|
|
62
62
|
|
|
63
63
|
// src/index.prod.ts
|
|
64
64
|
init_esm_shims();
|
|
65
|
-
import
|
|
65
|
+
import pino9 from "pino";
|
|
66
|
+
|
|
67
|
+
// src/project-setup.ts
|
|
68
|
+
init_esm_shims();
|
|
69
|
+
import { access, mkdir, writeFile } from "fs/promises";
|
|
70
|
+
import { platform, type } from "os";
|
|
71
|
+
import { join } from "path";
|
|
72
|
+
import pino from "pino";
|
|
73
|
+
|
|
74
|
+
// src/tools/generate-tracker.ts
|
|
75
|
+
init_esm_shims();
|
|
76
|
+
function generateTrackerHtml(eventsPath) {
|
|
77
|
+
return `<!DOCTYPE html>
|
|
78
|
+
<html lang="en">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="UTF-8" />
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
82
|
+
<title>ToolCairn Tracker</title>
|
|
83
|
+
<style>
|
|
84
|
+
:root {
|
|
85
|
+
--bg: #0a0a0f;
|
|
86
|
+
--surface: #12121a;
|
|
87
|
+
--surface2: #1a1a26;
|
|
88
|
+
--border: #2a2a3a;
|
|
89
|
+
--accent: #7c5cfc;
|
|
90
|
+
--accent2: #5b8def;
|
|
91
|
+
--green: #22c55e;
|
|
92
|
+
--red: #ef4444;
|
|
93
|
+
--yellow: #f59e0b;
|
|
94
|
+
--text: #e2e8f0;
|
|
95
|
+
--muted: #64748b;
|
|
96
|
+
--mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
97
|
+
}
|
|
98
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
99
|
+
body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; font-size: 14px; min-height: 100vh; }
|
|
100
|
+
|
|
101
|
+
header { display: flex; align-items: center; gap: 12px; padding: 16px 24px; border-bottom: 1px solid var(--border); background: var(--surface); }
|
|
102
|
+
header h1 { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; }
|
|
103
|
+
header h1 span { color: var(--accent); }
|
|
104
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; margin-left: auto; }
|
|
105
|
+
.status-dot.paused { background: var(--yellow); animation: none; }
|
|
106
|
+
@keyframes pulse { 0%,100%{ opacity:1; } 50%{ opacity:0.4; } }
|
|
107
|
+
|
|
108
|
+
.controls { display: flex; gap: 8px; align-items: center; padding: 12px 24px; border-bottom: 1px solid var(--border); background: var(--surface); }
|
|
109
|
+
.btn { padding: 5px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface2); color: var(--text); cursor: pointer; font-size: 12px; transition: border-color .15s; }
|
|
110
|
+
.btn:hover { border-color: var(--accent); }
|
|
111
|
+
.btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
112
|
+
input[type=range] { accent-color: var(--accent); }
|
|
113
|
+
.label { color: var(--muted); font-size: 12px; }
|
|
114
|
+
|
|
115
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
|
|
116
|
+
.metric { background: var(--surface); padding: 14px 18px; }
|
|
117
|
+
.metric-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
|
|
118
|
+
.metric-value { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
119
|
+
.metric-value.green { color: var(--green); }
|
|
120
|
+
.metric-value.red { color: var(--red); }
|
|
121
|
+
.metric-value.accent { color: var(--accent); }
|
|
122
|
+
.metric-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
|
|
123
|
+
|
|
124
|
+
.layout { display: grid; grid-template-columns: 1fr 340px; height: calc(100vh - 140px); }
|
|
125
|
+
.feed { overflow-y: auto; border-right: 1px solid var(--border); }
|
|
126
|
+
.sidebar { overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
127
|
+
|
|
128
|
+
.event-row { display: grid; grid-template-columns: 80px 160px 1fr auto auto; gap: 12px; align-items: center; padding: 8px 16px; border-bottom: 1px solid #1a1a22; transition: background .1s; cursor: pointer; }
|
|
129
|
+
.event-row:hover { background: var(--surface2); }
|
|
130
|
+
.event-row.selected { background: #1e1a30; }
|
|
131
|
+
.event-row .time { font-family: var(--mono); font-size: 11px; color: var(--muted); }
|
|
132
|
+
.event-row .tool { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 600; }
|
|
133
|
+
.event-row .summary { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
134
|
+
.event-row .dur { font-family: var(--mono); font-size: 11px; color: var(--muted); text-align: right; }
|
|
135
|
+
.badge { display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 4px; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
136
|
+
.badge.ok { background: rgba(34,197,94,.15); color: var(--green); }
|
|
137
|
+
.badge.error { background: rgba(239,68,68,.15); color: var(--red); }
|
|
138
|
+
.badge.warn { background: rgba(245,158,11,.15); color: var(--yellow); }
|
|
139
|
+
|
|
140
|
+
.detail-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; }
|
|
141
|
+
.detail-card h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 10px; }
|
|
142
|
+
.kv { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid #1a1a22; font-size: 12px; }
|
|
143
|
+
.kv:last-child { border-bottom: none; }
|
|
144
|
+
.kv .k { color: var(--muted); }
|
|
145
|
+
.kv .v { font-family: var(--mono); color: var(--text); }
|
|
146
|
+
.kv .v.green { color: var(--green); }
|
|
147
|
+
.kv .v.red { color: var(--red); }
|
|
148
|
+
.kv .v.yellow { color: var(--yellow); }
|
|
149
|
+
|
|
150
|
+
.bar-chart { margin-top: 6px; }
|
|
151
|
+
.bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: 11px; }
|
|
152
|
+
.bar-label { width: 120px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
|
|
153
|
+
.bar-track { flex: 1; height: 6px; background: var(--surface2); border-radius: 3px; }
|
|
154
|
+
.bar-fill { height: 100%; border-radius: 3px; background: var(--accent); transition: width .3s; }
|
|
155
|
+
.bar-count { width: 28px; text-align: right; color: var(--text); }
|
|
156
|
+
|
|
157
|
+
.empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--muted); gap: 8px; }
|
|
158
|
+
.empty svg { opacity: .3; }
|
|
159
|
+
.empty p { font-size: 13px; }
|
|
160
|
+
.empty code { font-family: var(--mono); font-size: 11px; background: var(--surface2); padding: 3px 8px; border-radius: 4px; color: var(--accent); }
|
|
161
|
+
|
|
162
|
+
.insights-list { list-style: none; display: flex; flex-direction: column; gap: 6px; }
|
|
163
|
+
.insight-item { background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; font-size: 12px; }
|
|
164
|
+
.insight-item .i-tool { color: var(--accent); font-family: var(--mono); font-weight: 600; }
|
|
165
|
+
.insight-item .i-text { color: var(--muted); margin-top: 2px; }
|
|
166
|
+
|
|
167
|
+
::-webkit-scrollbar { width: 4px; }
|
|
168
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
169
|
+
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
170
|
+
</style>
|
|
171
|
+
</head>
|
|
172
|
+
<body>
|
|
173
|
+
|
|
174
|
+
<header>
|
|
175
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
176
|
+
<circle cx="10" cy="10" r="9" stroke="#7c5cfc" stroke-width="1.5"/>
|
|
177
|
+
<path d="M6 10h8M10 6v8" stroke="#7c5cfc" stroke-width="1.5" stroke-linecap="round"/>
|
|
178
|
+
</svg>
|
|
179
|
+
<h1><span>Tool</span>Pilot Tracker</h1>
|
|
180
|
+
<div id="statusText" style="font-size:12px; color:var(--muted);">Loading...</div>
|
|
181
|
+
<div id="statusDot" class="status-dot paused"></div>
|
|
182
|
+
</header>
|
|
183
|
+
|
|
184
|
+
<div class="controls">
|
|
185
|
+
<button class="btn active" id="btnLive" onclick="toggleLive()">\u2B24 Live</button>
|
|
186
|
+
<button class="btn" id="btnClear" onclick="clearEvents()">Clear</button>
|
|
187
|
+
<span class="label" style="margin-left:8px;">Interval:</span>
|
|
188
|
+
<input type="range" min="1" max="30" value="3" id="intervalSlider" onchange="setInterval_(this.value)" style="width:80px;" />
|
|
189
|
+
<span class="label" id="intervalLabel">3s</span>
|
|
190
|
+
<span style="margin-left:auto; font-size:11px; color:var(--muted);" id="lastRefresh">\u2014</span>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="metrics" id="metrics">
|
|
194
|
+
<div class="metric"><div class="metric-label">Total Calls</div><div class="metric-value accent" id="mTotal">0</div></div>
|
|
195
|
+
<div class="metric"><div class="metric-label">Success Rate</div><div class="metric-value green" id="mSuccess">\u2014</div></div>
|
|
196
|
+
<div class="metric"><div class="metric-label">Avg Latency</div><div class="metric-value" id="mLatency">\u2014</div></div>
|
|
197
|
+
<div class="metric"><div class="metric-label">Issues Caught</div><div class="metric-value yellow" id="mIssues">0</div><div class="metric-sub">check_issue calls</div></div>
|
|
198
|
+
<div class="metric"><div class="metric-label">Deprecation Warns</div><div class="metric-value yellow" id="mDeprecation">0</div></div>
|
|
199
|
+
<div class="metric"><div class="metric-label">Non-OSS Guided</div><div class="metric-value" id="mNonOss">0</div></div>
|
|
200
|
+
<div class="metric"><div class="metric-label">Graph Updates</div><div class="metric-value accent" id="mGraph">0</div></div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div class="layout">
|
|
204
|
+
<div class="feed" id="feed">
|
|
205
|
+
<div class="empty" id="emptyState">
|
|
206
|
+
<svg width="40" height="40" viewBox="0 0 40 40"><circle cx="20" cy="20" r="18" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M13 20h14M20 13v14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
207
|
+
<p>Waiting for MCP tool calls...</p>
|
|
208
|
+
<code>Set TOOLCAIRN_EVENTS_PATH in your MCP server env</code>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="sidebar">
|
|
212
|
+
<div class="detail-card" id="detailPanel" style="display:none">
|
|
213
|
+
<h3>Event Detail</h3>
|
|
214
|
+
<div id="detailContent"></div>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="detail-card">
|
|
217
|
+
<h3>Calls by Tool</h3>
|
|
218
|
+
<div id="toolChart" class="bar-chart"></div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="detail-card">
|
|
221
|
+
<h3>Recent Insights</h3>
|
|
222
|
+
<ul class="insights-list" id="insightsList"></ul>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<script>
|
|
228
|
+
// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
229
|
+
const EVENTS_PATH = ${JSON.stringify(eventsPath)};
|
|
230
|
+
|
|
231
|
+
// \u2500\u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
232
|
+
let allEvents = [];
|
|
233
|
+
let selectedId = null;
|
|
234
|
+
let isLive = true;
|
|
235
|
+
let pollIntervalMs = 3000;
|
|
236
|
+
let pollHandle = null;
|
|
237
|
+
let lastByteOffset = 0;
|
|
238
|
+
|
|
239
|
+
// \u2500\u2500\u2500 Polling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
240
|
+
async function fetchEvents() {
|
|
241
|
+
if (!EVENTS_PATH) return;
|
|
242
|
+
try {
|
|
243
|
+
// Fetch with range header to only get new bytes
|
|
244
|
+
const headers = lastByteOffset > 0 ? { 'Range': \`bytes=\${lastByteOffset}-\` } : {};
|
|
245
|
+
const res = await fetch(\`file://\${EVENTS_PATH}\`, { headers }).catch(() => null);
|
|
246
|
+
if (!res) return;
|
|
247
|
+
|
|
248
|
+
const text = await res.text();
|
|
249
|
+
if (!text.trim()) return;
|
|
250
|
+
|
|
251
|
+
const newLines = text.trim().split('\\n').filter(Boolean);
|
|
252
|
+
let added = 0;
|
|
253
|
+
for (const line of newLines) {
|
|
254
|
+
try {
|
|
255
|
+
const ev = JSON.parse(line);
|
|
256
|
+
if (!allEvents.find(e => e.id === ev.id)) {
|
|
257
|
+
allEvents.push(ev);
|
|
258
|
+
added++;
|
|
259
|
+
}
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (added > 0) {
|
|
264
|
+
allEvents.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
265
|
+
renderAll();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
document.getElementById('lastRefresh').textContent = 'Updated ' + new Date().toLocaleTimeString();
|
|
269
|
+
document.getElementById('statusDot').className = 'status-dot' + (isLive ? '' : ' paused');
|
|
270
|
+
document.getElementById('statusText').textContent = \`\${allEvents.length} events\`;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
console.warn('Fetch error', e);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function toggleLive() {
|
|
277
|
+
isLive = !isLive;
|
|
278
|
+
document.getElementById('btnLive').className = 'btn' + (isLive ? ' active' : '');
|
|
279
|
+
document.getElementById('statusDot').className = 'status-dot' + (isLive ? '' : ' paused');
|
|
280
|
+
if (isLive) startPolling(); else stopPolling();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function clearEvents() {
|
|
284
|
+
allEvents = [];
|
|
285
|
+
selectedId = null;
|
|
286
|
+
renderAll();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function setInterval_(v) {
|
|
290
|
+
pollIntervalMs = Number(v) * 1000;
|
|
291
|
+
document.getElementById('intervalLabel').textContent = v + 's';
|
|
292
|
+
if (isLive) { stopPolling(); startPolling(); }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function startPolling() {
|
|
296
|
+
if (pollHandle) clearInterval(pollHandle);
|
|
297
|
+
fetchEvents();
|
|
298
|
+
pollHandle = setInterval(fetchEvents, pollIntervalMs);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function stopPolling() {
|
|
302
|
+
if (pollHandle) { clearInterval(pollHandle); pollHandle = null; }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// \u2500\u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
306
|
+
function fmtTime(iso) {
|
|
307
|
+
return new Date(iso).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function toolSummary(ev) {
|
|
311
|
+
const m = ev.metadata || {};
|
|
312
|
+
if (ev.tool_name === 'search_tools' || ev.tool_name === 'search_tools_respond') {
|
|
313
|
+
const parts = [];
|
|
314
|
+
if (m.is_two_option) parts.push('2-option result');
|
|
315
|
+
if (m.had_non_indexed_guidance) parts.push('non-OSS guidance');
|
|
316
|
+
if (m.had_deprecation_warning) parts.push('\u26A0 deprecated tool');
|
|
317
|
+
if (m.had_credibility_warning) parts.push('\u26A0 low-stars warning');
|
|
318
|
+
return parts.join(' \xB7 ') || m.status || '';
|
|
319
|
+
}
|
|
320
|
+
if (ev.tool_name === 'check_issue') return m.status ? \`status: \${m.status}\` : '';
|
|
321
|
+
if (ev.tool_name === 'suggest_graph_update') {
|
|
322
|
+
if (m.auto_graduated) return '\u2713 auto-graduated to graph';
|
|
323
|
+
if (m.staged) return 'staged for review';
|
|
324
|
+
return '';
|
|
325
|
+
}
|
|
326
|
+
if (ev.tool_name === 'compare_tools') return m.recommendation ? \`rec: \${m.recommendation}\` : '';
|
|
327
|
+
if (ev.tool_name === 'check_compatibility') return m.compatibility_signal ? m.compatibility_signal : '';
|
|
328
|
+
return m.status || '';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function renderFeed() {
|
|
332
|
+
const feed = document.getElementById('feed');
|
|
333
|
+
const empty = document.getElementById('emptyState');
|
|
334
|
+
if (allEvents.length === 0) {
|
|
335
|
+
empty.style.display = 'flex';
|
|
336
|
+
feed.querySelectorAll('.event-row').forEach(r => r.remove());
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
empty.style.display = 'none';
|
|
340
|
+
|
|
341
|
+
// Remove rows not in allEvents
|
|
342
|
+
const existingIds = new Set(Array.from(feed.querySelectorAll('.event-row')).map(r => r.dataset.id));
|
|
343
|
+
const currentIds = new Set(allEvents.map(e => e.id));
|
|
344
|
+
existingIds.forEach(id => { if (!currentIds.has(id)) feed.querySelector(\`[data-id="\${id}"]\`)?.remove(); });
|
|
345
|
+
|
|
346
|
+
// Add new rows at top
|
|
347
|
+
for (const ev of allEvents) {
|
|
348
|
+
if (feed.querySelector(\`[data-id="\${ev.id}"]\`)) continue;
|
|
349
|
+
const row = document.createElement('div');
|
|
350
|
+
row.className = 'event-row' + (selectedId === ev.id ? ' selected' : '');
|
|
351
|
+
row.dataset.id = ev.id;
|
|
352
|
+
row.onclick = () => selectEvent(ev.id);
|
|
353
|
+
|
|
354
|
+
const badgeClass = ev.status === 'ok' ? 'ok' : 'error';
|
|
355
|
+
const summary = toolSummary(ev);
|
|
356
|
+
row.innerHTML = \`
|
|
357
|
+
<span class="time">\${fmtTime(ev.created_at)}</span>
|
|
358
|
+
<span class="tool">\${ev.tool_name}</span>
|
|
359
|
+
<span class="summary">\${summary}</span>
|
|
360
|
+
<span class="dur">\${ev.duration_ms}ms</span>
|
|
361
|
+
<span class="badge \${badgeClass}">\${ev.status}</span>
|
|
362
|
+
\`;
|
|
363
|
+
|
|
364
|
+
// Insert in chronological order (newest first)
|
|
365
|
+
const firstRow = feed.querySelector('.event-row');
|
|
366
|
+
if (firstRow) feed.insertBefore(row, firstRow);
|
|
367
|
+
else feed.appendChild(row);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function renderMetrics() {
|
|
372
|
+
const total = allEvents.length;
|
|
373
|
+
const okCount = allEvents.filter(e => e.status === 'ok').length;
|
|
374
|
+
const avgMs = total > 0 ? Math.round(allEvents.reduce((s, e) => s + e.duration_ms, 0) / total) : 0;
|
|
375
|
+
const issueCount = allEvents.filter(e => e.tool_name === 'check_issue').length;
|
|
376
|
+
const deprecCount = allEvents.filter(e => e.metadata?.had_deprecation_warning).length;
|
|
377
|
+
const nonOssCount = allEvents.filter(e => e.metadata?.had_non_indexed_guidance).length;
|
|
378
|
+
const graphCount = allEvents.filter(e => e.tool_name === 'suggest_graph_update').length;
|
|
379
|
+
|
|
380
|
+
document.getElementById('mTotal').textContent = total;
|
|
381
|
+
document.getElementById('mSuccess').textContent = total > 0 ? Math.round(okCount / total * 100) + '%' : '\u2014';
|
|
382
|
+
document.getElementById('mLatency').textContent = total > 0 ? avgMs + 'ms' : '\u2014';
|
|
383
|
+
document.getElementById('mIssues').textContent = issueCount;
|
|
384
|
+
document.getElementById('mDeprecation').textContent = deprecCount;
|
|
385
|
+
document.getElementById('mNonOss').textContent = nonOssCount;
|
|
386
|
+
document.getElementById('mGraph').textContent = graphCount;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function renderToolChart() {
|
|
390
|
+
const counts = {};
|
|
391
|
+
for (const ev of allEvents) counts[ev.tool_name] = (counts[ev.tool_name] || 0) + 1;
|
|
392
|
+
const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 8);
|
|
393
|
+
const max = sorted[0]?.[1] || 1;
|
|
394
|
+
const html = sorted.map(([tool, count]) => \`
|
|
395
|
+
<div class="bar-row">
|
|
396
|
+
<span class="bar-label">\${tool}</span>
|
|
397
|
+
<div class="bar-track"><div class="bar-fill" style="width:\${count/max*100}%"></div></div>
|
|
398
|
+
<span class="bar-count">\${count}</span>
|
|
399
|
+
</div>
|
|
400
|
+
\`).join('');
|
|
401
|
+
document.getElementById('toolChart').innerHTML = html || '<span style="color:var(--muted);font-size:12px">No data yet</span>';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function renderInsights() {
|
|
405
|
+
const insights = [];
|
|
406
|
+
for (const ev of allEvents.slice(0, 50)) {
|
|
407
|
+
const m = ev.metadata || {};
|
|
408
|
+
if (ev.tool_name === 'check_issue' && ev.status === 'ok') {
|
|
409
|
+
insights.push({ tool: ev.tool_name, text: 'Issue check ran \u2014 may have prevented a debug loop', time: ev.created_at });
|
|
410
|
+
}
|
|
411
|
+
if (m.had_deprecation_warning) {
|
|
412
|
+
insights.push({ tool: ev.tool_name, text: 'Deprecated/unmaintained tool detected in results', time: ev.created_at });
|
|
413
|
+
}
|
|
414
|
+
if (m.auto_graduated) {
|
|
415
|
+
insights.push({ tool: 'suggest_graph_update', text: 'New edge auto-graduated to graph (confidence \u22650.8)', time: ev.created_at });
|
|
416
|
+
}
|
|
417
|
+
if (m.had_non_indexed_guidance) {
|
|
418
|
+
insights.push({ tool: ev.tool_name, text: 'Non-indexed tool detected \u2014 non-OSS guidance provided', time: ev.created_at });
|
|
419
|
+
}
|
|
420
|
+
if (m.recommendation) {
|
|
421
|
+
insights.push({ tool: 'compare_tools', text: \`Tool comparison recommended: \${m.recommendation}\`, time: ev.created_at });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const list = document.getElementById('insightsList');
|
|
425
|
+
if (insights.length === 0) {
|
|
426
|
+
list.innerHTML = '<li style="color:var(--muted);font-size:12px">No insights yet</li>';
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
list.innerHTML = insights.slice(0, 8).map(i => \`
|
|
430
|
+
<li class="insight-item">
|
|
431
|
+
<div class="i-tool">\${i.tool}</div>
|
|
432
|
+
<div class="i-text">\${i.text}</div>
|
|
433
|
+
</li>
|
|
434
|
+
\`).join('');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function selectEvent(id) {
|
|
438
|
+
selectedId = id;
|
|
439
|
+
document.querySelectorAll('.event-row').forEach(r => r.classList.toggle('selected', r.dataset.id === id));
|
|
440
|
+
const ev = allEvents.find(e => e.id === id);
|
|
441
|
+
if (!ev) return;
|
|
442
|
+
const panel = document.getElementById('detailPanel');
|
|
443
|
+
const content = document.getElementById('detailContent');
|
|
444
|
+
panel.style.display = 'block';
|
|
445
|
+
const m = ev.metadata || {};
|
|
446
|
+
const rows = [
|
|
447
|
+
['Tool', ev.tool_name],
|
|
448
|
+
['Status', ev.status],
|
|
449
|
+
['Duration', ev.duration_ms + 'ms'],
|
|
450
|
+
['Time', new Date(ev.created_at).toLocaleString()],
|
|
451
|
+
ev.query_id ? ['Session ID', ev.query_id.slice(0, 8) + '...'] : null,
|
|
452
|
+
...Object.entries(m).filter(([k]) => k !== 'tool').map(([k, v]) => [k, String(v)])
|
|
453
|
+
].filter(Boolean);
|
|
454
|
+
content.innerHTML = rows.map(([k, v]) => {
|
|
455
|
+
const cls = v === 'true' || v === 'ok' ? 'green' : v === 'false' || v === 'error' ? 'red' : '';
|
|
456
|
+
return \`<div class="kv"><span class="k">\${k}</span><span class="v \${cls}">\${v}</span></div>\`;
|
|
457
|
+
}).join('');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function renderAll() {
|
|
461
|
+
renderFeed();
|
|
462
|
+
renderMetrics();
|
|
463
|
+
renderToolChart();
|
|
464
|
+
renderInsights();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// \u2500\u2500\u2500 Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
468
|
+
if (!EVENTS_PATH || EVENTS_PATH === 'null') {
|
|
469
|
+
document.getElementById('statusText').textContent = 'No events path configured';
|
|
470
|
+
document.getElementById('emptyState').querySelector('p').textContent = 'TOOLCAIRN_EVENTS_PATH not set in MCP server environment';
|
|
471
|
+
} else {
|
|
472
|
+
startPolling();
|
|
473
|
+
}
|
|
474
|
+
</script>
|
|
475
|
+
</body>
|
|
476
|
+
</html>`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/project-setup.ts
|
|
480
|
+
var logger = pino({ name: "@toolcairn/mcp-server:project-setup" });
|
|
481
|
+
var INITIAL_CONFIG = {
|
|
482
|
+
version: "1.0",
|
|
483
|
+
project: {
|
|
484
|
+
name: "",
|
|
485
|
+
language: "",
|
|
486
|
+
framework: ""
|
|
487
|
+
},
|
|
488
|
+
tools: {
|
|
489
|
+
confirmed: [],
|
|
490
|
+
pending_evaluation: []
|
|
491
|
+
},
|
|
492
|
+
audit_log: []
|
|
493
|
+
};
|
|
494
|
+
function detectOs() {
|
|
495
|
+
const p = platform();
|
|
496
|
+
const labels = {
|
|
497
|
+
win32: "Windows",
|
|
498
|
+
darwin: "macOS",
|
|
499
|
+
linux: "Linux",
|
|
500
|
+
freebsd: "FreeBSD",
|
|
501
|
+
openbsd: "OpenBSD",
|
|
502
|
+
sunos: "Solaris",
|
|
503
|
+
android: "Android"
|
|
504
|
+
};
|
|
505
|
+
return { platform: p, label: labels[p] ?? type() };
|
|
506
|
+
}
|
|
507
|
+
function toFileUrl(absPath) {
|
|
508
|
+
return absPath.replace(/\\/g, "/");
|
|
509
|
+
}
|
|
510
|
+
async function ensureProjectSetup(projectRoot = process.cwd()) {
|
|
511
|
+
const os = detectOs();
|
|
512
|
+
logger.info(
|
|
513
|
+
{ os: os.label, platform: os.platform, projectRoot },
|
|
514
|
+
"Detected OS \u2014 starting project setup"
|
|
515
|
+
);
|
|
516
|
+
const dir = join(projectRoot, ".toolpilot");
|
|
517
|
+
const configPath = join(dir, "config.json");
|
|
518
|
+
const trackerPath = join(dir, "tracker.html");
|
|
519
|
+
const eventsPath = join(dir, "events.jsonl");
|
|
520
|
+
const eventsPathForUrl = toFileUrl(eventsPath);
|
|
521
|
+
try {
|
|
522
|
+
await mkdir(dir, { recursive: true });
|
|
523
|
+
await createIfAbsent(configPath, JSON.stringify(INITIAL_CONFIG, null, 2), "config.json");
|
|
524
|
+
await createIfAbsent(trackerPath, generateTrackerHtml(eventsPathForUrl), "tracker.html");
|
|
525
|
+
await createIfAbsent(eventsPath, "", "events.jsonl");
|
|
526
|
+
logger.info({ dir, os: os.label }, ".toolpilot setup ready");
|
|
527
|
+
} catch (e) {
|
|
528
|
+
logger.warn(
|
|
529
|
+
{ err: e, dir, os: os.label },
|
|
530
|
+
"Project setup failed \u2014 continuing without .toolpilot files"
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function createIfAbsent(filePath, content, label) {
|
|
535
|
+
try {
|
|
536
|
+
await access(filePath);
|
|
537
|
+
logger.debug({ file: label }, "Already exists \u2014 skipping");
|
|
538
|
+
} catch {
|
|
539
|
+
await writeFile(filePath, content, "utf-8");
|
|
540
|
+
logger.info({ file: label }, "Created");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
66
543
|
|
|
67
544
|
// src/server.prod.ts
|
|
68
545
|
init_esm_shims();
|
|
@@ -75,7 +552,7 @@ init_esm_shims();
|
|
|
75
552
|
// ../../packages/remote/dist/client.js
|
|
76
553
|
init_esm_shims();
|
|
77
554
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
78
|
-
var
|
|
555
|
+
var ToolCairnClient = class {
|
|
79
556
|
baseUrl;
|
|
80
557
|
apiKey;
|
|
81
558
|
timeoutMs;
|
|
@@ -153,7 +630,7 @@ var ToolPilotClient = class {
|
|
|
153
630
|
text: JSON.stringify({
|
|
154
631
|
ok: false,
|
|
155
632
|
error: "network_error",
|
|
156
|
-
message: `
|
|
633
|
+
message: `ToolCairn API unreachable: ${msg}. Check your internet connection or try again later.`
|
|
157
634
|
})
|
|
158
635
|
}
|
|
159
636
|
],
|
|
@@ -166,7 +643,7 @@ var ToolPilotClient = class {
|
|
|
166
643
|
method: "POST",
|
|
167
644
|
headers: {
|
|
168
645
|
"Content-Type": "application/json",
|
|
169
|
-
"X-
|
|
646
|
+
"X-ToolCairn-Key": this.apiKey,
|
|
170
647
|
"Accept-Encoding": "gzip"
|
|
171
648
|
},
|
|
172
649
|
body: JSON.stringify(body),
|
|
@@ -177,11 +654,11 @@ var ToolPilotClient = class {
|
|
|
177
654
|
|
|
178
655
|
// ../../packages/remote/dist/credentials.js
|
|
179
656
|
init_esm_shims();
|
|
180
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
657
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
181
658
|
import { homedir } from "os";
|
|
182
|
-
import { join } from "path";
|
|
183
|
-
var CREDENTIALS_DIR =
|
|
184
|
-
var CREDENTIALS_FILE =
|
|
659
|
+
import { join as join2 } from "path";
|
|
660
|
+
var CREDENTIALS_DIR = join2(homedir(), ".toolpilot");
|
|
661
|
+
var CREDENTIALS_FILE = join2(CREDENTIALS_DIR, "credentials.json");
|
|
185
662
|
async function loadOrCreateCredentials(registerFn) {
|
|
186
663
|
try {
|
|
187
664
|
const raw = await readFile(CREDENTIALS_FILE, "utf-8");
|
|
@@ -200,8 +677,8 @@ async function loadOrCreateCredentials(registerFn) {
|
|
|
200
677
|
}
|
|
201
678
|
}
|
|
202
679
|
async function saveCredentials(creds) {
|
|
203
|
-
await
|
|
204
|
-
await
|
|
680
|
+
await mkdir2(CREDENTIALS_DIR, { recursive: true });
|
|
681
|
+
await writeFile2(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), "utf-8");
|
|
205
682
|
}
|
|
206
683
|
|
|
207
684
|
// ../../packages/tools/dist/local.js
|
|
@@ -348,8 +825,8 @@ function errResult(error, message) {
|
|
|
348
825
|
|
|
349
826
|
// ../../packages/tools/dist/handlers/classify-prompt.js
|
|
350
827
|
init_esm_shims();
|
|
351
|
-
import
|
|
352
|
-
var
|
|
828
|
+
import pino2 from "pino";
|
|
829
|
+
var logger2 = pino2({ name: "@toolpilot/tools:classify-prompt" });
|
|
353
830
|
var TOOL_REQUIRED_CLASSIFICATIONS = [
|
|
354
831
|
"tool_discovery",
|
|
355
832
|
"stack_building",
|
|
@@ -357,7 +834,7 @@ var TOOL_REQUIRED_CLASSIFICATIONS = [
|
|
|
357
834
|
];
|
|
358
835
|
async function handleClassifyPrompt(args) {
|
|
359
836
|
try {
|
|
360
|
-
|
|
837
|
+
logger2.info({ promptLen: args.prompt.length }, "classify_prompt called");
|
|
361
838
|
const projectToolsContext = args.project_tools && args.project_tools.length > 0 ? `
|
|
362
839
|
|
|
363
840
|
The project already uses: ${args.project_tools.join(", ")}. Consider whether the prompt relates to tools already confirmed in the project.` : "";
|
|
@@ -403,14 +880,14 @@ Respond with ONLY 0 or 1.`;
|
|
|
403
880
|
instructions: "Step 1: Send classification_prompt to the LLM and get a classification. Step 2: If classification is in tool_required_if, call refine_requirement with the classification. Otherwise, proceed without ToolPilot search."
|
|
404
881
|
});
|
|
405
882
|
} catch (e) {
|
|
406
|
-
|
|
883
|
+
logger2.error({ err: e }, "classify_prompt failed");
|
|
407
884
|
return errResult("classify_error", e instanceof Error ? e.message : String(e));
|
|
408
885
|
}
|
|
409
886
|
}
|
|
410
887
|
|
|
411
888
|
// ../../packages/tools/dist/handlers/toolpilot-init.js
|
|
412
889
|
init_esm_shims();
|
|
413
|
-
import
|
|
890
|
+
import pino3 from "pino";
|
|
414
891
|
|
|
415
892
|
// ../../packages/tools/dist/templates/agent-instructions.js
|
|
416
893
|
init_esm_shims();
|
|
@@ -556,7 +1033,7 @@ function getOpenCodeMcpEntry(serverPath) {
|
|
|
556
1033
|
|
|
557
1034
|
// ../../packages/tools/dist/templates/generate-tracker.js
|
|
558
1035
|
init_esm_shims();
|
|
559
|
-
function
|
|
1036
|
+
function generateTrackerHtml2(eventsPath) {
|
|
560
1037
|
return `<!DOCTYPE html>
|
|
561
1038
|
<html lang="en">
|
|
562
1039
|
<head>
|
|
@@ -960,10 +1437,10 @@ if (!EVENTS_PATH || EVENTS_PATH === 'null') {
|
|
|
960
1437
|
}
|
|
961
1438
|
|
|
962
1439
|
// ../../packages/tools/dist/handlers/toolpilot-init.js
|
|
963
|
-
var
|
|
1440
|
+
var logger3 = pino3({ name: "@toolpilot/tools:toolpilot-init" });
|
|
964
1441
|
async function handleToolpilotInit(args) {
|
|
965
1442
|
try {
|
|
966
|
-
|
|
1443
|
+
logger3.info({ agent: args.agent, project_root: args.project_root }, "toolpilot_init called");
|
|
967
1444
|
const instructions = getInstructionsForAgent(args.agent);
|
|
968
1445
|
const isOpenCode = args.agent === "opencode";
|
|
969
1446
|
const mcpConfigEntry = isOpenCode ? getOpenCodeMcpEntry(args.server_path) : getMcpConfigEntry(args.server_path);
|
|
@@ -1005,7 +1482,7 @@ async function handleToolpilotInit(args) {
|
|
|
1005
1482
|
step: step++,
|
|
1006
1483
|
action: "create",
|
|
1007
1484
|
file: ".toolpilot/tracker.html",
|
|
1008
|
-
content:
|
|
1485
|
+
content: generateTrackerHtml2(eventsPath),
|
|
1009
1486
|
note: `Open .toolpilot/tracker.html in your browser to monitor MCP tool calls in real time. Set TOOLPILOT_EVENTS_PATH=${eventsPath} in your MCP server environment to enable event logging.`
|
|
1010
1487
|
});
|
|
1011
1488
|
}
|
|
@@ -1041,18 +1518,18 @@ async function handleToolpilotInit(args) {
|
|
|
1041
1518
|
next_steps: hasToolpilotConfig ? "Setup complete. Open .toolpilot/tracker.html to monitor tool calls." : "After completing setup steps, call init_project_config to initialize .toolpilot/config.json."
|
|
1042
1519
|
});
|
|
1043
1520
|
} catch (e) {
|
|
1044
|
-
|
|
1521
|
+
logger3.error({ err: e }, "toolpilot_init failed");
|
|
1045
1522
|
return errResult("init_error", e instanceof Error ? e.message : String(e));
|
|
1046
1523
|
}
|
|
1047
1524
|
}
|
|
1048
1525
|
|
|
1049
1526
|
// ../../packages/tools/dist/handlers/init-project-config.js
|
|
1050
1527
|
init_esm_shims();
|
|
1051
|
-
import
|
|
1052
|
-
var
|
|
1528
|
+
import pino4 from "pino";
|
|
1529
|
+
var logger4 = pino4({ name: "@toolpilot/tools:init-project-config" });
|
|
1053
1530
|
async function handleInitProjectConfig(args) {
|
|
1054
1531
|
try {
|
|
1055
|
-
|
|
1532
|
+
logger4.info({ project: args.project_name }, "init_project_config called");
|
|
1056
1533
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1057
1534
|
const confirmedTools = (args.detected_tools ?? []).map((t) => ({
|
|
1058
1535
|
name: t.name,
|
|
@@ -1091,22 +1568,22 @@ async function handleInitProjectConfig(args) {
|
|
|
1091
1568
|
next_step: confirmedTools.length > 0 ? "Config initialized with auto-detected tools. Use search_tools to find any additional tools you need." : "Config initialized. Use classify_prompt \u2192 refine_requirement \u2192 search_tools to discover tools for your project."
|
|
1092
1569
|
});
|
|
1093
1570
|
} catch (e) {
|
|
1094
|
-
|
|
1571
|
+
logger4.error({ err: e }, "init_project_config failed");
|
|
1095
1572
|
return errResult("init_config_error", e instanceof Error ? e.message : String(e));
|
|
1096
1573
|
}
|
|
1097
1574
|
}
|
|
1098
1575
|
|
|
1099
1576
|
// ../../packages/tools/dist/handlers/read-project-config.js
|
|
1100
1577
|
init_esm_shims();
|
|
1101
|
-
import
|
|
1102
|
-
var
|
|
1578
|
+
import pino5 from "pino";
|
|
1579
|
+
var logger5 = pino5({ name: "@toolpilot/tools:read-project-config" });
|
|
1103
1580
|
var STALENESS_THRESHOLD_DAYS = 90;
|
|
1104
1581
|
function daysSince(isoDate) {
|
|
1105
1582
|
return (Date.now() - new Date(isoDate).getTime()) / (1e3 * 60 * 60 * 24);
|
|
1106
1583
|
}
|
|
1107
1584
|
async function handleReadProjectConfig(args) {
|
|
1108
1585
|
try {
|
|
1109
|
-
|
|
1586
|
+
logger5.info("read_project_config called");
|
|
1110
1587
|
let config3;
|
|
1111
1588
|
try {
|
|
1112
1589
|
config3 = JSON.parse(args.config_content);
|
|
@@ -1152,18 +1629,18 @@ async function handleReadProjectConfig(args) {
|
|
|
1152
1629
|
].filter(Boolean).join("\n")
|
|
1153
1630
|
});
|
|
1154
1631
|
} catch (e) {
|
|
1155
|
-
|
|
1632
|
+
logger5.error({ err: e }, "read_project_config failed");
|
|
1156
1633
|
return errResult("read_config_error", e instanceof Error ? e.message : String(e));
|
|
1157
1634
|
}
|
|
1158
1635
|
}
|
|
1159
1636
|
|
|
1160
1637
|
// ../../packages/tools/dist/handlers/update-project-config.js
|
|
1161
1638
|
init_esm_shims();
|
|
1162
|
-
import
|
|
1163
|
-
var
|
|
1639
|
+
import pino6 from "pino";
|
|
1640
|
+
var logger6 = pino6({ name: "@toolpilot/tools:update-project-config" });
|
|
1164
1641
|
async function handleUpdateProjectConfig(args) {
|
|
1165
1642
|
try {
|
|
1166
|
-
|
|
1643
|
+
logger6.info({ action: args.action, tool: args.tool_name }, "update_project_config called");
|
|
1167
1644
|
let config3;
|
|
1168
1645
|
try {
|
|
1169
1646
|
config3 = JSON.parse(args.current_config);
|
|
@@ -1261,20 +1738,20 @@ async function handleUpdateProjectConfig(args) {
|
|
|
1261
1738
|
instructions: "Write updated_config_json to .toolpilot/config.json to persist this change."
|
|
1262
1739
|
});
|
|
1263
1740
|
} catch (e) {
|
|
1264
|
-
|
|
1741
|
+
logger6.error({ err: e }, "update_project_config failed");
|
|
1265
1742
|
return errResult("update_config_error", e instanceof Error ? e.message : String(e));
|
|
1266
1743
|
}
|
|
1267
1744
|
}
|
|
1268
1745
|
|
|
1269
1746
|
// src/server.prod.ts
|
|
1270
|
-
import
|
|
1747
|
+
import pino8 from "pino";
|
|
1271
1748
|
|
|
1272
1749
|
// src/middleware/event-logger.ts
|
|
1273
1750
|
init_esm_shims();
|
|
1274
|
-
import { appendFile, mkdir as
|
|
1751
|
+
import { appendFile, mkdir as mkdir3 } from "fs/promises";
|
|
1275
1752
|
import { dirname } from "path";
|
|
1276
|
-
import
|
|
1277
|
-
var
|
|
1753
|
+
import pino7 from "pino";
|
|
1754
|
+
var logger7 = pino7({ name: "@toolcairn/mcp-server:event-logger" });
|
|
1278
1755
|
var _prisma = null;
|
|
1279
1756
|
async function getPrisma() {
|
|
1280
1757
|
if (!_prisma) {
|
|
@@ -1287,10 +1764,10 @@ async function getPrisma() {
|
|
|
1287
1764
|
return _prisma;
|
|
1288
1765
|
}
|
|
1289
1766
|
function isTrackingEnabled() {
|
|
1290
|
-
return process.env.
|
|
1767
|
+
return process.env.TOOLCAIRN_TRACKING_ENABLED !== "false";
|
|
1291
1768
|
}
|
|
1292
1769
|
function getEventsPath() {
|
|
1293
|
-
return process.env.
|
|
1770
|
+
return process.env.TOOLCAIRN_EVENTS_PATH ?? null;
|
|
1294
1771
|
}
|
|
1295
1772
|
function extractQueryId(args) {
|
|
1296
1773
|
if (typeof args.query_id === "string") return args.query_id;
|
|
@@ -1325,11 +1802,11 @@ function extractMetadata(toolName, result) {
|
|
|
1325
1802
|
}
|
|
1326
1803
|
async function writeToFile(eventsPath, event) {
|
|
1327
1804
|
try {
|
|
1328
|
-
await
|
|
1805
|
+
await mkdir3(dirname(eventsPath), { recursive: true });
|
|
1329
1806
|
await appendFile(eventsPath, `${JSON.stringify(event)}
|
|
1330
1807
|
`, "utf-8");
|
|
1331
1808
|
} catch (e) {
|
|
1332
|
-
|
|
1809
|
+
logger7.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
|
|
1333
1810
|
}
|
|
1334
1811
|
}
|
|
1335
1812
|
async function writeToPrisma(event) {
|
|
@@ -1348,7 +1825,7 @@ async function writeToPrisma(event) {
|
|
|
1348
1825
|
}
|
|
1349
1826
|
});
|
|
1350
1827
|
} catch (e) {
|
|
1351
|
-
|
|
1828
|
+
logger7.warn({ err: e }, "Failed to write McpEvent to Prisma");
|
|
1352
1829
|
}
|
|
1353
1830
|
}
|
|
1354
1831
|
function withEventLogging(toolName, handler) {
|
|
@@ -1389,28 +1866,37 @@ function withEventLogging(toolName, handler) {
|
|
|
1389
1866
|
}
|
|
1390
1867
|
|
|
1391
1868
|
// src/server.prod.ts
|
|
1392
|
-
var
|
|
1869
|
+
var logger8 = pino8({ name: "@toolcairn/mcp-server:prod" });
|
|
1393
1870
|
var SETUP_INSTRUCTIONS = `
|
|
1394
|
-
#
|
|
1871
|
+
# ToolCairn \u2014 Agent Setup Instructions
|
|
1395
1872
|
|
|
1396
|
-
You are connected to the
|
|
1873
|
+
You are connected to the ToolCairn MCP server. ToolCairn is your active tool intelligence co-pilot.
|
|
1397
1874
|
|
|
1398
1875
|
## On Every Session Start \u2014 Do This First
|
|
1399
1876
|
|
|
1400
|
-
|
|
1877
|
+
Read \`.toolcairn/config.json\` in the project root. The server auto-creates this file on startup,
|
|
1878
|
+
so it will almost always exist \u2014 but it may be an empty scaffold that still needs configuration.
|
|
1401
1879
|
|
|
1402
|
-
###
|
|
1880
|
+
### Case 1 \u2014 File does NOT exist (rare: read-only filesystem or manual deletion):
|
|
1403
1881
|
1. List the project root files (package.json, requirements.txt, CLAUDE.md, .cursorrules, .mcp.json, etc.)
|
|
1404
|
-
2. Call \`
|
|
1405
|
-
3. Call \`init_project_config\` \u2014 pass
|
|
1406
|
-
4. Write the returned \`config_json\` to \`.
|
|
1407
|
-
|
|
1408
|
-
###
|
|
1409
|
-
|
|
1882
|
+
2. Call \`toolcairn_init\` with your agent type, project root path, and the list of detected files.
|
|
1883
|
+
3. Call \`init_project_config\` \u2014 pass project name, language, framework, and any tools you detect.
|
|
1884
|
+
4. Write the returned \`config_json\` to \`.toolcairn/config.json\`.
|
|
1885
|
+
|
|
1886
|
+
### Case 2 \u2014 File EXISTS but \`project.name\` is empty (auto-created scaffold \u2014 not yet configured):
|
|
1887
|
+
The server wrote the file at startup. You still need to fill in the project details:
|
|
1888
|
+
1. List the project root files to detect the tech stack.
|
|
1889
|
+
2. Call \`toolcairn_init\` with your agent type, project root, and detected files.
|
|
1890
|
+
This appends ToolCairn rules to CLAUDE.md (or equivalent) and sets up the tracker.
|
|
1891
|
+
3. Call \`init_project_config\` with the project name, language, framework, and detected tools.
|
|
1892
|
+
4. Write the returned \`config_json\` to \`.toolcairn/config.json\` (overwrites the empty scaffold).
|
|
1893
|
+
|
|
1894
|
+
### Case 3 \u2014 File EXISTS and \`project.name\` is set (returning to a configured project):
|
|
1895
|
+
1. Call \`read_project_config\` with the file content.
|
|
1410
1896
|
2. If \`stale_tools\` is non-empty, inform the user and offer to re-verify those tools.
|
|
1411
|
-
3. If \`pending_evaluation\` is non-empty, offer to run \`search_tools\` or \`compare_tools
|
|
1897
|
+
3. If \`pending_evaluation\` is non-empty, offer to run \`search_tools\` or \`compare_tools\`.
|
|
1412
1898
|
|
|
1413
|
-
## When to Use
|
|
1899
|
+
## When to Use ToolCairn Tools
|
|
1414
1900
|
|
|
1415
1901
|
| Situation | Tool to call |
|
|
1416
1902
|
|-----------|-------------|
|
|
@@ -1426,38 +1912,38 @@ Check whether \`.toolpilot/config.json\` exists in the project root.
|
|
|
1426
1912
|
`.trim();
|
|
1427
1913
|
async function buildProdServer() {
|
|
1428
1914
|
const creds = await loadOrCreateCredentials();
|
|
1429
|
-
const remote = new
|
|
1915
|
+
const remote = new ToolCairnClient({
|
|
1430
1916
|
baseUrl: import_config.config.TOOLPILOT_API_URL,
|
|
1431
1917
|
apiKey: creds.client_id
|
|
1432
1918
|
});
|
|
1433
|
-
|
|
1919
|
+
logger8.info(
|
|
1434
1920
|
{ apiUrl: import_config.config.TOOLPILOT_API_URL, clientId: `${creds.client_id.slice(0, 8)}...` },
|
|
1435
1921
|
"Production MCP mode: connecting to remote API"
|
|
1436
1922
|
);
|
|
1437
1923
|
const server = new McpServer(
|
|
1438
|
-
{ name: "
|
|
1924
|
+
{ name: "toolcairn", version: "0.1.0" },
|
|
1439
1925
|
{ instructions: SETUP_INSTRUCTIONS }
|
|
1440
1926
|
);
|
|
1441
1927
|
server.registerTool(
|
|
1442
1928
|
"classify_prompt",
|
|
1443
1929
|
{
|
|
1444
|
-
description: "Classify a developer prompt to determine if
|
|
1930
|
+
description: "Classify a developer prompt to determine if ToolCairn tool search is needed. Returns a structured classification prompt for the agent to evaluate.",
|
|
1445
1931
|
inputSchema: classifyPromptSchema
|
|
1446
1932
|
},
|
|
1447
1933
|
withEventLogging("classify_prompt", async (args) => handleClassifyPrompt(args))
|
|
1448
1934
|
);
|
|
1449
1935
|
server.registerTool(
|
|
1450
|
-
"
|
|
1936
|
+
"toolcairn_init",
|
|
1451
1937
|
{
|
|
1452
|
-
description: "Set up
|
|
1938
|
+
description: "Set up ToolCairn integration for the current project. Generates agent instruction content, MCP config entry, and project config initializer.",
|
|
1453
1939
|
inputSchema: toolpilotInitSchema
|
|
1454
1940
|
},
|
|
1455
|
-
withEventLogging("
|
|
1941
|
+
withEventLogging("toolcairn_init", async (args) => handleToolpilotInit(args))
|
|
1456
1942
|
);
|
|
1457
1943
|
server.registerTool(
|
|
1458
1944
|
"init_project_config",
|
|
1459
1945
|
{
|
|
1460
|
-
description: "Initialize a .
|
|
1946
|
+
description: "Initialize a .toolcairn/config.json file for the current project. Returns the config JSON for the agent to write to disk.",
|
|
1461
1947
|
inputSchema: initProjectConfigSchema
|
|
1462
1948
|
},
|
|
1463
1949
|
withEventLogging("init_project_config", async (args) => handleInitProjectConfig(args))
|
|
@@ -1465,7 +1951,7 @@ async function buildProdServer() {
|
|
|
1465
1951
|
server.registerTool(
|
|
1466
1952
|
"read_project_config",
|
|
1467
1953
|
{
|
|
1468
|
-
description: "Parse and validate a .
|
|
1954
|
+
description: "Parse and validate a .toolcairn/config.json file. Returns confirmed tools, pending evaluations, stale tools, and agent instructions.",
|
|
1469
1955
|
inputSchema: readProjectConfigSchema
|
|
1470
1956
|
},
|
|
1471
1957
|
withEventLogging("read_project_config", async (args) => handleReadProjectConfig(args))
|
|
@@ -1473,7 +1959,7 @@ async function buildProdServer() {
|
|
|
1473
1959
|
server.registerTool(
|
|
1474
1960
|
"update_project_config",
|
|
1475
1961
|
{
|
|
1476
|
-
description: "Apply a mutation to .
|
|
1962
|
+
description: "Apply a mutation to .toolcairn/config.json and return the updated content. Actions: add_tool, remove_tool, update_tool, add_evaluation.",
|
|
1477
1963
|
inputSchema: updateProjectConfigSchema
|
|
1478
1964
|
},
|
|
1479
1965
|
withEventLogging("update_project_config", async (args) => handleUpdateProjectConfig(args))
|
|
@@ -1537,7 +2023,7 @@ async function buildProdServer() {
|
|
|
1537
2023
|
server.registerTool(
|
|
1538
2024
|
"verify_suggestion",
|
|
1539
2025
|
{
|
|
1540
|
-
description: "Validate agent-suggested tools against the
|
|
2026
|
+
description: "Validate agent-suggested tools against the ToolCairn graph.",
|
|
1541
2027
|
inputSchema: verifySuggestionSchema
|
|
1542
2028
|
},
|
|
1543
2029
|
withEventLogging("verify_suggestion", async (args) => remote.verifySuggestion(args))
|
|
@@ -1545,7 +2031,7 @@ async function buildProdServer() {
|
|
|
1545
2031
|
server.registerTool(
|
|
1546
2032
|
"report_outcome",
|
|
1547
2033
|
{
|
|
1548
|
-
description: "Report the outcome of using a tool recommended by
|
|
2034
|
+
description: "Report the outcome of using a tool recommended by ToolCairn (fire-and-forget).",
|
|
1549
2035
|
inputSchema: reportOutcomeSchema
|
|
1550
2036
|
},
|
|
1551
2037
|
withEventLogging("report_outcome", async (args) => remote.reportOutcome(args))
|
|
@@ -1553,7 +2039,7 @@ async function buildProdServer() {
|
|
|
1553
2039
|
server.registerTool(
|
|
1554
2040
|
"suggest_graph_update",
|
|
1555
2041
|
{
|
|
1556
|
-
description: "Suggest a new tool, relationship, use case, or health update to the
|
|
2042
|
+
description: "Suggest a new tool, relationship, use case, or health update to the ToolCairn graph.",
|
|
1557
2043
|
inputSchema: suggestGraphUpdateSchema
|
|
1558
2044
|
},
|
|
1559
2045
|
withEventLogging("suggest_graph_update", async (args) => remote.suggestGraphUpdate(args))
|
|
@@ -1577,16 +2063,17 @@ function createTransport() {
|
|
|
1577
2063
|
|
|
1578
2064
|
// src/index.prod.ts
|
|
1579
2065
|
process.env.TOOLPILOT_MODE = "production";
|
|
1580
|
-
var
|
|
2066
|
+
var logger9 = pino9({ name: "@toolcairn/mcp-server" });
|
|
1581
2067
|
async function main() {
|
|
1582
|
-
|
|
2068
|
+
logger9.info("Starting ToolCairn MCP Server (production mode)");
|
|
2069
|
+
await ensureProjectSetup();
|
|
1583
2070
|
const server = await buildProdServer();
|
|
1584
2071
|
const transport = createTransport();
|
|
1585
2072
|
await server.connect(transport);
|
|
1586
|
-
|
|
2073
|
+
logger9.info("ToolCairn MCP Server started");
|
|
1587
2074
|
}
|
|
1588
2075
|
main().catch((error) => {
|
|
1589
|
-
|
|
2076
|
+
pino9({ name: "@toolcairn/mcp-server" }).error({ err: error }, "Failed to start MCP server");
|
|
1590
2077
|
process.exit(1);
|
|
1591
2078
|
});
|
|
1592
2079
|
//# sourceMappingURL=index.js.map
|