tare-mcp 0.2.1 → 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 +119 -38
- 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
|
@@ -4,16 +4,30 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/tare-mcp)
|
|
5
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
|
-
|
|
10
|
+
npm install tare-mcp
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
```ts
|
|
14
|
+
import { measureTools } from "tare-mcp";
|
|
15
|
+
|
|
16
|
+
const tools = await mcpClient.listTools();
|
|
17
|
+
const report = await measureTools(tools, { budget: 40_000 });
|
|
15
18
|
|
|
16
|
-
|
|
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:
|
|
17
31
|
|
|
18
32
|
- how many tools your agent sees
|
|
19
33
|
- how much context those tools consume, estimated for Claude and OpenAI cl100k
|
|
@@ -21,36 +35,20 @@ It did not make them cheap to carry.
|
|
|
21
35
|
- which tools overlap and compete for model attention
|
|
22
36
|
- whether your setup exceeds a context budget
|
|
23
37
|
|
|
24
|
-
Use
|
|
38
|
+
Use the CLI when you want to inspect config-discovered MCP servers locally:
|
|
25
39
|
|
|
26
40
|
```bash
|
|
27
41
|
npx tare-mcp
|
|
28
42
|
```
|
|
29
43
|
|
|
30
|
-
Use it in CI to catch MCP bloat before merge:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# one time
|
|
34
|
-
mkdir -p .tare
|
|
35
|
-
npx tare-mcp --json > .tare/baseline.json
|
|
36
|
-
git add .tare/baseline.json
|
|
37
|
-
|
|
38
|
-
# on every PR
|
|
39
|
-
npx tare-mcp --json > tare-report.json
|
|
40
|
-
npx tare-mcp diff \
|
|
41
|
-
--base .tare/baseline.json \
|
|
42
|
-
--head tare-report.json \
|
|
43
|
-
--max-token-increase 5000 \
|
|
44
|
-
--max-tool-increase 20
|
|
45
|
-
```
|
|
46
|
-
|
|
47
44
|
Think of it as `du -sh node_modules`, but for agent tool context.
|
|
48
45
|
|
|
49
46
|
## Table of Contents
|
|
50
47
|
|
|
51
48
|
- [Why This Matters](#why-this-matters)
|
|
52
49
|
- [Why Token Count Is Not the Whole Problem](#why-token-count-is-not-the-whole-problem)
|
|
53
|
-
- [
|
|
50
|
+
- [Using tare-mcp in your agent](#using-tare-mcp-in-your-agent)
|
|
51
|
+
- [CLI Quickstart](#cli-quickstart)
|
|
54
52
|
- [Hosted MCP Quickstart](#hosted-mcp-quickstart)
|
|
55
53
|
- [Scenario Examples](#scenario-examples)
|
|
56
54
|
- [Example Output](#example-output)
|
|
@@ -88,7 +86,80 @@ If three servers all expose tools that look like "search", the model has to choo
|
|
|
88
86
|
- what your tools weigh
|
|
89
87
|
- where your tools overlap
|
|
90
88
|
|
|
91
|
-
##
|
|
89
|
+
## Using tare-mcp in your agent
|
|
90
|
+
|
|
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.
|
|
92
|
+
|
|
93
|
+
Use `measureTools()` when you already have the tool definitions in memory:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { measureTools } from "tare-mcp";
|
|
97
|
+
|
|
98
|
+
const tools = await mcpClient.listTools();
|
|
99
|
+
const report = await measureTools(tools);
|
|
100
|
+
|
|
101
|
+
console.log(
|
|
102
|
+
`MCP tool surface: ${report.summary.tools} tools, ~${report.summary.estimatedTokens.claude} Claude tokens`
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
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
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
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
|
|
92
163
|
|
|
93
164
|
Run it without installing:
|
|
94
165
|
|
|
@@ -314,10 +385,11 @@ Recommendations:
|
|
|
314
385
|
|
|
315
386
|
## Supported transports
|
|
316
387
|
|
|
317
|
-
v0.
|
|
388
|
+
v0.3 supports:
|
|
318
389
|
|
|
319
390
|
- stdio MCP servers
|
|
320
391
|
- Streamable HTTP MCP servers
|
|
392
|
+
- programmatic tool definitions through `measureTools()`
|
|
321
393
|
|
|
322
394
|
SSE may be supported best-effort later.
|
|
323
395
|
|
|
@@ -357,12 +429,12 @@ That mode requires `ANTHROPIC_API_KEY` and uses Anthropic's `POST /v1/messages/c
|
|
|
357
429
|
|
|
358
430
|
Environment variables that control tokenization:
|
|
359
431
|
|
|
360
|
-
| Variable
|
|
361
|
-
|
|
362
|
-
| `ANTHROPIC_API_KEY`
|
|
363
|
-
| `TARE_CLAUDE_TOKENIZER`
|
|
364
|
-
| `TARE_ANTHROPIC_MODEL`
|
|
365
|
-
| `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 |
|
|
366
438
|
|
|
367
439
|
## Security model
|
|
368
440
|
|
|
@@ -512,11 +584,11 @@ When a growth is intentional, regenerate `.tare/baseline.json` on the accepted s
|
|
|
512
584
|
|
|
513
585
|
Exit codes:
|
|
514
586
|
|
|
515
|
-
| Code | Meaning
|
|
516
|
-
|
|
517
|
-
|
|
|
518
|
-
|
|
|
519
|
-
|
|
|
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. |
|
|
520
592
|
|
|
521
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.
|
|
522
594
|
|
|
@@ -579,6 +651,7 @@ npx tare-mcp --no-exec --json
|
|
|
579
651
|
## Publishing to npm
|
|
580
652
|
|
|
581
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).
|
|
582
655
|
|
|
583
656
|
To publish from GitHub Actions:
|
|
584
657
|
|
|
@@ -656,15 +729,23 @@ Options:
|
|
|
656
729
|
|
|
657
730
|
## Roadmap
|
|
658
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
|
+
|
|
659
737
|
v0.2:
|
|
738
|
+
|
|
660
739
|
- [x] PR diff/regression mode for JSON reports
|
|
661
740
|
- [x] Threshold flags for token, tool, server, and overlap growth
|
|
662
741
|
|
|
663
742
|
Next:
|
|
743
|
+
|
|
664
744
|
- [ ] Per-tool schema breakdown
|
|
665
745
|
- [ ] Context budget config file (`tare.config.json`)
|
|
666
746
|
|
|
667
747
|
Later:
|
|
748
|
+
|
|
668
749
|
- [ ] Better SSE fallback
|
|
669
750
|
- [ ] Improved Claude local token estimator
|
|
670
751
|
- [ ] Opt-in API-backed token counting improvements
|
|
@@ -673,7 +754,7 @@ Later:
|
|
|
673
754
|
- [ ] MCP profile generator
|
|
674
755
|
- [ ] `tare-mcp --fix` to generate lean MCP profiles
|
|
675
756
|
|
|
676
|
-
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.
|
|
677
758
|
|
|
678
759
|
## License
|
|
679
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": {
|