mcp-baggage 0.1.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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/bin.d.ts +9 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +215 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +163 -0
- package/dist/config.js.map +1 -0
- package/dist/exact.d.ts +23 -0
- package/dist/exact.js +69 -0
- package/dist/exact.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +50 -0
- package/dist/mcp.js +288 -0
- package/dist/mcp.js.map +1 -0
- package/dist/report.d.ts +52 -0
- package/dist/report.js +166 -0
- package/dist/report.js.map +1 -0
- package/dist/tokens.d.ts +31 -0
- package/dist/tokens.js +67 -0
- package/dist/tokens.js.map +1 -0
- package/dist/toml.d.ts +11 -0
- package/dist/toml.js +145 -0
- package/dist/toml.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +43 -0
- package/dist/usage.js +119 -0
- package/dist/usage.js.map +1 -0
- package/dist/weigh.d.ts +41 -0
- package/dist/weigh.js +79 -0
- package/dist/weigh.js.map +1 -0
- package/package.json +50 -0
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal MCP client — enough to ask a server what it carries, and no more.
|
|
3
|
+
*
|
|
4
|
+
* The official SDK would do this, and would also pull a dependency tree into a
|
|
5
|
+
* tool whose whole subject is weight. What is needed here is four messages:
|
|
6
|
+
* `initialize`, the `initialized` notification, and then the three listings.
|
|
7
|
+
* No tool is ever called, nothing is written, and the connection is closed as
|
|
8
|
+
* soon as the listing is in hand.
|
|
9
|
+
*
|
|
10
|
+
* Both transports are spoken because both are in people's configs: stdio,
|
|
11
|
+
* where the server is a process this spawns, and streamable HTTP, where it is
|
|
12
|
+
* a URL. A server that fails is reported as a failed row rather than taken as
|
|
13
|
+
* a reason to stop — one broken entry in a config should not cost the run.
|
|
14
|
+
*/
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
16
|
+
import { createInterface } from 'node:readline';
|
|
17
|
+
/** The version this announces. Servers negotiate down; none of them refuse a listing. */
|
|
18
|
+
const PROTOCOL = '2025-06-18';
|
|
19
|
+
const CLIENT = { name: 'mcp-baggage', version: '0.1.0' };
|
|
20
|
+
/** How long one server gets, all in, before it is written off. */
|
|
21
|
+
export const DEFAULT_TIMEOUT = 20_000;
|
|
22
|
+
const asArray = (v) => (Array.isArray(v) ? v : []);
|
|
23
|
+
/** Tool entries, defensively: a server that sends nonsense loses that entry, not the row. */
|
|
24
|
+
function toTools(raw) {
|
|
25
|
+
return asArray(raw)
|
|
26
|
+
.map((t) => (t && typeof t === 'object' ? t : {}))
|
|
27
|
+
.filter((t) => typeof t['name'] === 'string')
|
|
28
|
+
.map((t) => ({
|
|
29
|
+
name: t['name'],
|
|
30
|
+
description: typeof t['description'] === 'string' ? t['description'] : undefined,
|
|
31
|
+
inputSchema: t['inputSchema'] ?? t['input_schema'],
|
|
32
|
+
outputSchema: t['outputSchema'],
|
|
33
|
+
annotations: t['annotations'],
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* How to start the process.
|
|
38
|
+
*
|
|
39
|
+
* Almost every server in a config is `npx` or `uvx`, which on Windows are
|
|
40
|
+
* `.cmd` shims that `CreateProcess` will not run — the shell has to. So on
|
|
41
|
+
* Windows the whole thing becomes one quoted command line handed to `cmd`,
|
|
42
|
+
* rather than a command plus an argument array: passing both is what Node
|
|
43
|
+
* deprecated, because the arguments would be concatenated unescaped.
|
|
44
|
+
*/
|
|
45
|
+
export function stdioOptions(spec) {
|
|
46
|
+
const args = spec.args ?? [];
|
|
47
|
+
if (process.platform !== 'win32')
|
|
48
|
+
return { command: spec.command, args, shell: false };
|
|
49
|
+
const quote = (s) => (s === '' || /[\s"&|<>^()]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s);
|
|
50
|
+
return { command: [spec.command, ...args].map(quote).join(' '), args: [], shell: true };
|
|
51
|
+
}
|
|
52
|
+
function openStdio(spec) {
|
|
53
|
+
const { command, args, shell } = stdioOptions(spec);
|
|
54
|
+
const child = spawn(command, args, {
|
|
55
|
+
shell,
|
|
56
|
+
cwd: spec.cwd ?? process.cwd(),
|
|
57
|
+
env: { ...process.env, ...spec.env },
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
});
|
|
61
|
+
const pending = new Map();
|
|
62
|
+
let nextId = 1;
|
|
63
|
+
let stderr = '';
|
|
64
|
+
child.stderr.on('data', (chunk) => {
|
|
65
|
+
// Kept only to explain a failure; a chatty server must not fill memory.
|
|
66
|
+
stderr = (stderr + chunk.toString()).slice(-2000);
|
|
67
|
+
});
|
|
68
|
+
const lines = createInterface({ input: child.stdout });
|
|
69
|
+
lines.on('line', (line) => {
|
|
70
|
+
if (!line.trim().startsWith('{'))
|
|
71
|
+
return; // servers that log to stdout anyway
|
|
72
|
+
let message;
|
|
73
|
+
try {
|
|
74
|
+
message = JSON.parse(line);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (typeof message.id !== 'number')
|
|
80
|
+
return;
|
|
81
|
+
const waiter = pending.get(message.id);
|
|
82
|
+
if (!waiter)
|
|
83
|
+
return;
|
|
84
|
+
pending.delete(message.id);
|
|
85
|
+
if (message.error)
|
|
86
|
+
waiter.reject(new Error(message.error.message ?? 'server returned an error'));
|
|
87
|
+
else
|
|
88
|
+
waiter.resolve(message.result);
|
|
89
|
+
});
|
|
90
|
+
const fail = (reason) => {
|
|
91
|
+
const error = new Error(stderr.trim() ? `${reason}: ${stderr.trim().split('\n').slice(-1)[0]}` : reason);
|
|
92
|
+
for (const waiter of pending.values())
|
|
93
|
+
waiter.reject(error);
|
|
94
|
+
pending.clear();
|
|
95
|
+
};
|
|
96
|
+
child.on('error', (e) => fail(e.message));
|
|
97
|
+
child.on('exit', (code) => fail(`server exited (${code ?? 'signal'})`));
|
|
98
|
+
const send = (payload) => {
|
|
99
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
request: (method, params) => new Promise((resolve, reject) => {
|
|
103
|
+
const id = nextId++;
|
|
104
|
+
pending.set(id, { resolve, reject });
|
|
105
|
+
send({ jsonrpc: '2.0', id, method, params: params ?? {} });
|
|
106
|
+
}),
|
|
107
|
+
notify: async (method, params) => {
|
|
108
|
+
send({ jsonrpc: '2.0', method, params: params ?? {} });
|
|
109
|
+
},
|
|
110
|
+
close: () => {
|
|
111
|
+
lines.close();
|
|
112
|
+
child.stdin.end();
|
|
113
|
+
child.kill();
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Streamable HTTP.
|
|
119
|
+
*
|
|
120
|
+
* The answer to a POST is either a JSON body or an SSE stream carrying the
|
|
121
|
+
* same object, and the spec allows a server to choose per request, so both are
|
|
122
|
+
* read. The session id the server hands back on `initialize` has to ride along
|
|
123
|
+
* on everything after it.
|
|
124
|
+
*/
|
|
125
|
+
function openHttp(spec, signal) {
|
|
126
|
+
const url = spec.url;
|
|
127
|
+
let sessionId;
|
|
128
|
+
let nextId = 1;
|
|
129
|
+
const post = async (payload) => fetch(url, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
signal,
|
|
132
|
+
headers: {
|
|
133
|
+
'content-type': 'application/json',
|
|
134
|
+
accept: 'application/json, text/event-stream',
|
|
135
|
+
'mcp-protocol-version': PROTOCOL,
|
|
136
|
+
...(sessionId ? { 'mcp-session-id': sessionId } : {}),
|
|
137
|
+
...spec.headers,
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify(payload),
|
|
140
|
+
});
|
|
141
|
+
/** An SSE body, reduced to the one JSON-RPC object that was asked for. */
|
|
142
|
+
const fromStream = (text, id) => {
|
|
143
|
+
for (const line of text.split(/\r?\n/)) {
|
|
144
|
+
if (!line.startsWith('data:'))
|
|
145
|
+
continue;
|
|
146
|
+
try {
|
|
147
|
+
const message = JSON.parse(line.slice(5).trim());
|
|
148
|
+
if (message.id === id)
|
|
149
|
+
return message;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// A comment or a keep-alive; the next line may still be the answer.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
request: async (method, params) => {
|
|
159
|
+
const id = nextId++;
|
|
160
|
+
const response = await post({ jsonrpc: '2.0', id, method, params: params ?? {} });
|
|
161
|
+
const handed = response.headers.get('mcp-session-id');
|
|
162
|
+
if (handed)
|
|
163
|
+
sessionId = handed;
|
|
164
|
+
if (!response.ok)
|
|
165
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`.trim());
|
|
166
|
+
const text = await response.text();
|
|
167
|
+
const message = response.headers.get('content-type')?.includes('text/event-stream')
|
|
168
|
+
? fromStream(text, id)
|
|
169
|
+
: JSON.parse(text);
|
|
170
|
+
if (!message)
|
|
171
|
+
throw new Error('no answer in the stream');
|
|
172
|
+
if (message.error)
|
|
173
|
+
throw new Error(message.error.message ?? 'server returned an error');
|
|
174
|
+
return message.result;
|
|
175
|
+
},
|
|
176
|
+
notify: async (method, params) => {
|
|
177
|
+
await post({ jsonrpc: '2.0', method, params: params ?? {} });
|
|
178
|
+
},
|
|
179
|
+
close: () => { },
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** Lists that come back a page at a time; a server with 200 tools sends several. */
|
|
183
|
+
async function listAll(session, method, field) {
|
|
184
|
+
const items = [];
|
|
185
|
+
let cursor;
|
|
186
|
+
for (let page = 0; page < 20; page++) {
|
|
187
|
+
const result = (await session.request(method, cursor ? { cursor } : {}));
|
|
188
|
+
if (!result)
|
|
189
|
+
break;
|
|
190
|
+
items.push(...asArray(result[field]));
|
|
191
|
+
cursor = typeof result['nextCursor'] === 'string' ? result['nextCursor'] : undefined;
|
|
192
|
+
if (!cursor)
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return items;
|
|
196
|
+
}
|
|
197
|
+
/** A listing a server does not support is an empty listing, not a failure. */
|
|
198
|
+
async function listOrNone(session, method, field) {
|
|
199
|
+
try {
|
|
200
|
+
return await listAll(session, method, field);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Ask one server what it carries.
|
|
208
|
+
*
|
|
209
|
+
* Never throws: a server that cannot be reached comes back as an inventory
|
|
210
|
+
* with an `error` and no tools, which is a row in the report like any other.
|
|
211
|
+
*/
|
|
212
|
+
export async function inspect(spec, timeout = DEFAULT_TIMEOUT) {
|
|
213
|
+
const started = Date.now();
|
|
214
|
+
const empty = { server: spec, tools: [], prompts: [], resources: [] };
|
|
215
|
+
if (spec.transport === 'stdio' && !spec.command) {
|
|
216
|
+
return { ...empty, ms: 0, error: 'no command in the config' };
|
|
217
|
+
}
|
|
218
|
+
const controller = new AbortController();
|
|
219
|
+
const session = spec.transport === 'stdio' ? openStdio(spec) : openHttp(spec, controller.signal);
|
|
220
|
+
const expiry = setTimeout(() => controller.abort(), timeout);
|
|
221
|
+
const giveUp = new Promise((_, reject) => {
|
|
222
|
+
controller.signal.addEventListener('abort', () => reject(new Error(`no answer in ${Math.round(timeout / 1000)}s`)));
|
|
223
|
+
});
|
|
224
|
+
try {
|
|
225
|
+
const handshake = (await Promise.race([
|
|
226
|
+
session.request('initialize', {
|
|
227
|
+
protocolVersion: PROTOCOL,
|
|
228
|
+
capabilities: {},
|
|
229
|
+
clientInfo: CLIENT,
|
|
230
|
+
}),
|
|
231
|
+
giveUp,
|
|
232
|
+
]));
|
|
233
|
+
await session.notify('notifications/initialized');
|
|
234
|
+
const info = (handshake?.['serverInfo'] ?? {});
|
|
235
|
+
const name = typeof info['name'] === 'string' ? info['name'] : undefined;
|
|
236
|
+
const version = typeof info['version'] === 'string' ? info['version'] : undefined;
|
|
237
|
+
const [tools, prompts, resources] = (await Promise.race([
|
|
238
|
+
Promise.all([
|
|
239
|
+
listOrNone(session, 'tools/list', 'tools'),
|
|
240
|
+
listOrNone(session, 'prompts/list', 'prompts'),
|
|
241
|
+
listOrNone(session, 'resources/list', 'resources'),
|
|
242
|
+
]),
|
|
243
|
+
giveUp,
|
|
244
|
+
]));
|
|
245
|
+
return {
|
|
246
|
+
server: spec,
|
|
247
|
+
title: name ? (version ? `${name} ${version}` : name) : undefined,
|
|
248
|
+
tools: toTools(tools),
|
|
249
|
+
prompts: prompts,
|
|
250
|
+
resources: resources,
|
|
251
|
+
ms: Date.now() - started,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
return { ...empty, ms: Date.now() - started, error: e instanceof Error ? e.message : String(e) };
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
clearTimeout(expiry);
|
|
259
|
+
controller.abort();
|
|
260
|
+
session.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Every server, a few at a time.
|
|
265
|
+
*
|
|
266
|
+
* Each one is a process to spawn, so they are not all started at once; four in
|
|
267
|
+
* flight keeps a machine with a dozen servers responsive and still finishes in
|
|
268
|
+
* about the time the slowest handful take.
|
|
269
|
+
*/
|
|
270
|
+
export async function inspectAll(specs, options = {}) {
|
|
271
|
+
const lanes = Math.max(1, options.lanes ?? 4);
|
|
272
|
+
const out = new Array(specs.length);
|
|
273
|
+
let next = 0;
|
|
274
|
+
const worker = async () => {
|
|
275
|
+
for (;;) {
|
|
276
|
+
const index = next++;
|
|
277
|
+
const spec = specs[index];
|
|
278
|
+
if (!spec)
|
|
279
|
+
return;
|
|
280
|
+
const inv = await inspect(spec, options.timeout);
|
|
281
|
+
out[index] = inv;
|
|
282
|
+
options.onDone?.(inv);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
await Promise.all(Array.from({ length: Math.min(lanes, specs.length) }, worker));
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report, as lines of text. [PURE]
|
|
3
|
+
*
|
|
4
|
+
* One table, then the sentence the table is for. The table is the evidence and
|
|
5
|
+
* the sentence is the point: a number nobody reads changes nothing, and what
|
|
6
|
+
* this has to say is short enough to fit in one — *every request carries this
|
|
7
|
+
* much, and this much of it has never been used*.
|
|
8
|
+
*
|
|
9
|
+
* Lines rather than console calls, so the same report can be printed, written
|
|
10
|
+
* to a file or asserted on in a test.
|
|
11
|
+
*/
|
|
12
|
+
import type { ServerSpec, WeighedServer, WeighedTool } from './types.ts';
|
|
13
|
+
/** The context window a share is measured against, when none is given. */
|
|
14
|
+
export declare const DEFAULT_WINDOW = 200000;
|
|
15
|
+
/** `412`, `12.4k`, `1.2M` — a token count that fits a column. */
|
|
16
|
+
export declare function compact(n: number): string;
|
|
17
|
+
/** A path with the home directory put back as `~`, so a line fits a terminal. */
|
|
18
|
+
export declare function shorten(path: string, home?: string): string;
|
|
19
|
+
/** Where a server came from, in as few words as the reader needs. */
|
|
20
|
+
export declare function origin(spec: ServerSpec): string;
|
|
21
|
+
/**
|
|
22
|
+
* What is being paid for and not used, split two ways.
|
|
23
|
+
*
|
|
24
|
+
* A server where nothing at all has been called is one decision — switch the
|
|
25
|
+
* server off — and naming its forty tools one by one buries that decision in a
|
|
26
|
+
* list. A server that is half used is the opposite: the server stays, and the
|
|
27
|
+
* only thing worth printing is which of its tools are dead weight. So whole
|
|
28
|
+
* dead servers are reported as servers, and loose tools only from the servers
|
|
29
|
+
* that survive.
|
|
30
|
+
*/
|
|
31
|
+
export declare function idleness(servers: readonly WeighedServer[]): {
|
|
32
|
+
servers: WeighedServer[];
|
|
33
|
+
tools: WeighedTool[];
|
|
34
|
+
tokens: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The whole report.
|
|
38
|
+
*
|
|
39
|
+
* `exact` is carried through to the wording rather than hidden: a figure that
|
|
40
|
+
* came from a heuristic and one that came from the API's own counter should
|
|
41
|
+
* not read identically.
|
|
42
|
+
*/
|
|
43
|
+
export declare function report(servers: readonly WeighedServer[], options?: {
|
|
44
|
+
window?: number;
|
|
45
|
+
days?: number | null;
|
|
46
|
+
exact?: boolean;
|
|
47
|
+
home?: string;
|
|
48
|
+
}): string[];
|
|
49
|
+
/** Every tool of every server, heaviest first — the long form, behind a flag. */
|
|
50
|
+
export declare function breakdown(servers: readonly WeighedServer[]): string[];
|
|
51
|
+
/** The same figures, for a script rather than a person. */
|
|
52
|
+
export declare function asJson(servers: readonly WeighedServer[], window: number, exact: boolean): unknown;
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report, as lines of text. [PURE]
|
|
3
|
+
*
|
|
4
|
+
* One table, then the sentence the table is for. The table is the evidence and
|
|
5
|
+
* the sentence is the point: a number nobody reads changes nothing, and what
|
|
6
|
+
* this has to say is short enough to fit in one — *every request carries this
|
|
7
|
+
* much, and this much of it has never been used*.
|
|
8
|
+
*
|
|
9
|
+
* Lines rather than console calls, so the same report can be printed, written
|
|
10
|
+
* to a file or asserted on in a test.
|
|
11
|
+
*/
|
|
12
|
+
import { totalTokens } from "./weigh.js";
|
|
13
|
+
/** The context window a share is measured against, when none is given. */
|
|
14
|
+
export const DEFAULT_WINDOW = 200_000;
|
|
15
|
+
/** How many individual tools to name before the list stops earning its space. */
|
|
16
|
+
const TOOL_LIST = 8;
|
|
17
|
+
/** `412`, `12.4k`, `1.2M` — a token count that fits a column. */
|
|
18
|
+
export function compact(n) {
|
|
19
|
+
if (n < 1000)
|
|
20
|
+
return String(Math.round(n));
|
|
21
|
+
if (n < 100_000)
|
|
22
|
+
return `${Math.round(n / 100) / 10}k`;
|
|
23
|
+
if (n < 1_000_000)
|
|
24
|
+
return `${Math.round(n / 1000)}k`;
|
|
25
|
+
return `${Math.round(n / 100_000) / 10}M`;
|
|
26
|
+
}
|
|
27
|
+
const share = (n, window) => `${((n / window) * 100).toFixed(1)}%`;
|
|
28
|
+
/** A path with the home directory put back as `~`, so a line fits a terminal. */
|
|
29
|
+
export function shorten(path, home) {
|
|
30
|
+
if (!home)
|
|
31
|
+
return path;
|
|
32
|
+
const same = (a) => a.replace(/\\/g, '/').toLowerCase();
|
|
33
|
+
return same(path).startsWith(same(home)) ? `~${path.slice(home.length)}` : path;
|
|
34
|
+
}
|
|
35
|
+
/** Where a server came from, in as few words as the reader needs. */
|
|
36
|
+
export function origin(spec) {
|
|
37
|
+
const clients = spec.carriedBy.join(', ');
|
|
38
|
+
return spec.scope === 'project' ? `${clients} (project)` : clients;
|
|
39
|
+
}
|
|
40
|
+
const NAME_WIDTH = 20;
|
|
41
|
+
function row(s, window) {
|
|
42
|
+
const name = s.server.name.padEnd(NAME_WIDTH).slice(0, NAME_WIDTH);
|
|
43
|
+
if (s.error)
|
|
44
|
+
return ` ${name} ${'—'.padStart(5)} ${'—'.padStart(8)} ${'—'.padStart(7)} ${'—'.padStart(7)} ${s.error}`;
|
|
45
|
+
const used = s.tools.filter((t) => (t.calls ?? 0) > 0).length;
|
|
46
|
+
const known = s.tools.some((t) => t.calls !== null);
|
|
47
|
+
const usage = known ? `${used}/${s.tools.length}` : '—';
|
|
48
|
+
const idle = known && used === 0 && s.tools.length > 0 ? ' never called' : '';
|
|
49
|
+
const off = s.server.enabled ? '' : ' (switched off)';
|
|
50
|
+
return (` ${name}` +
|
|
51
|
+
` ${String(s.tools.length).padStart(5)}` +
|
|
52
|
+
` ${compact(s.tokens).padStart(8)}` +
|
|
53
|
+
` ${share(s.tokens, window).padStart(7)}` +
|
|
54
|
+
` ${usage.padStart(7)}` +
|
|
55
|
+
idle +
|
|
56
|
+
off);
|
|
57
|
+
}
|
|
58
|
+
const HEAD = ` ${'server'.padEnd(NAME_WIDTH)} ${'tools'.padStart(5)} ${'tokens'.padStart(8)} ${'of ctx'.padStart(7)} ${'used'.padStart(7)}`;
|
|
59
|
+
/**
|
|
60
|
+
* What is being paid for and not used, split two ways.
|
|
61
|
+
*
|
|
62
|
+
* A server where nothing at all has been called is one decision — switch the
|
|
63
|
+
* server off — and naming its forty tools one by one buries that decision in a
|
|
64
|
+
* list. A server that is half used is the opposite: the server stays, and the
|
|
65
|
+
* only thing worth printing is which of its tools are dead weight. So whole
|
|
66
|
+
* dead servers are reported as servers, and loose tools only from the servers
|
|
67
|
+
* that survive.
|
|
68
|
+
*/
|
|
69
|
+
export function idleness(servers) {
|
|
70
|
+
const dead = servers.filter((s) => !s.error && s.tools.length > 0 && s.tools.every((t) => t.calls === 0));
|
|
71
|
+
const deadNames = new Set(dead.map((s) => s.server.name));
|
|
72
|
+
const tools = servers
|
|
73
|
+
.filter((s) => !s.error && !deadNames.has(s.server.name))
|
|
74
|
+
.flatMap((s) => s.tools)
|
|
75
|
+
.filter((t) => t.calls === 0)
|
|
76
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
77
|
+
return {
|
|
78
|
+
servers: dead,
|
|
79
|
+
tools,
|
|
80
|
+
tokens: totalTokens(dead) + tools.reduce((n, t) => n + t.tokens, 0),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The whole report.
|
|
85
|
+
*
|
|
86
|
+
* `exact` is carried through to the wording rather than hidden: a figure that
|
|
87
|
+
* came from a heuristic and one that came from the API's own counter should
|
|
88
|
+
* not read identically.
|
|
89
|
+
*/
|
|
90
|
+
export function report(servers, options = {}) {
|
|
91
|
+
if (servers.length === 0)
|
|
92
|
+
return ['no MCP servers found in any config on this machine'];
|
|
93
|
+
const window = options.window ?? DEFAULT_WINDOW;
|
|
94
|
+
const live = servers.filter((s) => !s.error);
|
|
95
|
+
const total = totalTokens(live);
|
|
96
|
+
const toolCount = live.reduce((n, s) => n + s.tools.length, 0);
|
|
97
|
+
const lines = [HEAD, ''];
|
|
98
|
+
for (const s of servers)
|
|
99
|
+
lines.push(row(s, window));
|
|
100
|
+
lines.push('', ` ${'total'.padEnd(NAME_WIDTH)} ${String(toolCount).padStart(5)} ${compact(total).padStart(8)} ${share(total, window).padStart(7)}`, '', `every request carries ${compact(total)} tokens of tool definitions — ${share(total, window)} of a ${compact(window)} window,`, `before a line of your own code is read. (${options.exact ? 'counted by the API' : 'estimated — --exact counts'})`);
|
|
101
|
+
if (options.days) {
|
|
102
|
+
const idle = idleness(servers);
|
|
103
|
+
if (idle.tokens > 0) {
|
|
104
|
+
const portion = total > 0 ? Math.round((idle.tokens / total) * 100) : 100;
|
|
105
|
+
lines.push('', `${compact(idle.tokens)} of it — ${portion}% — has not been called in ${options.days} days.`);
|
|
106
|
+
}
|
|
107
|
+
if (idle.servers.length > 0) {
|
|
108
|
+
lines.push('', 'nothing at all has been called from:', '');
|
|
109
|
+
for (const s of idle.servers) {
|
|
110
|
+
lines.push(` ${s.server.name.padEnd(NAME_WIDTH)} ${compact(s.tokens).padStart(8)} ${shorten(s.server.source, options.home)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (idle.tools.length > 0) {
|
|
114
|
+
lines.push('', 'and these tools, in servers you do use:', '');
|
|
115
|
+
for (const t of idle.tools.slice(0, TOOL_LIST)) {
|
|
116
|
+
lines.push(` ${t.qualified.padEnd(44).slice(0, 44)} ${compact(t.tokens).padStart(7)}`);
|
|
117
|
+
}
|
|
118
|
+
if (idle.tools.length > TOOL_LIST)
|
|
119
|
+
lines.push(` … and ${idle.tools.length - TOOL_LIST} more`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const failed = servers.filter((s) => s.error);
|
|
123
|
+
if (failed.length > 0) {
|
|
124
|
+
const s = failed.length === 1;
|
|
125
|
+
lines.push('', `${failed.length} server${s ? '' : 's'} could not be reached and ${s ? 'is' : 'are'} not in the total.`);
|
|
126
|
+
}
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
|
129
|
+
/** Every tool of every server, heaviest first — the long form, behind a flag. */
|
|
130
|
+
export function breakdown(servers) {
|
|
131
|
+
const lines = [];
|
|
132
|
+
for (const s of servers) {
|
|
133
|
+
if (s.error)
|
|
134
|
+
continue;
|
|
135
|
+
lines.push('', `${s.server.name}${s.title ? ` (${s.title})` : ''} · ${compact(s.tokens)} · ${origin(s.server)}`);
|
|
136
|
+
for (const t of s.tools) {
|
|
137
|
+
const calls = t.calls === null ? '' : t.calls === 0 ? ' never called' : ` ${t.calls} call${t.calls === 1 ? '' : 's'}`;
|
|
138
|
+
lines.push(` ${t.name.padEnd(40).slice(0, 40)} ${compact(t.tokens).padStart(7)}${calls}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
/** The same figures, for a script rather than a person. */
|
|
144
|
+
export function asJson(servers, window, exact) {
|
|
145
|
+
return {
|
|
146
|
+
window,
|
|
147
|
+
exact,
|
|
148
|
+
total: totalTokens(servers.filter((s) => !s.error)),
|
|
149
|
+
servers: servers.map((s) => ({
|
|
150
|
+
name: s.server.name,
|
|
151
|
+
client: s.server.client,
|
|
152
|
+
carriedBy: s.server.carriedBy,
|
|
153
|
+
source: s.server.source,
|
|
154
|
+
transport: s.server.transport,
|
|
155
|
+
enabled: s.server.enabled,
|
|
156
|
+
title: s.title,
|
|
157
|
+
tokens: s.tokens,
|
|
158
|
+
tools: s.tools,
|
|
159
|
+
prompts: s.prompts,
|
|
160
|
+
resources: s.resources,
|
|
161
|
+
ms: s.ms,
|
|
162
|
+
error: s.error,
|
|
163
|
+
})),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many tokens a piece of text is worth. [PURE]
|
|
3
|
+
*
|
|
4
|
+
* There is no public tokenizer for Claude, so this is an estimate and says so
|
|
5
|
+
* everywhere it is shown. It is not `length / 4`: tool definitions are JSON,
|
|
6
|
+
* and JSON is mostly punctuation, indentation and short identifiers, which
|
|
7
|
+
* that ratio gets wrong in both directions at once — it under-counts braces
|
|
8
|
+
* and over-counts indentation.
|
|
9
|
+
*
|
|
10
|
+
* Instead the text is cut into runs of one kind of character and each run is
|
|
11
|
+
* priced the way a byte-pair tokenizer is known to treat it: words in pieces
|
|
12
|
+
* of about five characters, digits in threes, adjacent punctuation paired up,
|
|
13
|
+
* a leading space absorbed into the word after it.
|
|
14
|
+
*
|
|
15
|
+
* That is a model of a tokenizer, not the tokenizer, and it has not been
|
|
16
|
+
* calibrated against the real one — so it is called an estimate everywhere it
|
|
17
|
+
* is shown, and it is good for the question it is for: which of these servers
|
|
18
|
+
* is the big one. When the figure itself has to be right, `--exact` asks the
|
|
19
|
+
* API (see `exact.ts`) and nothing in this file is used.
|
|
20
|
+
*/
|
|
21
|
+
/** The estimate, for any text. */
|
|
22
|
+
export declare function estimate(text: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* What a client adds around each tool definition it sends.
|
|
25
|
+
*
|
|
26
|
+
* The name and schema are counted from the text itself; this is the framing
|
|
27
|
+
* that does not appear in the JSON — the wrapper the API puts each tool in.
|
|
28
|
+
* It is small and flat, and it is here as a named constant rather than buried
|
|
29
|
+
* in an expression so that it can be argued with.
|
|
30
|
+
*/
|
|
31
|
+
export declare const TOOL_FRAMING = 8;
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many tokens a piece of text is worth. [PURE]
|
|
3
|
+
*
|
|
4
|
+
* There is no public tokenizer for Claude, so this is an estimate and says so
|
|
5
|
+
* everywhere it is shown. It is not `length / 4`: tool definitions are JSON,
|
|
6
|
+
* and JSON is mostly punctuation, indentation and short identifiers, which
|
|
7
|
+
* that ratio gets wrong in both directions at once — it under-counts braces
|
|
8
|
+
* and over-counts indentation.
|
|
9
|
+
*
|
|
10
|
+
* Instead the text is cut into runs of one kind of character and each run is
|
|
11
|
+
* priced the way a byte-pair tokenizer is known to treat it: words in pieces
|
|
12
|
+
* of about five characters, digits in threes, adjacent punctuation paired up,
|
|
13
|
+
* a leading space absorbed into the word after it.
|
|
14
|
+
*
|
|
15
|
+
* That is a model of a tokenizer, not the tokenizer, and it has not been
|
|
16
|
+
* calibrated against the real one — so it is called an estimate everywhere it
|
|
17
|
+
* is shown, and it is good for the question it is for: which of these servers
|
|
18
|
+
* is the big one. When the figure itself has to be right, `--exact` asks the
|
|
19
|
+
* API (see `exact.ts`) and nothing in this file is used.
|
|
20
|
+
*/
|
|
21
|
+
/** Letters, digits, whitespace, everything else — each in runs. */
|
|
22
|
+
const RUNS = /[A-Za-z]+|[0-9]+|\s+|[^\sA-Za-z0-9]+/g;
|
|
23
|
+
/**
|
|
24
|
+
* One run's worth of tokens.
|
|
25
|
+
*
|
|
26
|
+
* - Letters: common words and identifiers come out around five characters a
|
|
27
|
+
* token once `camelCase` boundaries and word-piece splits are averaged in.
|
|
28
|
+
* - Digits: tokenizers group them in threes.
|
|
29
|
+
* - Whitespace: a lone space is absorbed into the word that follows it and is
|
|
30
|
+
* free; a newline is its own token, and the indentation after it packs into
|
|
31
|
+
* roughly four characters a token.
|
|
32
|
+
* - Punctuation: adjacent marks pair up — `{"`, `":`, `",` are each one token —
|
|
33
|
+
* so a run costs about half its length, never less than one.
|
|
34
|
+
*/
|
|
35
|
+
function runTokens(run) {
|
|
36
|
+
const first = run[0];
|
|
37
|
+
if (/\s/.test(first)) {
|
|
38
|
+
const newlines = (run.match(/\n/g) ?? []).length;
|
|
39
|
+
if (newlines === 0)
|
|
40
|
+
return run.length === 1 ? 0 : Math.ceil(run.length / 4);
|
|
41
|
+
return newlines + Math.ceil((run.length - newlines) / 4);
|
|
42
|
+
}
|
|
43
|
+
if (first >= '0' && first <= '9')
|
|
44
|
+
return Math.ceil(run.length / 3);
|
|
45
|
+
if (/[A-Za-z]/.test(first))
|
|
46
|
+
return Math.max(1, Math.round(run.length / 5));
|
|
47
|
+
return Math.max(1, Math.ceil(run.length / 2));
|
|
48
|
+
}
|
|
49
|
+
/** The estimate, for any text. */
|
|
50
|
+
export function estimate(text) {
|
|
51
|
+
if (!text)
|
|
52
|
+
return 0;
|
|
53
|
+
let total = 0;
|
|
54
|
+
for (const run of text.match(RUNS) ?? [])
|
|
55
|
+
total += runTokens(run);
|
|
56
|
+
return total;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* What a client adds around each tool definition it sends.
|
|
60
|
+
*
|
|
61
|
+
* The name and schema are counted from the text itself; this is the framing
|
|
62
|
+
* that does not appear in the JSON — the wrapper the API puts each tool in.
|
|
63
|
+
* It is small and flat, and it is here as a named constant rather than buried
|
|
64
|
+
* in an expression so that it can be argued with.
|
|
65
|
+
*/
|
|
66
|
+
export const TOOL_FRAMING = 8;
|
|
67
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,mEAAmE;AACnE,MAAM,IAAI,GAAG,uCAAuC,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;IAE/B,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACjD,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC5E,OAAO,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnE,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC"}
|
package/dist/toml.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Just enough TOML to read Codex's config. [PURE]
|
|
3
|
+
*
|
|
4
|
+
* Codex keeps its servers in `~/.codex/config.toml`, and a dependency would be
|
|
5
|
+
* a poor trade for the handful of shapes that file actually uses: table
|
|
6
|
+
* headers, strings, integers, booleans, string arrays and inline tables. What
|
|
7
|
+
* this cannot parse it skips rather than guesses at, and an unreadable file
|
|
8
|
+
* costs one client, not the run.
|
|
9
|
+
*/
|
|
10
|
+
/** The whole file, as nested plain objects. Arrays of tables are not supported. */
|
|
11
|
+
export declare function parseToml(text: string): Record<string, unknown>;
|