tare-mcp 0.2.0 → 0.3.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/README.md +142 -49
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +20 -8
- package/dist/index.d.ts +425 -0
- package/dist/index.js +136 -8
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,42 +1,54 @@
|
|
|
1
1
|
# tare-mcp
|
|
2
2
|
|
|
3
|
-
[](package.json)
|
|
3
|
+
[](https://github.com/nishantmodak/tare-mcp/actions/workflows/ci.yml)
|
|
5
4
|
[](https://www.npmjs.com/package/tare-mcp)
|
|
5
|
+
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Measure the MCP tool surface your agent is about to send to a model.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
pnpm build
|
|
12
|
-
node dist/cli.js
|
|
10
|
+
npm install tare-mcp
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
```ts
|
|
14
|
+
import { measureTools } from "tare-mcp";
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
const tools = await mcpClient.listTools();
|
|
17
|
+
const report = await measureTools(tools, { budget: 40_000 });
|
|
18
|
+
|
|
19
|
+
console.log(
|
|
20
|
+
`MCP tool surface: ${report.summary.tools} tools, ~${report.summary.estimatedTokens.claude} Claude tokens`
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (report.metadata.budgetExceeded) {
|
|
24
|
+
throw new Error("MCP tool surface exceeds budget");
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
MCP made tools easy to connect. It did not make them cheap to carry.
|
|
29
|
+
|
|
30
|
+
`tare-mcp` shows, from inside your agent or from the CLI:
|
|
19
31
|
|
|
20
32
|
- how many tools your agent sees
|
|
21
|
-
- how much context those tools consume
|
|
33
|
+
- how much context those tools consume, estimated for Claude and OpenAI cl100k
|
|
22
34
|
- which servers dominate the budget
|
|
23
35
|
- which tools overlap and compete for model attention
|
|
24
36
|
- whether your setup exceeds a context budget
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
Use the CLI when you want to inspect config-discovered MCP servers locally:
|
|
27
39
|
|
|
28
40
|
```bash
|
|
29
|
-
|
|
41
|
+
npx tare-mcp
|
|
30
42
|
```
|
|
31
43
|
|
|
32
|
-
but for agent tool context.
|
|
44
|
+
Think of it as `du -sh node_modules`, but for agent tool context.
|
|
33
45
|
|
|
34
46
|
## Table of Contents
|
|
35
47
|
|
|
36
48
|
- [Why This Matters](#why-this-matters)
|
|
37
49
|
- [Why Token Count Is Not the Whole Problem](#why-token-count-is-not-the-whole-problem)
|
|
38
|
-
- [
|
|
39
|
-
- [Quickstart](#quickstart)
|
|
50
|
+
- [Using tare-mcp in your agent](#using-tare-mcp-in-your-agent)
|
|
51
|
+
- [CLI Quickstart](#cli-quickstart)
|
|
40
52
|
- [Hosted MCP Quickstart](#hosted-mcp-quickstart)
|
|
41
53
|
- [Scenario Examples](#scenario-examples)
|
|
42
54
|
- [Example Output](#example-output)
|
|
@@ -74,25 +86,82 @@ If three servers all expose tools that look like "search", the model has to choo
|
|
|
74
86
|
- what your tools weigh
|
|
75
87
|
- where your tools overlap
|
|
76
88
|
|
|
77
|
-
##
|
|
89
|
+
## Using tare-mcp in your agent
|
|
78
90
|
|
|
79
|
-
|
|
91
|
+
Production agents often do not have a stable `.mcp.json` file to inspect. They connect to MCP servers, call `tools/list`, and pass those tool definitions to the model on each request.
|
|
80
92
|
|
|
81
|
-
|
|
93
|
+
Use `measureTools()` when you already have the tool definitions in memory:
|
|
82
94
|
|
|
83
|
-
|
|
95
|
+
```ts
|
|
96
|
+
import { measureTools } from "tare-mcp";
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
const tools = await mcpClient.listTools();
|
|
99
|
+
const report = await measureTools(tools);
|
|
86
100
|
|
|
87
|
-
|
|
101
|
+
console.log(
|
|
102
|
+
`MCP tool surface: ${report.summary.tools} tools, ~${report.summary.estimatedTokens.claude} Claude tokens`
|
|
103
|
+
);
|
|
104
|
+
```
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
For multiple MCP servers, add `server` per tool so overlap warnings and per-server totals remain useful:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { measureTools } from "tare-mcp";
|
|
110
|
+
|
|
111
|
+
const last9Tools = await last9Client.listTools();
|
|
112
|
+
const githubTools = await githubClient.listTools();
|
|
113
|
+
|
|
114
|
+
const report = await measureTools([
|
|
115
|
+
...last9Tools.map((tool) => ({ ...tool, server: "last9" })),
|
|
116
|
+
...githubTools.map((tool) => ({ ...tool, server: "github" }))
|
|
117
|
+
]);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For unattributed tools, pass a fallback server name:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const report = await measureTools(tools, {
|
|
124
|
+
serverName: "agent"
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Budget checks are metadata, not exceptions. That keeps the library easy to use in request paths, logs, and CI:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const report = await measureTools(tools, { budget: 40_000 });
|
|
132
|
+
|
|
133
|
+
if (report.metadata.budgetExceeded) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`MCP tool surface exceeds budget: ~${report.summary.estimatedTokens.claude} Claude tokens`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Structured logging example:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
logger.info("mcp.tool_surface", {
|
|
144
|
+
tools: report.summary.tools,
|
|
145
|
+
servers: report.summary.servers,
|
|
146
|
+
tokens_claude: report.summary.estimatedTokens.claude,
|
|
147
|
+
tokens_openai_cl100k: report.summary.estimatedTokens.openaiCl100k,
|
|
148
|
+
overlap_clusters: report.overlapClusters.length,
|
|
149
|
+
budget_exceeded: report.metadata.budgetExceeded ?? false
|
|
150
|
+
});
|
|
93
151
|
```
|
|
94
152
|
|
|
95
|
-
|
|
153
|
+
The programmatic API is local-first. It does not read config files, spawn MCP servers, or call cloud tokenization APIs by default. API-backed Claude token counting is opt-in:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const report = await measureTools(tools, {
|
|
157
|
+
claudeTokenizerMode: "api",
|
|
158
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## CLI Quickstart
|
|
163
|
+
|
|
164
|
+
Run it without installing:
|
|
96
165
|
|
|
97
166
|
```bash
|
|
98
167
|
npx tare-mcp
|
|
@@ -120,26 +189,20 @@ Context window usage:
|
|
|
120
189
|
|
|
121
190
|
If the output is empty or shows "Config files found: 0", see [Config discovery](#config-discovery).
|
|
122
191
|
|
|
123
|
-
Install it in a project
|
|
192
|
+
Install it in a project:
|
|
124
193
|
|
|
125
194
|
```bash
|
|
126
195
|
npm install --save-dev tare-mcp
|
|
127
196
|
npx tare-mcp
|
|
128
197
|
```
|
|
129
198
|
|
|
130
|
-
Install it globally
|
|
199
|
+
Install it globally:
|
|
131
200
|
|
|
132
201
|
```bash
|
|
133
202
|
npm install --global tare-mcp
|
|
134
203
|
tare-mcp
|
|
135
204
|
```
|
|
136
205
|
|
|
137
|
-
For local development, keep using the source command:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
pnpm dev
|
|
141
|
-
```
|
|
142
|
-
|
|
143
206
|
Static-only mode parses config without starting servers or calling hosted endpoints:
|
|
144
207
|
|
|
145
208
|
```bash
|
|
@@ -159,6 +222,14 @@ Emit JSON for CI or other tools:
|
|
|
159
222
|
npx tare-mcp --json
|
|
160
223
|
```
|
|
161
224
|
|
|
225
|
+
For local development from this repository:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pnpm install
|
|
229
|
+
pnpm build
|
|
230
|
+
pnpm dev
|
|
231
|
+
```
|
|
232
|
+
|
|
162
233
|
## Hosted MCP Quickstart
|
|
163
234
|
|
|
164
235
|
Use this when you want to inspect a real hosted MCP endpoint.
|
|
@@ -166,7 +237,19 @@ Use this when you want to inspect a real hosted MCP endpoint.
|
|
|
166
237
|
```bash
|
|
167
238
|
mkdir -p /tmp/tare-mcp-hosted
|
|
168
239
|
cd /tmp/tare-mcp-hosted
|
|
169
|
-
|
|
240
|
+
cat > .mcp.json <<'JSON'
|
|
241
|
+
{
|
|
242
|
+
"mcpServers": {
|
|
243
|
+
"last9": {
|
|
244
|
+
"type": "http",
|
|
245
|
+
"url": "https://mcp.last9.io/mcp",
|
|
246
|
+
"headers": {
|
|
247
|
+
"Authorization": "Bearer ${LAST9_MCP_TOKEN}"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
JSON
|
|
170
253
|
export LAST9_MCP_TOKEN="..."
|
|
171
254
|
npx tare-mcp --timeout 10000
|
|
172
255
|
```
|
|
@@ -302,10 +385,11 @@ Recommendations:
|
|
|
302
385
|
|
|
303
386
|
## Supported transports
|
|
304
387
|
|
|
305
|
-
v0.
|
|
388
|
+
v0.3 supports:
|
|
306
389
|
|
|
307
390
|
- stdio MCP servers
|
|
308
391
|
- Streamable HTTP MCP servers
|
|
392
|
+
- programmatic tool definitions through `measureTools()`
|
|
309
393
|
|
|
310
394
|
SSE may be supported best-effort later.
|
|
311
395
|
|
|
@@ -345,12 +429,12 @@ That mode requires `ANTHROPIC_API_KEY` and uses Anthropic's `POST /v1/messages/c
|
|
|
345
429
|
|
|
346
430
|
Environment variables that control tokenization:
|
|
347
431
|
|
|
348
|
-
| Variable
|
|
349
|
-
|
|
350
|
-
| `ANTHROPIC_API_KEY`
|
|
351
|
-
| `TARE_CLAUDE_TOKENIZER`
|
|
352
|
-
| `TARE_ANTHROPIC_MODEL`
|
|
353
|
-
| `TARE_DISABLE_ANTHROPIC_TOKEN_API` | `1`
|
|
432
|
+
| Variable | Values | Default | Description |
|
|
433
|
+
| ---------------------------------- | -------------- | ------------------- | ----------------------------------------------- |
|
|
434
|
+
| `ANTHROPIC_API_KEY` | string | — | Required for `--claude-tokenizer api` |
|
|
435
|
+
| `TARE_CLAUDE_TOKENIZER` | `local`, `api` | `local` | Override `--claude-tokenizer` via env |
|
|
436
|
+
| `TARE_ANTHROPIC_MODEL` | model ID | `claude-sonnet-4-6` | Model used for API-backed token counting |
|
|
437
|
+
| `TARE_DISABLE_ANTHROPIC_TOKEN_API` | `1` | unset | Disable API-backed counting even when requested |
|
|
354
438
|
|
|
355
439
|
## Security model
|
|
356
440
|
|
|
@@ -500,11 +584,11 @@ When a growth is intentional, regenerate `.tare/baseline.json` on the accepted s
|
|
|
500
584
|
|
|
501
585
|
Exit codes:
|
|
502
586
|
|
|
503
|
-
| Code | Meaning
|
|
504
|
-
|
|
505
|
-
|
|
|
506
|
-
|
|
|
507
|
-
|
|
|
587
|
+
| Code | Meaning |
|
|
588
|
+
| ---: | ------------------------------------------------------------------------------------------------ |
|
|
589
|
+
| `0` | Diff completed and thresholds passed, or no thresholds were set. |
|
|
590
|
+
| `1` | A configured regression threshold was exceeded. |
|
|
591
|
+
| `2` | Invalid usage or invalid input, including missing files, invalid JSON, or missing report fields. |
|
|
508
592
|
|
|
509
593
|
Estimates are still estimates. The value of the baseline workflow is consistency: the same tool estimates are compared over time, so accidental MCP bloat becomes visible during review.
|
|
510
594
|
|
|
@@ -567,6 +651,7 @@ npx tare-mcp --no-exec --json
|
|
|
567
651
|
## Publishing to npm
|
|
568
652
|
|
|
569
653
|
This repository includes [`.github/workflows/publish-npm.yml`](.github/workflows/publish-npm.yml).
|
|
654
|
+
Maintainers should use the [release checklist](https://github.com/nishantmodak/tare-mcp/blob/main/docs/releasing.md).
|
|
570
655
|
|
|
571
656
|
To publish from GitHub Actions:
|
|
572
657
|
|
|
@@ -587,7 +672,7 @@ npm publish --access public --provenance
|
|
|
587
672
|
|
|
588
673
|
The npm package is named `tare-mcp` because the unscoped `tare` package name is already occupied on npm.
|
|
589
674
|
|
|
590
|
-
|
|
675
|
+
Users can install it with:
|
|
591
676
|
|
|
592
677
|
```bash
|
|
593
678
|
npm install --save-dev tare-mcp
|
|
@@ -644,15 +729,23 @@ Options:
|
|
|
644
729
|
|
|
645
730
|
## Roadmap
|
|
646
731
|
|
|
732
|
+
v0.3:
|
|
733
|
+
|
|
734
|
+
- [x] Programmatic API for running agents through `measureTools()`
|
|
735
|
+
- [x] Programmatic JSON reports compatible with `tare-mcp diff`
|
|
736
|
+
|
|
647
737
|
v0.2:
|
|
738
|
+
|
|
648
739
|
- [x] PR diff/regression mode for JSON reports
|
|
649
740
|
- [x] Threshold flags for token, tool, server, and overlap growth
|
|
650
741
|
|
|
651
742
|
Next:
|
|
743
|
+
|
|
652
744
|
- [ ] Per-tool schema breakdown
|
|
653
745
|
- [ ] Context budget config file (`tare.config.json`)
|
|
654
746
|
|
|
655
747
|
Later:
|
|
748
|
+
|
|
656
749
|
- [ ] Better SSE fallback
|
|
657
750
|
- [ ] Improved Claude local token estimator
|
|
658
751
|
- [ ] Opt-in API-backed token counting improvements
|
|
@@ -661,7 +754,7 @@ Later:
|
|
|
661
754
|
- [ ] MCP profile generator
|
|
662
755
|
- [ ] `tare-mcp --fix` to generate lean MCP profiles
|
|
663
756
|
|
|
664
|
-
Dashboards, profile generation, and auto-fix are intentionally not part of v0.
|
|
757
|
+
Dashboards, profile generation, and auto-fix are intentionally not part of v0.3.
|
|
665
758
|
|
|
666
759
|
## License
|
|
667
760
|
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { z as z4 } from "zod";
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
|
-
var VERSION = "0.
|
|
8
|
+
var VERSION = "0.3.0";
|
|
9
9
|
|
|
10
10
|
// src/utils/stableJson.ts
|
|
11
11
|
function stableValue(value) {
|
|
@@ -387,7 +387,7 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
387
387
|
estimatedTokens: analyzedTool.estimatedTokens,
|
|
388
388
|
hasInputSchema: analyzedTool.hasInputSchema
|
|
389
389
|
});
|
|
390
|
-
if (server.inspectionMode === "live") {
|
|
390
|
+
if (server.inspectionMode === "live" || server.inspectionMode === "programmatic") {
|
|
391
391
|
liveToolsForOverlap.push(analyzedTool);
|
|
392
392
|
}
|
|
393
393
|
}
|
|
@@ -444,7 +444,9 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
444
444
|
openaiCl100k: windowUsage(totalOpenAi, 2e5)
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
|
-
insufficientServers: analyzedServers.filter(
|
|
447
|
+
insufficientServers: analyzedServers.filter(
|
|
448
|
+
(server) => server.inspectionMode === "static-insufficient" || server.inspectionMode === "fallback-static-insufficient"
|
|
449
|
+
).length
|
|
448
450
|
},
|
|
449
451
|
servers: analyzedServers,
|
|
450
452
|
overlapClusters,
|
|
@@ -452,7 +454,7 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
452
454
|
warnings,
|
|
453
455
|
metadata: {
|
|
454
456
|
staticOnly: options.staticOnly,
|
|
455
|
-
inspectionMode: options.staticOnly ? "static-only" : "live default"
|
|
457
|
+
inspectionMode: options.inspectionMode ?? (options.staticOnly ? "static-only" : "live default")
|
|
456
458
|
}
|
|
457
459
|
};
|
|
458
460
|
report.recommendations = buildRecommendations(report);
|
|
@@ -996,13 +998,18 @@ var TareReportSchema = z3.object({
|
|
|
996
998
|
z3.object({
|
|
997
999
|
name: z3.string(),
|
|
998
1000
|
sourceConfigPath: z3.string(),
|
|
999
|
-
transport: z3.enum(["stdio", "streamable-http", "sse", "unknown"]),
|
|
1001
|
+
transport: z3.enum(["stdio", "streamable-http", "sse", "programmatic", "unknown"]),
|
|
1000
1002
|
command: z3.string().optional(),
|
|
1001
1003
|
args: z3.array(z3.string()).optional(),
|
|
1002
1004
|
urlHost: z3.string().optional(),
|
|
1003
1005
|
toolCount: z3.number(),
|
|
1004
1006
|
estimatedTokens: TokenTotalsSchema,
|
|
1005
|
-
inspectionMode: z3.enum([
|
|
1007
|
+
inspectionMode: z3.enum([
|
|
1008
|
+
"live",
|
|
1009
|
+
"programmatic",
|
|
1010
|
+
"static-insufficient",
|
|
1011
|
+
"fallback-static-insufficient"
|
|
1012
|
+
]),
|
|
1006
1013
|
confidence: z3.enum(["high", "medium", "low"]),
|
|
1007
1014
|
warnings: z3.array(z3.string()),
|
|
1008
1015
|
tools: z3.array(
|
|
@@ -1025,7 +1032,10 @@ var TareReportSchema = z3.object({
|
|
|
1025
1032
|
warnings: z3.array(z3.string()),
|
|
1026
1033
|
metadata: z3.object({
|
|
1027
1034
|
staticOnly: z3.boolean(),
|
|
1028
|
-
inspectionMode: z3.enum(["live default", "static-only"])
|
|
1035
|
+
inspectionMode: z3.enum(["live default", "static-only", "programmatic"]),
|
|
1036
|
+
budgetExceeded: z3.boolean().optional(),
|
|
1037
|
+
budgetTokens: z3.number().optional(),
|
|
1038
|
+
budgetTokenizer: z3.literal("claude").optional()
|
|
1029
1039
|
}).passthrough()
|
|
1030
1040
|
}).passthrough();
|
|
1031
1041
|
var ReportLoadError = class extends Error {
|
|
@@ -1908,7 +1918,9 @@ function renderHumanReport(report) {
|
|
|
1908
1918
|
}
|
|
1909
1919
|
}
|
|
1910
1920
|
}
|
|
1911
|
-
const insufficientServers = report.servers.filter(
|
|
1921
|
+
const insufficientServers = report.servers.filter(
|
|
1922
|
+
(server) => server.inspectionMode === "static-insufficient" || server.inspectionMode === "fallback-static-insufficient"
|
|
1923
|
+
);
|
|
1912
1924
|
if (insufficientServers.length > 0) {
|
|
1913
1925
|
lines.push("");
|
|
1914
1926
|
lines.push("Insufficient data:");
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const VERSION = "0.3.0";
|
|
4
|
+
|
|
5
|
+
type TransportKind = "stdio" | "streamable-http" | "http" | "sse" | "unknown";
|
|
6
|
+
type ReportTransportKind = "stdio" | "streamable-http" | "sse" | "programmatic" | "unknown";
|
|
7
|
+
type InspectionMode = "live" | "programmatic" | "static-insufficient" | "fallback-static-insufficient";
|
|
8
|
+
type Confidence = "high" | "medium" | "low";
|
|
9
|
+
type NormalizedServer = {
|
|
10
|
+
name: string;
|
|
11
|
+
command?: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
env?: Record<string, string>;
|
|
14
|
+
url?: string;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
sourceConfigPath: string;
|
|
18
|
+
transport?: TransportKind;
|
|
19
|
+
};
|
|
20
|
+
type McpToolDefinition = {
|
|
21
|
+
name: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
inputSchema?: unknown;
|
|
24
|
+
annotations?: unknown;
|
|
25
|
+
outputSchema?: unknown;
|
|
26
|
+
metadata?: unknown;
|
|
27
|
+
};
|
|
28
|
+
type InspectedServer = {
|
|
29
|
+
name: string;
|
|
30
|
+
sourceConfigPath: string;
|
|
31
|
+
transport: ReportTransportKind;
|
|
32
|
+
command?: string;
|
|
33
|
+
args?: string[];
|
|
34
|
+
urlHost?: string;
|
|
35
|
+
toolDefinitions: McpToolDefinition[];
|
|
36
|
+
inspectionMode: InspectionMode;
|
|
37
|
+
confidence: Confidence;
|
|
38
|
+
warnings: string[];
|
|
39
|
+
};
|
|
40
|
+
type InspectorOptions = {
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
fetch?: typeof globalThis.fetch;
|
|
43
|
+
};
|
|
44
|
+
type ToolContextPayload = {
|
|
45
|
+
server: string;
|
|
46
|
+
transport: ReportTransportKind;
|
|
47
|
+
tool: {
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
inputSchema?: unknown;
|
|
51
|
+
annotations?: unknown;
|
|
52
|
+
outputSchema?: unknown;
|
|
53
|
+
metadata?: unknown;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare function getDefaultConfigCandidates(cwd?: string, home?: string): string[];
|
|
58
|
+
type DiscoverConfigResult = {
|
|
59
|
+
paths: string[];
|
|
60
|
+
warnings: string[];
|
|
61
|
+
};
|
|
62
|
+
declare function discoverConfigs(cwd?: string, home?: string): Promise<DiscoverConfigResult>;
|
|
63
|
+
|
|
64
|
+
type ParsedConfig = {
|
|
65
|
+
path: string;
|
|
66
|
+
servers: NormalizedServer[];
|
|
67
|
+
warnings: string[];
|
|
68
|
+
};
|
|
69
|
+
declare function parseConfigText(text: string, sourceConfigPath: string): ParsedConfig;
|
|
70
|
+
declare function parseConfigFile(filePath: string): Promise<ParsedConfig>;
|
|
71
|
+
|
|
72
|
+
type NormalizeResult = {
|
|
73
|
+
server?: NormalizedServer;
|
|
74
|
+
warnings: string[];
|
|
75
|
+
};
|
|
76
|
+
declare function normalizeServer(name: string, rawConfig: unknown, sourceConfigPath: string): NormalizeResult;
|
|
77
|
+
|
|
78
|
+
declare function createStaticInspection(server: NormalizedServer, mode?: InspectionMode, warnings?: string[]): InspectedServer;
|
|
79
|
+
|
|
80
|
+
declare function buildServerEnv(server: NormalizedServer): Record<string, string>;
|
|
81
|
+
declare function inspectStdioServer(server: NormalizedServer, options: InspectorOptions): Promise<InspectedServer>;
|
|
82
|
+
|
|
83
|
+
declare function inspectStreamableHttpServer(server: NormalizedServer, options: InspectorOptions): Promise<InspectedServer>;
|
|
84
|
+
|
|
85
|
+
type TokenEstimate = {
|
|
86
|
+
tokenizer: "claude-estimate" | "claude-api" | "openai-cl100k" | "fallback-char-ratio";
|
|
87
|
+
tokens: number;
|
|
88
|
+
confidence: "high" | "medium" | "low";
|
|
89
|
+
warning?: string;
|
|
90
|
+
};
|
|
91
|
+
interface TokenCounter {
|
|
92
|
+
count(text: string): Promise<TokenEstimate>;
|
|
93
|
+
}
|
|
94
|
+
type DualTokenEstimate = {
|
|
95
|
+
claude: TokenEstimate;
|
|
96
|
+
openaiCl100k: TokenEstimate;
|
|
97
|
+
};
|
|
98
|
+
type ClaudeTokenizerMode = "local" | "api";
|
|
99
|
+
|
|
100
|
+
type CountTokensOptions = {
|
|
101
|
+
claudeTokenizerMode: ClaudeTokenizerMode;
|
|
102
|
+
anthropicApiKey?: string;
|
|
103
|
+
anthropicModel?: string;
|
|
104
|
+
anthropicDisabled?: boolean;
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
onWarning?: (warning: string) => void;
|
|
107
|
+
};
|
|
108
|
+
declare class TokenEstimator {
|
|
109
|
+
private readonly options;
|
|
110
|
+
private readonly cache;
|
|
111
|
+
private readonly openAiCounter;
|
|
112
|
+
private readonly anthropicQueue;
|
|
113
|
+
private anthropicApiUnavailable;
|
|
114
|
+
private emittedMissingKeyWarning;
|
|
115
|
+
private emittedDisabledWarning;
|
|
116
|
+
private emittedApiFailureWarning;
|
|
117
|
+
constructor(options: CountTokensOptions);
|
|
118
|
+
count(text: string): Promise<DualTokenEstimate>;
|
|
119
|
+
private countClaude;
|
|
120
|
+
private countClaudeWithApi;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type AnalyzedTool = {
|
|
124
|
+
server: string;
|
|
125
|
+
name: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
inputSchema?: unknown;
|
|
128
|
+
estimatedTokens: {
|
|
129
|
+
claude: number;
|
|
130
|
+
openaiCl100k: number;
|
|
131
|
+
};
|
|
132
|
+
hasInputSchema: boolean;
|
|
133
|
+
};
|
|
134
|
+
type OverlapCluster = {
|
|
135
|
+
label: string;
|
|
136
|
+
score: number;
|
|
137
|
+
reason: string;
|
|
138
|
+
signals: Array<"tfidf" | "intent-heuristic">;
|
|
139
|
+
tools: Array<{
|
|
140
|
+
server: string;
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
estimatedTokens?: {
|
|
144
|
+
claude?: number;
|
|
145
|
+
openaiCl100k?: number;
|
|
146
|
+
};
|
|
147
|
+
}>;
|
|
148
|
+
recommendation: string;
|
|
149
|
+
};
|
|
150
|
+
type TareReport = {
|
|
151
|
+
version: string;
|
|
152
|
+
generatedAt: string;
|
|
153
|
+
summary: {
|
|
154
|
+
configFiles: number;
|
|
155
|
+
servers: number;
|
|
156
|
+
tools: number;
|
|
157
|
+
estimatedTokens: {
|
|
158
|
+
claude: number;
|
|
159
|
+
openaiCl100k: number;
|
|
160
|
+
};
|
|
161
|
+
contextWindows: {
|
|
162
|
+
"64000": {
|
|
163
|
+
claude: number;
|
|
164
|
+
openaiCl100k: number;
|
|
165
|
+
};
|
|
166
|
+
"128000": {
|
|
167
|
+
claude: number;
|
|
168
|
+
openaiCl100k: number;
|
|
169
|
+
};
|
|
170
|
+
"200000": {
|
|
171
|
+
claude: number;
|
|
172
|
+
openaiCl100k: number;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
insufficientServers: number;
|
|
176
|
+
};
|
|
177
|
+
servers: Array<{
|
|
178
|
+
name: string;
|
|
179
|
+
sourceConfigPath: string;
|
|
180
|
+
transport: ReportTransportKind;
|
|
181
|
+
command?: string;
|
|
182
|
+
args?: string[];
|
|
183
|
+
urlHost?: string;
|
|
184
|
+
toolCount: number;
|
|
185
|
+
estimatedTokens: {
|
|
186
|
+
claude: number;
|
|
187
|
+
openaiCl100k: number;
|
|
188
|
+
};
|
|
189
|
+
inspectionMode: InspectionMode;
|
|
190
|
+
confidence: Confidence;
|
|
191
|
+
warnings: string[];
|
|
192
|
+
tools: Array<{
|
|
193
|
+
name: string;
|
|
194
|
+
description?: string;
|
|
195
|
+
estimatedTokens: {
|
|
196
|
+
claude: number;
|
|
197
|
+
openaiCl100k: number;
|
|
198
|
+
};
|
|
199
|
+
hasInputSchema: boolean;
|
|
200
|
+
}>;
|
|
201
|
+
}>;
|
|
202
|
+
overlapClusters: OverlapCluster[];
|
|
203
|
+
recommendations: Array<{
|
|
204
|
+
type: string;
|
|
205
|
+
message: string;
|
|
206
|
+
}>;
|
|
207
|
+
warnings: string[];
|
|
208
|
+
metadata: {
|
|
209
|
+
staticOnly: boolean;
|
|
210
|
+
inspectionMode: "live default" | "static-only" | "programmatic";
|
|
211
|
+
budgetExceeded?: boolean;
|
|
212
|
+
budgetTokens?: number;
|
|
213
|
+
budgetTokenizer?: "claude";
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
type AnalyzeOptions = {
|
|
218
|
+
configFiles: number;
|
|
219
|
+
staticOnly: boolean;
|
|
220
|
+
warnings?: string[];
|
|
221
|
+
inspectionMode?: "live default" | "static-only" | "programmatic";
|
|
222
|
+
};
|
|
223
|
+
declare function analyzeServers(inspectedServers: InspectedServer[], tokenEstimator: TokenEstimator, options: AnalyzeOptions): Promise<TareReport>;
|
|
224
|
+
|
|
225
|
+
type McpToolInput = {
|
|
226
|
+
name: string;
|
|
227
|
+
description?: string;
|
|
228
|
+
inputSchema?: unknown;
|
|
229
|
+
annotations?: unknown;
|
|
230
|
+
outputSchema?: unknown;
|
|
231
|
+
metadata?: unknown;
|
|
232
|
+
};
|
|
233
|
+
type AttributedMcpToolInput = McpToolInput & {
|
|
234
|
+
server: string;
|
|
235
|
+
};
|
|
236
|
+
type MeasureToolsOptions = {
|
|
237
|
+
serverName?: string;
|
|
238
|
+
budget?: number;
|
|
239
|
+
claudeTokenizerMode?: ClaudeTokenizerMode;
|
|
240
|
+
anthropicApiKey?: string;
|
|
241
|
+
anthropicModel?: string;
|
|
242
|
+
timeoutMs?: number;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
declare function measureTools(tools: readonly (McpToolInput | AttributedMcpToolInput)[], options?: MeasureToolsOptions): Promise<TareReport>;
|
|
246
|
+
|
|
247
|
+
declare class OverlapDetector {
|
|
248
|
+
private readonly threshold;
|
|
249
|
+
constructor(threshold?: number);
|
|
250
|
+
detect(tools: AnalyzedTool[]): OverlapCluster[];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
declare function buildRecommendations(report: Pick<TareReport, "summary" | "overlapClusters" | "servers">): TareReport["recommendations"];
|
|
254
|
+
|
|
255
|
+
declare class OpenAICl100kCounter implements TokenCounter {
|
|
256
|
+
count(text: string): Promise<TokenEstimate>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
declare class LocalClaudeEstimator implements TokenCounter {
|
|
260
|
+
private readonly openAiCount?;
|
|
261
|
+
constructor(openAiCount?: (() => Promise<TokenEstimate>) | undefined);
|
|
262
|
+
count(text: string): Promise<TokenEstimate>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
type BudgetTokenizer = "claude" | "openai";
|
|
266
|
+
declare function renderHumanReport(report: TareReport): string;
|
|
267
|
+
declare function renderBudgetFailure(report: TareReport, budget: number, tokenizer: BudgetTokenizer): string;
|
|
268
|
+
|
|
269
|
+
declare function renderJsonReport(report: TareReport): string;
|
|
270
|
+
|
|
271
|
+
declare const TareReportSchema: z.ZodType<TareReport>;
|
|
272
|
+
type LoadedTareReport = {
|
|
273
|
+
path: string;
|
|
274
|
+
report: TareReport;
|
|
275
|
+
};
|
|
276
|
+
declare class ReportLoadError extends Error {
|
|
277
|
+
readonly path: string;
|
|
278
|
+
constructor(filePath: string, message: string);
|
|
279
|
+
}
|
|
280
|
+
declare function loadReport(filePath: string): Promise<LoadedTareReport>;
|
|
281
|
+
|
|
282
|
+
type DiffTokenizer = "claude" | "openai";
|
|
283
|
+
type DiffTokenTotals = {
|
|
284
|
+
claude: number;
|
|
285
|
+
openaiCl100k: number;
|
|
286
|
+
};
|
|
287
|
+
type NumericDelta = {
|
|
288
|
+
base: number;
|
|
289
|
+
head: number;
|
|
290
|
+
delta: number;
|
|
291
|
+
};
|
|
292
|
+
type TokenDelta = {
|
|
293
|
+
base: DiffTokenTotals;
|
|
294
|
+
head: DiffTokenTotals;
|
|
295
|
+
delta: DiffTokenTotals;
|
|
296
|
+
};
|
|
297
|
+
type ValueDelta<T> = {
|
|
298
|
+
base: T;
|
|
299
|
+
head: T;
|
|
300
|
+
changed: boolean;
|
|
301
|
+
};
|
|
302
|
+
type DiffServer = {
|
|
303
|
+
name: string;
|
|
304
|
+
sourceConfigPath: string;
|
|
305
|
+
transport: ReportTransportKind;
|
|
306
|
+
command?: string;
|
|
307
|
+
args?: string[];
|
|
308
|
+
urlHost?: string;
|
|
309
|
+
toolCount: number;
|
|
310
|
+
estimatedTokens: DiffTokenTotals;
|
|
311
|
+
inspectionMode: InspectionMode;
|
|
312
|
+
confidence: Confidence;
|
|
313
|
+
};
|
|
314
|
+
type DiffTool = {
|
|
315
|
+
server: string;
|
|
316
|
+
name: string;
|
|
317
|
+
description?: string;
|
|
318
|
+
estimatedTokens: DiffTokenTotals;
|
|
319
|
+
hasInputSchema: boolean;
|
|
320
|
+
};
|
|
321
|
+
type DiffServerChange = {
|
|
322
|
+
name: string;
|
|
323
|
+
toolCount: NumericDelta;
|
|
324
|
+
estimatedTokens: TokenDelta;
|
|
325
|
+
transport: ValueDelta<ReportTransportKind>;
|
|
326
|
+
sourceConfigPath: ValueDelta<string>;
|
|
327
|
+
command: ValueDelta<string | null>;
|
|
328
|
+
args: ValueDelta<string[] | null>;
|
|
329
|
+
urlHost: ValueDelta<string | null>;
|
|
330
|
+
inspectionMode: ValueDelta<InspectionMode>;
|
|
331
|
+
confidence: ValueDelta<Confidence>;
|
|
332
|
+
};
|
|
333
|
+
type DiffToolChange = {
|
|
334
|
+
server: string;
|
|
335
|
+
name: string;
|
|
336
|
+
estimatedTokens: TokenDelta;
|
|
337
|
+
descriptionChanged: boolean;
|
|
338
|
+
inputSchemaPresenceChanged: boolean;
|
|
339
|
+
};
|
|
340
|
+
type DiffOverlapCluster = {
|
|
341
|
+
id: string;
|
|
342
|
+
label: string;
|
|
343
|
+
score: number;
|
|
344
|
+
tools: Array<{
|
|
345
|
+
server: string;
|
|
346
|
+
name: string;
|
|
347
|
+
}>;
|
|
348
|
+
recommendation: string;
|
|
349
|
+
};
|
|
350
|
+
type ThresholdResult = {
|
|
351
|
+
flag: string;
|
|
352
|
+
tokenizer?: DiffTokenizer;
|
|
353
|
+
allowed: number;
|
|
354
|
+
actual: number;
|
|
355
|
+
exceeded: boolean;
|
|
356
|
+
};
|
|
357
|
+
type TareDiffReport = {
|
|
358
|
+
version: string;
|
|
359
|
+
generatedAt: string;
|
|
360
|
+
base: {
|
|
361
|
+
path: string;
|
|
362
|
+
reportVersion: string;
|
|
363
|
+
generatedAt: string;
|
|
364
|
+
};
|
|
365
|
+
head: {
|
|
366
|
+
path: string;
|
|
367
|
+
reportVersion: string;
|
|
368
|
+
generatedAt: string;
|
|
369
|
+
};
|
|
370
|
+
summary: {
|
|
371
|
+
servers: NumericDelta;
|
|
372
|
+
tools: NumericDelta;
|
|
373
|
+
estimatedTokens: TokenDelta;
|
|
374
|
+
overlapClusters: NumericDelta;
|
|
375
|
+
};
|
|
376
|
+
servers: {
|
|
377
|
+
added: DiffServer[];
|
|
378
|
+
removed: DiffServer[];
|
|
379
|
+
changed: DiffServerChange[];
|
|
380
|
+
};
|
|
381
|
+
tools: {
|
|
382
|
+
added: DiffTool[];
|
|
383
|
+
removed: DiffTool[];
|
|
384
|
+
changed: DiffToolChange[];
|
|
385
|
+
};
|
|
386
|
+
overlapClusters: {
|
|
387
|
+
added: DiffOverlapCluster[];
|
|
388
|
+
removed: DiffOverlapCluster[];
|
|
389
|
+
};
|
|
390
|
+
thresholds: ThresholdResult[];
|
|
391
|
+
recommendations: Array<{
|
|
392
|
+
type: string;
|
|
393
|
+
message: string;
|
|
394
|
+
}>;
|
|
395
|
+
warnings: string[];
|
|
396
|
+
};
|
|
397
|
+
type DiffThresholdOptions = {
|
|
398
|
+
maxTokenIncrease?: number;
|
|
399
|
+
maxToolIncrease?: number;
|
|
400
|
+
maxServerIncrease?: number;
|
|
401
|
+
maxOverlapIncrease?: number;
|
|
402
|
+
tokenizer: DiffTokenizer;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
type DiffReportsOptions = {
|
|
406
|
+
basePath: string;
|
|
407
|
+
headPath: string;
|
|
408
|
+
generatedAt?: string;
|
|
409
|
+
};
|
|
410
|
+
type ReportOverlapCluster = TareReport["overlapClusters"][number];
|
|
411
|
+
declare function diffReports(baseReport: TareReport, headReport: TareReport, options: DiffReportsOptions): TareDiffReport;
|
|
412
|
+
declare function overlapClusterIdentity(cluster: ReportOverlapCluster): string;
|
|
413
|
+
|
|
414
|
+
declare function evaluateDiffThresholds(report: TareDiffReport, options: DiffThresholdOptions): ThresholdResult[];
|
|
415
|
+
declare function hasThresholdFailure(report: Pick<TareDiffReport, "thresholds">): boolean;
|
|
416
|
+
|
|
417
|
+
type DiffHumanReporterOptions = {
|
|
418
|
+
tokenizer: DiffTokenizer;
|
|
419
|
+
};
|
|
420
|
+
declare function renderDiffHumanReport(report: TareDiffReport, options: DiffHumanReporterOptions): string;
|
|
421
|
+
declare function renderDiffThresholdFailure(report: TareDiffReport, options: DiffHumanReporterOptions): string;
|
|
422
|
+
|
|
423
|
+
declare function renderDiffJsonReport(report: TareDiffReport): string;
|
|
424
|
+
|
|
425
|
+
export { type AnalyzedTool, type AttributedMcpToolInput, type ClaudeTokenizerMode, type Confidence, type DiffHumanReporterOptions, type DiffOverlapCluster, type DiffReportsOptions, type DiffServer, type DiffServerChange, type DiffThresholdOptions, type DiffTokenTotals, type DiffTokenizer, type DiffTool, type DiffToolChange, type DualTokenEstimate, type InspectedServer, type InspectionMode, LocalClaudeEstimator, type McpToolDefinition, type McpToolInput, type MeasureToolsOptions, type NormalizedServer, type NumericDelta, OpenAICl100kCounter, type OverlapCluster, OverlapDetector, ReportLoadError, type ReportTransportKind, type TareDiffReport, type TareReport, TareReportSchema, type ThresholdResult, type TokenCounter, type TokenDelta, type TokenEstimate, TokenEstimator, type ToolContextPayload, type TransportKind, VERSION, type ValueDelta, analyzeServers, buildRecommendations, buildServerEnv, createStaticInspection, diffReports, discoverConfigs, evaluateDiffThresholds, getDefaultConfigCandidates, hasThresholdFailure, inspectStdioServer, inspectStreamableHttpServer, loadReport, measureTools, normalizeServer, overlapClusterIdentity, parseConfigFile, parseConfigText, renderBudgetFailure, renderDiffHumanReport, renderDiffJsonReport, renderDiffThresholdFailure, renderHumanReport, renderJsonReport };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/version.ts
|
|
4
|
-
var VERSION = "0.
|
|
4
|
+
var VERSION = "0.3.0";
|
|
5
5
|
|
|
6
6
|
// src/discovery/discoverConfigs.ts
|
|
7
7
|
import os2 from "os";
|
|
@@ -1017,7 +1017,7 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
1017
1017
|
estimatedTokens: analyzedTool.estimatedTokens,
|
|
1018
1018
|
hasInputSchema: analyzedTool.hasInputSchema
|
|
1019
1019
|
});
|
|
1020
|
-
if (server.inspectionMode === "live") {
|
|
1020
|
+
if (server.inspectionMode === "live" || server.inspectionMode === "programmatic") {
|
|
1021
1021
|
liveToolsForOverlap.push(analyzedTool);
|
|
1022
1022
|
}
|
|
1023
1023
|
}
|
|
@@ -1074,7 +1074,9 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
1074
1074
|
openaiCl100k: windowUsage(totalOpenAi, 2e5)
|
|
1075
1075
|
}
|
|
1076
1076
|
},
|
|
1077
|
-
insufficientServers: analyzedServers.filter(
|
|
1077
|
+
insufficientServers: analyzedServers.filter(
|
|
1078
|
+
(server) => server.inspectionMode === "static-insufficient" || server.inspectionMode === "fallback-static-insufficient"
|
|
1079
|
+
).length
|
|
1078
1080
|
},
|
|
1079
1081
|
servers: analyzedServers,
|
|
1080
1082
|
overlapClusters,
|
|
@@ -1082,7 +1084,7 @@ async function analyzeServers(inspectedServers, tokenEstimator, options) {
|
|
|
1082
1084
|
warnings,
|
|
1083
1085
|
metadata: {
|
|
1084
1086
|
staticOnly: options.staticOnly,
|
|
1085
|
-
inspectionMode: options.staticOnly ? "static-only" : "live default"
|
|
1087
|
+
inspectionMode: options.inspectionMode ?? (options.staticOnly ? "static-only" : "live default")
|
|
1086
1088
|
}
|
|
1087
1089
|
};
|
|
1088
1090
|
report.recommendations = buildRecommendations(report);
|
|
@@ -1288,6 +1290,121 @@ var TokenEstimator = class {
|
|
|
1288
1290
|
}
|
|
1289
1291
|
};
|
|
1290
1292
|
|
|
1293
|
+
// src/api/measureTools.ts
|
|
1294
|
+
var DEFAULT_SERVER_NAME = "agent";
|
|
1295
|
+
var PROGRAMMATIC_SOURCE = "programmatic";
|
|
1296
|
+
async function measureTools(tools, options = {}) {
|
|
1297
|
+
validateTools(tools);
|
|
1298
|
+
const normalizedOptions = validateOptions(options);
|
|
1299
|
+
const tokenWarnings = [];
|
|
1300
|
+
const inspectedServers = inspectedServersFromTools(tools, normalizedOptions.serverName);
|
|
1301
|
+
const report = await analyzeServers(
|
|
1302
|
+
inspectedServers,
|
|
1303
|
+
new TokenEstimator({
|
|
1304
|
+
claudeTokenizerMode: normalizedOptions.claudeTokenizerMode ?? "local",
|
|
1305
|
+
anthropicApiKey: normalizedOptions.anthropicApiKey,
|
|
1306
|
+
anthropicModel: normalizedOptions.anthropicModel,
|
|
1307
|
+
timeoutMs: normalizedOptions.timeoutMs,
|
|
1308
|
+
onWarning: (warning) => tokenWarnings.push(warning)
|
|
1309
|
+
}),
|
|
1310
|
+
{
|
|
1311
|
+
configFiles: 0,
|
|
1312
|
+
staticOnly: false,
|
|
1313
|
+
inspectionMode: "programmatic"
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
report.warnings.push(...tokenWarnings);
|
|
1317
|
+
if (normalizedOptions.budget !== void 0) {
|
|
1318
|
+
report.metadata.budgetTokens = normalizedOptions.budget;
|
|
1319
|
+
report.metadata.budgetTokenizer = "claude";
|
|
1320
|
+
report.metadata.budgetExceeded = report.summary.estimatedTokens.claude > normalizedOptions.budget;
|
|
1321
|
+
}
|
|
1322
|
+
return report;
|
|
1323
|
+
}
|
|
1324
|
+
function inspectedServersFromTools(tools, fallbackServerName) {
|
|
1325
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1326
|
+
const defaultServerName = normalizeServerName(fallbackServerName) ?? DEFAULT_SERVER_NAME;
|
|
1327
|
+
for (const tool of tools) {
|
|
1328
|
+
const serverName = serverNameForTool(tool, defaultServerName);
|
|
1329
|
+
const group = groups.get(serverName) ?? [];
|
|
1330
|
+
group.push(toToolDefinition(tool));
|
|
1331
|
+
groups.set(serverName, group);
|
|
1332
|
+
}
|
|
1333
|
+
return [...groups.entries()].map(([name, toolDefinitions]) => ({
|
|
1334
|
+
name,
|
|
1335
|
+
sourceConfigPath: PROGRAMMATIC_SOURCE,
|
|
1336
|
+
transport: "programmatic",
|
|
1337
|
+
toolDefinitions,
|
|
1338
|
+
inspectionMode: "programmatic",
|
|
1339
|
+
confidence: "high",
|
|
1340
|
+
warnings: []
|
|
1341
|
+
}));
|
|
1342
|
+
}
|
|
1343
|
+
function serverNameForTool(tool, fallbackServerName) {
|
|
1344
|
+
return isAttributedTool(tool) ? normalizeServerName(tool.server) ?? fallbackServerName : fallbackServerName;
|
|
1345
|
+
}
|
|
1346
|
+
function toToolDefinition(tool) {
|
|
1347
|
+
return {
|
|
1348
|
+
name: tool.name,
|
|
1349
|
+
description: tool.description,
|
|
1350
|
+
inputSchema: tool.inputSchema,
|
|
1351
|
+
annotations: tool.annotations,
|
|
1352
|
+
outputSchema: tool.outputSchema,
|
|
1353
|
+
metadata: tool.metadata
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function isAttributedTool(tool) {
|
|
1357
|
+
return "server" in tool && typeof tool.server === "string" && tool.server.trim().length > 0;
|
|
1358
|
+
}
|
|
1359
|
+
function normalizeServerName(serverName) {
|
|
1360
|
+
const trimmed = serverName?.trim();
|
|
1361
|
+
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
1362
|
+
}
|
|
1363
|
+
function validateTools(tools) {
|
|
1364
|
+
if (!Array.isArray(tools)) {
|
|
1365
|
+
throw new TypeError("measureTools expected tools to be an array.");
|
|
1366
|
+
}
|
|
1367
|
+
for (const [index, tool] of tools.entries()) {
|
|
1368
|
+
if (!tool || typeof tool !== "object") {
|
|
1369
|
+
throw new TypeError(`measureTools expected tools[${index}] to be an object.`);
|
|
1370
|
+
}
|
|
1371
|
+
if (typeof tool.name !== "string" || tool.name.trim().length === 0) {
|
|
1372
|
+
throw new TypeError(`measureTools expected tools[${index}].name to be a non-empty string.`);
|
|
1373
|
+
}
|
|
1374
|
+
if ("server" in tool && tool.server !== void 0 && (typeof tool.server !== "string" || tool.server.trim().length === 0)) {
|
|
1375
|
+
throw new TypeError(
|
|
1376
|
+
`measureTools expected tools[${index}].server to be a non-empty string when provided.`
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function validateOptions(options) {
|
|
1382
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1383
|
+
throw new TypeError("measureTools expected options to be an object when provided.");
|
|
1384
|
+
}
|
|
1385
|
+
if (options.serverName !== void 0 && normalizeServerName(options.serverName) === void 0) {
|
|
1386
|
+
throw new TypeError("measureTools expected options.serverName to be a non-empty string.");
|
|
1387
|
+
}
|
|
1388
|
+
if (options.budget !== void 0 && (!Number.isFinite(options.budget) || options.budget < 0)) {
|
|
1389
|
+
throw new TypeError("measureTools expected options.budget to be a non-negative number.");
|
|
1390
|
+
}
|
|
1391
|
+
if (options.timeoutMs !== void 0 && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
|
|
1392
|
+
throw new TypeError("measureTools expected options.timeoutMs to be a positive number.");
|
|
1393
|
+
}
|
|
1394
|
+
if (options.claudeTokenizerMode !== void 0 && options.claudeTokenizerMode !== "local" && options.claudeTokenizerMode !== "api") {
|
|
1395
|
+
throw new TypeError(
|
|
1396
|
+
'measureTools expected options.claudeTokenizerMode to be "local" or "api".'
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
if (options.anthropicApiKey !== void 0 && typeof options.anthropicApiKey !== "string") {
|
|
1400
|
+
throw new TypeError("measureTools expected options.anthropicApiKey to be a string.");
|
|
1401
|
+
}
|
|
1402
|
+
if (options.anthropicModel !== void 0 && typeof options.anthropicModel !== "string") {
|
|
1403
|
+
throw new TypeError("measureTools expected options.anthropicModel to be a string.");
|
|
1404
|
+
}
|
|
1405
|
+
return options;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1291
1408
|
// src/reporters/humanReporter.ts
|
|
1292
1409
|
import pc from "picocolors";
|
|
1293
1410
|
function formatNumber(value) {
|
|
@@ -1381,7 +1498,9 @@ function renderHumanReport(report) {
|
|
|
1381
1498
|
}
|
|
1382
1499
|
}
|
|
1383
1500
|
}
|
|
1384
|
-
const insufficientServers = report.servers.filter(
|
|
1501
|
+
const insufficientServers = report.servers.filter(
|
|
1502
|
+
(server) => server.inspectionMode === "static-insufficient" || server.inspectionMode === "fallback-static-insufficient"
|
|
1503
|
+
);
|
|
1385
1504
|
if (insufficientServers.length > 0) {
|
|
1386
1505
|
lines.push("");
|
|
1387
1506
|
lines.push("Insufficient data:");
|
|
@@ -1483,13 +1602,18 @@ var TareReportSchema = z3.object({
|
|
|
1483
1602
|
z3.object({
|
|
1484
1603
|
name: z3.string(),
|
|
1485
1604
|
sourceConfigPath: z3.string(),
|
|
1486
|
-
transport: z3.enum(["stdio", "streamable-http", "sse", "unknown"]),
|
|
1605
|
+
transport: z3.enum(["stdio", "streamable-http", "sse", "programmatic", "unknown"]),
|
|
1487
1606
|
command: z3.string().optional(),
|
|
1488
1607
|
args: z3.array(z3.string()).optional(),
|
|
1489
1608
|
urlHost: z3.string().optional(),
|
|
1490
1609
|
toolCount: z3.number(),
|
|
1491
1610
|
estimatedTokens: TokenTotalsSchema,
|
|
1492
|
-
inspectionMode: z3.enum([
|
|
1611
|
+
inspectionMode: z3.enum([
|
|
1612
|
+
"live",
|
|
1613
|
+
"programmatic",
|
|
1614
|
+
"static-insufficient",
|
|
1615
|
+
"fallback-static-insufficient"
|
|
1616
|
+
]),
|
|
1493
1617
|
confidence: z3.enum(["high", "medium", "low"]),
|
|
1494
1618
|
warnings: z3.array(z3.string()),
|
|
1495
1619
|
tools: z3.array(
|
|
@@ -1512,7 +1636,10 @@ var TareReportSchema = z3.object({
|
|
|
1512
1636
|
warnings: z3.array(z3.string()),
|
|
1513
1637
|
metadata: z3.object({
|
|
1514
1638
|
staticOnly: z3.boolean(),
|
|
1515
|
-
inspectionMode: z3.enum(["live default", "static-only"])
|
|
1639
|
+
inspectionMode: z3.enum(["live default", "static-only", "programmatic"]),
|
|
1640
|
+
budgetExceeded: z3.boolean().optional(),
|
|
1641
|
+
budgetTokens: z3.number().optional(),
|
|
1642
|
+
budgetTokenizer: z3.literal("claude").optional()
|
|
1516
1643
|
}).passthrough()
|
|
1517
1644
|
}).passthrough();
|
|
1518
1645
|
var ReportLoadError = class extends Error {
|
|
@@ -2186,6 +2313,7 @@ export {
|
|
|
2186
2313
|
inspectStdioServer,
|
|
2187
2314
|
inspectStreamableHttpServer,
|
|
2188
2315
|
loadReport,
|
|
2316
|
+
measureTools,
|
|
2189
2317
|
normalizeServer,
|
|
2190
2318
|
overlapClusterIdentity,
|
|
2191
2319
|
parseConfigFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tare-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Local-first CLI for analyzing MCP context weight and tool ambiguity.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"tare-mcp": "./dist/cli.js"
|
|
10
10
|
},
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
11
12
|
"exports": {
|
|
12
|
-
".":
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
13
17
|
},
|
|
14
18
|
"main": "./dist/index.js",
|
|
15
19
|
"repository": {
|